summaryrefslogtreecommitdiff
path: root/Mailman/database
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/database')
-rw-r--r--Mailman/database/mailman.sql354
-rw-r--r--Mailman/database/message.py6
-rw-r--r--Mailman/database/messagestore.py91
3 files changed, 221 insertions, 230 deletions
diff --git a/Mailman/database/mailman.sql b/Mailman/database/mailman.sql
index a20b1b118..cff4daba0 100644
--- a/Mailman/database/mailman.sql
+++ b/Mailman/database/mailman.sql
@@ -1,201 +1,201 @@
CREATE TABLE _request (
- id INTEGER NOT NULL,
- "key" TEXT,
- request_type TEXT,
- data_hash TEXT,
- mailing_list_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT _request_mailing_list_id_fk FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id)
+ id INTEGER NOT NULL,
+ "key" TEXT,
+ request_type TEXT,
+ data_hash TEXT,
+ mailing_list_id INTEGER,
+ PRIMARY KEY (id),
+ CONSTRAINT _request_mailing_list_id_fk FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id)
);
CREATE TABLE address (
- id INTEGER NOT NULL,
- address TEXT,
- _original TEXT,
- real_name TEXT,
- verified_on TIMESTAMP,
- registered_on TIMESTAMP,
- user_id INTEGER,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT address_user_id_fk FOREIGN KEY(user_id) REFERENCES user (id),
- CONSTRAINT address_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
+ id INTEGER NOT NULL,
+ address TEXT,
+ _original TEXT,
+ real_name TEXT,
+ verified_on TIMESTAMP,
+ registered_on TIMESTAMP,
+ user_id INTEGER,
+ preferences_id INTEGER,
+ PRIMARY KEY (id),
+ CONSTRAINT address_user_id_fk FOREIGN KEY(user_id) REFERENCES user (id),
+ CONSTRAINT address_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
);
CREATE TABLE language (
- id INTEGER NOT NULL,
- code TEXT,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ code TEXT,
+ PRIMARY KEY (id)
);
CREATE TABLE mailinglist (
- id INTEGER NOT NULL,
- list_name TEXT,
- host_name TEXT,
- created_at TIMESTAMP,
- web_page_url TEXT,
- admin_member_chunksize INTEGER,
- hold_and_cmd_autoresponses BLOB,
- next_request_id INTEGER,
- next_digest_number INTEGER,
- admin_responses BLOB,
- postings_responses BLOB,
- request_responses BLOB,
- digest_last_sent_at NUMERIC(10, 2),
- one_last_digest BLOB,
- volume INTEGER,
- last_post_time TIMESTAMP,
- accept_these_nonmembers BLOB,
- acceptable_aliases BLOB,
- admin_immed_notify BOOLEAN,
- admin_notify_mchanges BOOLEAN,
- administrivia BOOLEAN,
- advertised BOOLEAN,
- anonymous_list BOOLEAN,
- archive BOOLEAN,
- archive_private BOOLEAN,
- archive_volume_frequency INTEGER,
- autorespond_admin BOOLEAN,
- autorespond_postings BOOLEAN,
- autorespond_requests INTEGER,
- autoresponse_admin_text TEXT,
- autoresponse_graceperiod TEXT,
- autoresponse_postings_text TEXT,
- autoresponse_request_text TEXT,
- ban_list BLOB,
- bounce_info_stale_after TEXT,
- bounce_matching_headers TEXT,
- bounce_notify_owner_on_disable BOOLEAN,
- bounce_notify_owner_on_removal BOOLEAN,
- bounce_processing BOOLEAN,
- bounce_score_threshold INTEGER,
- bounce_unrecognized_goes_to_list_owner BOOLEAN,
- bounce_you_are_disabled_warnings INTEGER,
- bounce_you_are_disabled_warnings_interval TEXT,
- collapse_alternatives BOOLEAN,
- convert_html_to_plaintext BOOLEAN,
- default_member_moderation BOOLEAN,
- description TEXT,
- digest_footer TEXT,
- digest_header TEXT,
- digest_is_default BOOLEAN,
- digest_send_periodic BOOLEAN,
- digest_size_threshold INTEGER,
- digest_volume_frequency INTEGER,
- digestable BOOLEAN,
- discard_these_nonmembers BLOB,
- emergency BOOLEAN,
- encode_ascii_prefixes BOOLEAN,
- filter_action INTEGER,
- filter_content BOOLEAN,
- filter_filename_extensions BLOB,
- filter_mime_types BLOB,
- first_strip_reply_to BOOLEAN,
- forward_auto_discards BOOLEAN,
- gateway_to_mail BOOLEAN,
- gateway_to_news BOOLEAN,
- generic_nonmember_action INTEGER,
- goodbye_msg TEXT,
- header_filter_rules BLOB,
- hold_these_nonmembers BLOB,
- include_list_post_header BOOLEAN,
- include_rfc2369_headers BOOLEAN,
- info TEXT,
- linked_newsgroup TEXT,
- max_days_to_hold INTEGER,
- max_message_size INTEGER,
- max_num_recipients INTEGER,
- member_moderation_action BOOLEAN,
- member_moderation_notice TEXT,
- mime_is_default_digest BOOLEAN,
- moderator_password TEXT,
- msg_footer TEXT,
- msg_header TEXT,
- new_member_options INTEGER,
- news_moderation TEXT,
- news_prefix_subject_too BOOLEAN,
- nntp_host TEXT,
- nondigestable BOOLEAN,
- nonmember_rejection_notice TEXT,
- obscure_addresses BOOLEAN,
- pass_filename_extensions BLOB,
- pass_mime_types BLOB,
- personalize TEXT,
- post_id INTEGER,
- preferred_language TEXT,
- private_roster BOOLEAN,
- real_name TEXT,
- reject_these_nonmembers BLOB,
- reply_goes_to_list TEXT,
- reply_to_address TEXT,
- require_explicit_destination BOOLEAN,
- respond_to_post_requests BOOLEAN,
- scrub_nondigest BOOLEAN,
- send_goodbye_msg BOOLEAN,
- send_reminders BOOLEAN,
- send_welcome_msg BOOLEAN,
- subject_prefix TEXT,
- subscribe_auto_approval BLOB,
- subscribe_policy INTEGER,
- topics BLOB,
- topics_bodylines_limit INTEGER,
- topics_enabled BOOLEAN,
- unsubscribe_policy INTEGER,
- welcome_msg TEXT,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ list_name TEXT,
+ host_name TEXT,
+ created_at TIMESTAMP,
+ web_page_url TEXT,
+ admin_member_chunksize INTEGER,
+ hold_and_cmd_autoresponses BLOB,
+ next_request_id INTEGER,
+ next_digest_number INTEGER,
+ admin_responses BLOB,
+ postings_responses BLOB,
+ request_responses BLOB,
+ digest_last_sent_at NUMERIC(10, 2),
+ one_last_digest BLOB,
+ volume INTEGER,
+ last_post_time TIMESTAMP,
+ accept_these_nonmembers BLOB,
+ acceptable_aliases BLOB,
+ admin_immed_notify BOOLEAN,
+ admin_notify_mchanges BOOLEAN,
+ administrivia BOOLEAN,
+ advertised BOOLEAN,
+ anonymous_list BOOLEAN,
+ archive BOOLEAN,
+ archive_private BOOLEAN,
+ archive_volume_frequency INTEGER,
+ autorespond_admin BOOLEAN,
+ autorespond_postings BOOLEAN,
+ autorespond_requests INTEGER,
+ autoresponse_admin_text TEXT,
+ autoresponse_graceperiod TEXT,
+ autoresponse_postings_text TEXT,
+ autoresponse_request_text TEXT,
+ ban_list BLOB,
+ bounce_info_stale_after TEXT,
+ bounce_matching_headers TEXT,
+ bounce_notify_owner_on_disable BOOLEAN,
+ bounce_notify_owner_on_removal BOOLEAN,
+ bounce_processing BOOLEAN,
+ bounce_score_threshold INTEGER,
+ bounce_unrecognized_goes_to_list_owner BOOLEAN,
+ bounce_you_are_disabled_warnings INTEGER,
+ bounce_you_are_disabled_warnings_interval TEXT,
+ collapse_alternatives BOOLEAN,
+ convert_html_to_plaintext BOOLEAN,
+ default_member_moderation BOOLEAN,
+ description TEXT,
+ digest_footer TEXT,
+ digest_header TEXT,
+ digest_is_default BOOLEAN,
+ digest_send_periodic BOOLEAN,
+ digest_size_threshold INTEGER,
+ digest_volume_frequency INTEGER,
+ digestable BOOLEAN,
+ discard_these_nonmembers BLOB,
+ emergency BOOLEAN,
+ encode_ascii_prefixes BOOLEAN,
+ filter_action INTEGER,
+ filter_content BOOLEAN,
+ filter_filename_extensions BLOB,
+ filter_mime_types BLOB,
+ first_strip_reply_to BOOLEAN,
+ forward_auto_discards BOOLEAN,
+ gateway_to_mail BOOLEAN,
+ gateway_to_news BOOLEAN,
+ generic_nonmember_action INTEGER,
+ goodbye_msg TEXT,
+ header_filter_rules BLOB,
+ hold_these_nonmembers BLOB,
+ include_list_post_header BOOLEAN,
+ include_rfc2369_headers BOOLEAN,
+ info TEXT,
+ linked_newsgroup TEXT,
+ max_days_to_hold INTEGER,
+ max_message_size INTEGER,
+ max_num_recipients INTEGER,
+ member_moderation_action BOOLEAN,
+ member_moderation_notice TEXT,
+ mime_is_default_digest BOOLEAN,
+ moderator_password TEXT,
+ msg_footer TEXT,
+ msg_header TEXT,
+ new_member_options INTEGER,
+ news_moderation TEXT,
+ news_prefix_subject_too BOOLEAN,
+ nntp_host TEXT,
+ nondigestable BOOLEAN,
+ nonmember_rejection_notice TEXT,
+ obscure_addresses BOOLEAN,
+ pass_filename_extensions BLOB,
+ pass_mime_types BLOB,
+ personalize TEXT,
+ post_id INTEGER,
+ preferred_language TEXT,
+ private_roster BOOLEAN,
+ real_name TEXT,
+ reject_these_nonmembers BLOB,
+ reply_goes_to_list TEXT,
+ reply_to_address TEXT,
+ require_explicit_destination BOOLEAN,
+ respond_to_post_requests BOOLEAN,
+ scrub_nondigest BOOLEAN,
+ send_goodbye_msg BOOLEAN,
+ send_reminders BOOLEAN,
+ send_welcome_msg BOOLEAN,
+ subject_prefix TEXT,
+ subscribe_auto_approval BLOB,
+ subscribe_policy INTEGER,
+ topics BLOB,
+ topics_bodylines_limit INTEGER,
+ topics_enabled BOOLEAN,
+ unsubscribe_policy INTEGER,
+ welcome_msg TEXT,
+ PRIMARY KEY (id)
);
CREATE TABLE member (
- id INTEGER NOT NULL,
- role TEXT,
- mailing_list TEXT,
- address_id INTEGER,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT member_address_id_fk FOREIGN KEY(address_id) REFERENCES address (id),
- CONSTRAINT member_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
+ id INTEGER NOT NULL,
+ role TEXT,
+ mailing_list TEXT,
+ address_id INTEGER,
+ preferences_id INTEGER,
+ PRIMARY KEY (id),
+ CONSTRAINT member_address_id_fk FOREIGN KEY(address_id) REFERENCES address (id),
+ CONSTRAINT member_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
);
CREATE TABLE message (
- id INTEGER NOT NULL,
- hash TEXT,
- path TEXT,
- message_id TEXT,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ message_id_hash TEXT,
+ path TEXT,
+ message_id TEXT,
+ PRIMARY KEY (id)
);
CREATE TABLE pended (
- id INTEGER NOT NULL,
- token TEXT,
- expiration_date TIMESTAMP,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ token TEXT,
+ expiration_date TIMESTAMP,
+ PRIMARY KEY (id)
);
CREATE TABLE pendedkeyvalue (
- id INTEGER NOT NULL,
- "key" TEXT,
- value TEXT,
- pended_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT pendedkeyvalue_pended_id_fk FOREIGN KEY(pended_id) REFERENCES pended (id)
+ id INTEGER NOT NULL,
+ "key" TEXT,
+ value TEXT,
+ pended_id INTEGER,
+ PRIMARY KEY (id),
+ CONSTRAINT pendedkeyvalue_pended_id_fk FOREIGN KEY(pended_id) REFERENCES pended (id)
);
CREATE TABLE preferences (
- id INTEGER NOT NULL,
- acknowledge_posts BOOLEAN,
- hide_address BOOLEAN,
- preferred_language TEXT,
- receive_list_copy BOOLEAN,
- receive_own_postings BOOLEAN,
- delivery_mode TEXT,
- delivery_status TEXT,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ acknowledge_posts BOOLEAN,
+ hide_address BOOLEAN,
+ preferred_language TEXT,
+ receive_list_copy BOOLEAN,
+ receive_own_postings BOOLEAN,
+ delivery_mode TEXT,
+ delivery_status TEXT,
+ PRIMARY KEY (id)
);
CREATE TABLE user (
- id INTEGER NOT NULL,
- real_name TEXT,
- password TEXT,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT user_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
+ id INTEGER NOT NULL,
+ real_name TEXT,
+ password TEXT,
+ preferences_id INTEGER,
+ PRIMARY KEY (id),
+ CONSTRAINT user_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
);
CREATE TABLE version (
- id INTEGER NOT NULL,
- component TEXT,
- version INTEGER,
- PRIMARY KEY (id)
+ id INTEGER NOT NULL,
+ component TEXT,
+ version INTEGER,
+ PRIMARY KEY (id)
);
CREATE INDEX ix__request_mailing_list_id ON _request (mailing_list_id);
CREATE INDEX ix_address_preferences_id ON address (preferences_id);
diff --git a/Mailman/database/message.py b/Mailman/database/message.py
index b8a7e3dfb..01e71b0c0 100644
--- a/Mailman/database/message.py
+++ b/Mailman/database/message.py
@@ -31,12 +31,12 @@ class Message(Model):
id = Int(primary=True, default=AutoReload)
message_id = Unicode()
- hash = RawStr()
+ message_id_hash = RawStr()
path = RawStr()
# This is a Messge-ID field representation, not a database row id.
- def __init__(self, message_id, hash, path):
+ def __init__(self, message_id, message_id_hash, path):
self.message_id = message_id
- self.hash = hash
+ self.message_id_hash = message_id_hash
self.path = path
config.db.store.add(self)
diff --git a/Mailman/database/messagestore.py b/Mailman/database/messagestore.py
index 69c5d58d5..57c0042e8 100644
--- a/Mailman/database/messagestore.py
+++ b/Mailman/database/messagestore.py
@@ -50,31 +50,35 @@ class MessageStore:
message_ids = message.get_all('message-id', [])
if len(message_ids) <> 1:
raise ValueError('Exactly one Message-ID header required')
- # Calculate and insert the X-List-ID-Hash.
+ # Calculate and insert the X-Message-ID-Hash.
message_id = message_ids[0]
+ # Complain if the Message-ID already exists in the storage.
+ existing = config.db.store.find(Message,
+ Message.message_id == message_id).one()
+ if existing is not None:
+ raise ValueError('Message ID already exists in message store: %s',
+ message_id)
shaobj = hashlib.sha1(message_id)
hash32 = base64.b32encode(shaobj.digest())
- del message['X-List-ID-Hash']
- message['X-List-ID-Hash'] = hash32
+ del message['X-Message-ID-Hash']
+ message['X-Message-ID-Hash'] = hash32
# Calculate the path on disk where we're going to store this message
# object, in pickled format.
parts = []
split = list(hash32)
while split and len(parts) < MAX_SPLITS:
parts.append(split.pop(0) + split.pop(0))
- parts.append(EMPTYSTRING.join(split))
+ parts.append(hash32)
relpath = os.path.join(*parts)
# Store the message in the database. This relies on the database
# providing a unique serial number, but to get this information, we
# have to use a straight insert instead of relying on Elixir to create
# the object.
- row = Message(hash=hash32, path=relpath, message_id=message_id)
- # Add the additional header.
- seqno = row.id
- del message['X-List-Sequence-Number']
- message['X-List-Sequence-Number'] = str(seqno)
+ row = Message(message_id=message_id,
+ message_id_hash=hash32,
+ path=relpath)
# Now calculate the full file system path.
- path = os.path.join(config.MESSAGES_DIR, relpath, str(seqno))
+ path = os.path.join(config.MESSAGES_DIR, relpath)
# Write the file to the path, but catch the appropriate exception in
# case the parent directories don't yet exist. In that case, create
# them and try again.
@@ -88,54 +92,41 @@ class MessageStore:
if e.errno <> errno.ENOENT:
raise
os.makedirs(os.path.dirname(path))
- return seqno
+ return hash32
- def _msgobj(self, msgrow):
- path = os.path.join(config.MESSAGES_DIR, msgrow.path, str(msgrow.id))
+ def _get_message(self, row):
+ path = os.path.join(config.MESSAGES_DIR, row.path)
with open(path) as fp:
return pickle.load(fp)
- def get_messages_by_message_id(self, message_id):
- for msgrow in config.db.store.find(Message, message_id=message_id):
- yield self._msgobj(msgrow)
+ def get_message_by_id(self, message_id):
+ row = config.db.store.find(Message, message_id=message_id).one()
+ if row is None:
+ return None
+ return self._get_message(row)
- def get_messages_by_hash(self, hash):
+ def get_message_by_hash(self, message_id_hash):
# It's possible the hash came from a message header, in which case it
- # will be a Unicode. However when coming from source code, it will
- # always be an 8-string. Coerce to the latter if necessary; it must
- # be US-ASCII.
- if isinstance(hash, unicode):
- hash = hash.encode('ascii')
- for msgrow in config.db.store.find(Message, hash=hash):
- yield self._msgobj(msgrow)
-
- def _getmsg(self, global_id):
- try:
- hash, seqno = global_id.split('/', 1)
- seqno = int(seqno)
- except ValueError:
- return None
- messages = config.db.store.find(Message, id=seqno)
- if messages.count() == 0:
+ # will be a Unicode. However when coming from source code, it may be
+ # an 8-string. Coerce to the latter if necessary; it must be
+ # US-ASCII.
+ if isinstance(message_id_hash, unicode):
+ message_id_hash = message_id_hash.encode('ascii')
+ row = config.db.store.find(Message,
+ message_id_hash=message_id_hash).one()
+ if row is None:
return None
- assert messages.count() == 1, 'Multiple id matches'
- if messages[0].hash <> hash:
- # The client lied about which message they wanted. They gave a
- # valid sequence number, but the hash did not match.
- return None
- return messages[0]
-
- def get_message(self, global_id):
- msgrow = self._getmsg(global_id)
- return (self._msgobj(msgrow) if msgrow is not None else None)
+ return self._get_message(row)
@property
def messages(self):
- for msgrow in config.db.store.find(Message):
- yield self._msgobj(msgrow)
+ for row in config.db.store.find(Message):
+ yield self._get_message(row)
- def delete_message(self, global_id):
- msgrow = self._getmsg(global_id)
- if msgrow is None:
- raise KeyError(global_id)
- config.db.store.remove(msgrow)
+ def delete_message(self, message_id):
+ row = config.db.store.find(Message, message_id=message_id).one()
+ if row is None:
+ raise LookupError(message_id)
+ path = os.path.join(config.MESSAGES_DIR, row.path)
+ os.remove(path)
+ config.db.store.remove(row)