? .cvsignore ? doc_improvement.patch ? query_documentation.patch ? query_documentation2.patch Index: privatemsg.module =================================================================== RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg.module,v retrieving revision 1.70.2.30.2.91.2.93 diff -u -p -r1.70.2.30.2.91.2.93 privatemsg.module --- privatemsg.module 10 Nov 2009 19:48:17 -0000 1.70.2.30.2.91.2.93 +++ privatemsg.module 12 Nov 2009 12:37:52 -0000 @@ -1000,21 +1000,29 @@ function pm_preview($form, &$form_state) function privatemsg_sql_list(&$fragments, $account, $argument = 'list') { $fragments['primary_table'] = '{pm_message} pm'; - // Load enabled columns + // Load enabled columns. $fields = array_filter(variable_get('privatemsg_display_fields', array('participants'))); - // Required columns + // Required columns. $fragments['select'][] = 'pmi.thread_id'; + // We have to use MIN as the subject might not be the same in some threads. + // MIN() does not have a useful meaning except that it helps to correctly + // aggregate the thread on PostgreSQL. $fragments['select'][] = 'MIN(pm.subject) as subject'; $fragments['select'][] = 'MAX(pm.timestamp) as last_updated'; + // We use SUM so that we can count the number of unread messages. $fragments['select'][] = 'SUM(pmi.is_new) as is_new'; + // Select number of messages in the thread if the count is + // set to be displayed. if (in_array('count', $fields)) { - $fragments['select'][] = 'COUNT(distinct pmi.mid) as count'; // We only want the distinct number of messages in this thread. + $fragments['select'][] = 'COUNT(distinct pmi.mid) as count'; } if (in_array('participants', $fields)) { - // Query for a string with uid's, for example "1,6,7". This needs a subquery on PostgreSQL. + // Query for a string with uid's, for example "1,6,7". + // @todo: Replace this with a single query similiar to the tag list. if ($GLOBALS['db_type'] == 'pgsql') { + // PostgreSQL does not know GROUP_CONCAT, so a subquery is required. $fragments['select'][] = "array_to_string(array(SELECT DISTINCT textin(int4out(pmia.uid)) FROM {pm_index} pmia WHERE pmia.thread_id = pmi.thread_id), ',') AS participants"; @@ -1028,8 +1036,10 @@ function privatemsg_sql_list(&$fragments if (in_array('thread_started', $fields)) { $fragments['select'][] = 'MIN(pm.timestamp) as thread_started'; } - // pm_index needs to be the first join. + $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid'; + + // Only load undeleted messages of the current user and group by thread. $fragments['where'][] = 'pmi.uid = %d'; $fragments['query_args']['where'][] = $account->uid; $fragments['where'][] = 'pmi.deleted = 0'; @@ -1053,7 +1063,7 @@ function privatemsg_sql_list(&$fragments * Account for which the messages should be loaded. */ function privatemsg_sql_load(&$fragments, $pmids, $account = NULL) { - $fragments['primary_table'] = '{pm_message} pm'; // Our primary table + $fragments['primary_table'] = '{pm_message} pm'; $fragments['select'][] = "pm.mid"; $fragments['select'][] = "pm.author"; @@ -1065,6 +1075,7 @@ function privatemsg_sql_load(&$fragments $fragments['select'][] = "pmi.thread_id"; $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON pm.mid = pmi.mid'; + // Use IN() to load multiple messages at the same time. $fragments['where'][] = 'pmi.mid IN (' . db_placeholders($pmids) . ')'; $fragments['query_args']['where'] += $pmids; if ($account) { @@ -1088,6 +1099,8 @@ function privatemsg_sql_load(&$fragments function privatemsg_sql_messages(&$fragments, $threads, $account = NULL, $load_all = FALSE) { $fragments['primary_table'] = '{pm_index} pmi'; + // Use DISTINCT to only load each mid once even if the user is listed more + // than once as recipient. $fragments['select'][] = 'DISTINCT(pmi.mid) as mid'; $fragments['where'][] = 'pmi.thread_id IN ('. db_placeholders($threads) .')'; $fragments['query_args']['where'] += $threads; @@ -1097,6 +1110,7 @@ function privatemsg_sql_messages(&$fragm $fragments['query_args']['where'][] = $account->uid; } if (!$load_all) { + // Also load deleted messages when requested. $fragments['where'][] = 'pmi.deleted = 0'; } $fragments['order_by'][] = 'pmi.mid ASC'; @@ -1113,6 +1127,8 @@ function privatemsg_sql_messages(&$fragm function privatemsg_sql_participants(&$fragments, $thread_id) { $fragments['primary_table'] = '{pm_index} pmi'; + // Only load each participant once since they are listed as recipient for + // every message of that thread. $fragments['select'][] = 'DISTINCT(pmi.uid) AS uid'; $fragments['select'][] = 'u.name AS name'; @@ -1121,26 +1137,48 @@ function privatemsg_sql_participants(&$f $fragments['query_args']['where'][] = $thread_id; } - +/** + * Query definition to count unread messages. + * + * @param $fragments + * Query fragments array. + * @param $account + * User object for which the messages are being counted. + */ function privatemsg_sql_unread_count(&$fragments, $account) { $fragments['primary_table'] = '{pm_index} pmi'; $fragments['select'][] = 'COUNT(DISTINCT thread_id) as unread_count'; + + // Only count new messages that have not been deleted. $fragments['where'][] = 'pmi.deleted = 0'; $fragments['where'][] = 'pmi.is_new = 1'; $fragments['where'][] = 'pmi.uid = %d'; $fragments['query_args']['where'][] = $account->uid; } +/** + * Query definition to search for username autocomplete suggestions. + * + * @param $fragments + * Query fragments array. + * @param $search + * Which search string is currently searched for. + * @param $names + * Array of names not to be used as suggestions. + */ function privatemsg_sql_autocomplete(&$fragments, $search, $names) { $fragments['primary_table'] = '{users} u'; $fragments['select'][] = 'u.name'; + // Escape the % to get it through the placeholder replacement. $fragments['where'][] = "u.name LIKE '%s'"; $fragments['query_args']['where'][] = $search .'%%'; if (!empty($names)) { + // If there are already names selected, exclude them from the suggestions. $fragments['where'][] = "u.name NOT IN (". db_placeholders($names, 'text') .")"; $fragments['query_args']['where'] += $names; } + // Only load active users and sort them by name. $fragments['where'][] = 'u.status <> 0'; $fragments['order_by'][] = 'u.name ASC'; } @@ -1157,14 +1195,19 @@ function privatemsg_sql_deleted(&$fragme $fragments['primary_table'] = '{pm_message} pm'; $fragments['select'][] = 'pm.mid'; + // The lowest value is higher than 0 if all recipients have deleted a message. $fragments['select'][] = 'MIN(pmi.deleted) as is_deleted'; + // The time the most recent deletion happened. $fragments['select'][] = 'MAX(pmi.deleted) as last_deleted'; $fragments['inner_join'][] = 'INNER JOIN {pm_index} pmi ON (pmi.mid = pm.mid)'; $fragments['group_by'][] = 'pm.mid'; + // Ignore messages that have not been deleted by all users. $fragments['having'][] = 'MIN(pmi.deleted) > 0'; + + // Only select messages that have been deleted more than n days ago. $fragments['having'][] = 'MAX(pmi.deleted) < %d'; $fragments['query_args']['having'][] = time() - $days * 86400; } Index: privatemsg_filter/privatemsg_filter.module =================================================================== RCS file: /cvs/drupal/contributions/modules/privatemsg/privatemsg_filter/privatemsg_filter.module,v retrieving revision 1.1.2.28 diff -u -p -r1.1.2.28 privatemsg_filter.module --- privatemsg_filter/privatemsg_filter.module 10 Nov 2009 19:48:17 -0000 1.1.2.28 +++ privatemsg_filter/privatemsg_filter.module 12 Nov 2009 12:37:52 -0000 @@ -764,6 +764,11 @@ function privatemsg_filter_tags_autocomp } /** + * @addtogroup sql + * @{ + */ + +/** * Limit the user autocomplete for the filter widget. * * @param $fragments @@ -775,8 +780,15 @@ function privatemsg_filter_tags_autocomp */ function privatemsg_filter_privatemsg_sql_autocomplete_alter(&$fragments, $search, $names) { global $user; + // arg(1) is an additional URL argument passed to the URL when only + // users that are listed as recipient for threads of that user should be + // displayed. + // @todo: Check if these results can be grouped to avoid unecessary loops. if (arg(1) == 'filter') { + // JOIN on index entries where the to be selected user is a recipient. $fragments['inner_join'][] = 'INNER JOIN {pm_index} pip ON pip.uid = u.uid'; + // JOIN on rows where the current user is the recipient and that have the + // same mid as those above. $fragments['inner_join'][] = 'INNER JOIN {pm_index} piu ON piu.uid = %d AND pip.mid = piu.mid'; $fragments['query_args']['join'][] = $user->uid; } @@ -801,13 +813,16 @@ function privatemsg_filter_sql_tags(&$fr $fragments['select'][] = 't.public'; if (!empty($threads)) { + // If the tag list needs to be for specific threads. $fragments['select'][] = 'ti.thread_id'; $fragments['inner_join'][] = 'INNER JOIN {pm_tags_index} ti on ti.tag_id = t.tag_id'; $fragments['where'][] = 'ti.thread_id IN (' . db_placeholders($threads) . ')'; $fragments['query_args']['where'] += $threads; } else { + // Tag usage counter is only used when we select all tags. $fragments['select'][] = 'COUNT(ti.thread_id) as count'; + // LEFT JOIN so that unused tags are displayed too. $fragments['inner_join'][] = 'LEFT JOIN {pm_tags_index} ti ON t.tag_id = ti.tag_id'; $fragments['group_by'][] = 't.tag_id'; $fragments['group_by'][] = 't.tag'; @@ -818,7 +833,6 @@ function privatemsg_filter_sql_tags(&$fr $fragments['query_args']['where'][] = $user->uid; } - // Only select n tags per thread (ordered per tag_id), see // http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/. // @@ -835,7 +849,9 @@ function privatemsg_filter_sql_tags(&$fr AND pmtic.tag_id < ti.tag_id) < %d'; $fragments['query_args']['where'][] = $limit; } - elseif (!empty($thread_id) || !empty($user)) { + elseif (!empty($threads) || !empty($user)) { + // Only add a sort when we are not loading the tags for the admin page. + // Sorting is handled through tablesort_sql() then. $fragments['order_by'][] = 't.tag ASC'; } } @@ -856,18 +872,27 @@ function privatemsg_filter_sql_tags_auto $fragments['primary_table'] = '{pm_tags} pmt'; $fragments['select'][] = 'pmt.tag'; $fragments['where'][] = "pmt.tag LIKE '%s'"; + // Escape % to get through the placeholder replacement. $fragments['query_args']['where'][] = $search .'%%'; if (!empty($tags)) { + // Exclude tags. $fragments['where'][] = "pmt.tag NOT IN (". db_placeholders($tags, 'text') .")"; $fragments['query_args']['where'] += $tags; } + // LEFT JOIN to be able to load public, unused tags. $fragments['inner_join'][] = 'LEFT JOIN {pm_tags_index} pmti ON pmt.tag_id = pmti.tag_id AND pmti.uid = %d'; $fragments['query_args']['join'][] = $user->uid; + // Autocomplete should only display Tags used by that user or public tags. + // This is done to avoid information disclosure as part of tag names. $fragments['where'][] = '(pmti.uid IS NOT NULL OR pmt.public = 1)'; $fragments['order_by'][] = 'pmt.tag ASC'; } /** + * @} + */ + +/** * Implement hook_user(). */ function privatemsg_filter_user($op, &$edit, &$account, $category = NULL) {