From 06d58e6e67660ed38568f5dcc9cbf5502c53d1a9 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Wed, 30 Dec 2015 12:36:05 +0100 Subject: [PATCH] MDL-49231 mod_glossary: Fix SQL queries for Oracle --- mod/glossary/classes/entry_query_builder.php | 85 +------------ mod/glossary/lib.php | 126 +++++++++++++++---- 2 files changed, 104 insertions(+), 107 deletions(-) diff --git a/mod/glossary/classes/entry_query_builder.php b/mod/glossary/classes/entry_query_builder.php index a6bdea89af2..2b74234d522 100644 --- a/mod/glossary/classes/entry_query_builder.php +++ b/mod/glossary/classes/entry_query_builder.php @@ -159,17 +159,6 @@ class mod_glossary_entry_query_builder { return $DB->count_records_sql($this->build_query(true), $this->params); } - /** - * Distinct a field. - * - * @param string $field The field. - * @param string $table The table name, without the prefix 'glossary_'. - */ - public function distinct($field, $table) { - $field = self::resolve_field($field, $table); - array_unshift($this->fields, 'DISTINCT(' . $field . ')'); - } - /** * Filter a field using a letter. * @@ -289,78 +278,6 @@ class mod_glossary_entry_query_builder { $this->params['filtertermb'] = $term; } - /** - * Filter by search terms. - * - * Note that this does not handle invalid or too short terms. This requires the alias - * table to be joined in the query. See {@link self::join_alias()}. - * - * @param array $terms Array of terms. - * @param bool $fullsearch Whether or not full search should be enabled. - */ - public function filter_by_search_terms(array $terms, $fullsearch = true) { - global $DB; - static $i = 0; - - if ($DB->sql_regex_supported()) { - $regexp = $DB->sql_regex(true); - $notregexp = $DB->sql_regex(false); - } - - $params = array(); - $conceptfield = self::resolve_field('concept', 'entries'); - $aliasfield = self::resolve_field('alias', 'alias'); - $definitionfield = self::resolve_field('definition', 'entries'); - $conditions = array(); - - foreach ($terms as $searchterm) { - $i++; - - $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle - // will use it to simulate the "-" operator with LIKE clause. - - if (empty($fullsearch)) { - // With fullsearch disabled, look only within concepts and aliases. - $concat = $DB->sql_concat($conceptfield, "' '", "COALESCE($aliasfield, :emptychar{$i})"); - } else { - // With fullsearch enabled, look also within definitions. - $concat = $DB->sql_concat($conceptfield, "' '", $definitionfield, "' '", "COALESCE($aliasfield, :emptychar{$i})"); - } - $params['emptychar' . $i] = ''; - - // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries. - if (!$DB->sql_regex_supported()) { - if (substr($searchterm, 0, 1) === '-') { - $not = true; - } - $searchterm = trim($searchterm, '+-'); - } - - if (substr($searchterm, 0, 1) === '+') { - $searchterm = trim($searchterm, '+-'); - $conditions[] = "$concat $regexp :searchterm{$i}"; - $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; - - } else if (substr($searchterm, 0, 1) === "-") { - $searchterm = trim($searchterm, '+-'); - $conditions[] = "$concat $notregexp :searchterm{$i}"; - $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; - - } else { - $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not); - $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%'; - } - } - - // When there are no conditions we add a negative one to ensure that we don't return anything. - if (empty($conditions)) { - $conditions[] = '1 = 2'; - } - - $this->where[] = implode(' AND ', $conditions); - $this->params = array_merge($this->params, $params); - } - /** * Convenience method to get get the SQL statement for the full name. * @@ -411,7 +328,7 @@ class mod_glossary_entry_query_builder { * Join the alias table. * * Note that this may cause the same entry to be returned more than once. You might want - * to add a distinct on the entry id. See {@link self::distinct()}. + * to add a distinct on the entry id. * * @return void */ diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index ed57a5e57c4..64ec9551c04 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -3605,7 +3605,7 @@ function glossary_get_authors($glossary, $context, $limit, $from, $options = arr } $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))"; - $sqlselect = "SELECT DISTINCT(u.id), $userfields"; + $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields"; $sql = " FROM {user} u JOIN {glossary_entries} ge ON ge.userid = u.id @@ -3640,6 +3640,77 @@ function glossary_get_categories($glossary, $from, $limit) { return array($categories, $count); } +/** + * Get the SQL where clause for searching terms. + * + * Note that this does not handle invalid or too short terms. + * + * @param array $terms Array of terms. + * @param bool $fullsearch Whether or not full search should be enabled. + * @return array The first element being the where clause, the second array of parameters. + * @since Moodle 3.1 + */ +function glossary_get_search_terms_sql(array $terms, $fullsearch = true) { + global $DB; + static $i = 0; + + if ($DB->sql_regex_supported()) { + $regexp = $DB->sql_regex(true); + $notregexp = $DB->sql_regex(false); + } + + $params = array(); + $conditions = array(); + + foreach ($terms as $searchterm) { + $i++; + + $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle + // will use it to simulate the "-" operator with LIKE clause. + + if (empty($fullsearch)) { + // With fullsearch disabled, look only within concepts and aliases. + $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})"); + } else { + // With fullsearch enabled, look also within definitions. + $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})"); + } + $params['emptychar' . $i] = ''; + + // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries. + if (!$DB->sql_regex_supported()) { + if (substr($searchterm, 0, 1) === '-') { + $not = true; + } + $searchterm = trim($searchterm, '+-'); + } + + if (substr($searchterm, 0, 1) === '+') { + $searchterm = trim($searchterm, '+-'); + $conditions[] = "$concat $regexp :searchterm{$i}"; + $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; + + } else if (substr($searchterm, 0, 1) === "-") { + $searchterm = trim($searchterm, '+-'); + $conditions[] = "$concat $notregexp :searchterm{$i}"; + $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; + + } else { + $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not); + $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%'; + } + } + + // When there are no conditions we add a negative one to ensure that we don't return anything. + if (empty($conditions)) { + $conditions[] = '1 = 2'; + } + + $where = implode(' AND ', $conditions); + return array($where, $params); +} + + /** * Returns the entries of a glossary by search. * @@ -3658,7 +3729,8 @@ function glossary_get_categories($glossary, $from, $limit) { * @since Moodle 3.1 */ function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit, - $options = array()) { + $options = array()) { + global $DB, $USER; // Remove too little terms. $terms = explode(' ', $query); @@ -3668,34 +3740,43 @@ function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch } } - // Build the query. - $qb = new mod_glossary_entry_query_builder($glossary); - if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { - $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); - } else { - $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); - } + list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch); - $qb->add_field('*', 'entries'); - $qb->join_alias(); - $qb->distinct('id', 'entries'); - $qb->join_user(); - $qb->add_user_fields(); - $qb->filter_by_search_terms($terms, $fullsearch); + $userfields = user_picture::fields('u', null, 'userdataid', 'userdata'); + + // Need one inner view here to avoid distinct + text. + $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . ' + FROM {glossary_entries} ge + LEFT JOIN {user} u ON u.id = ge.userid + JOIN ( '; + $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)'; + $sqlselect = "SELECT DISTINCT ge.id"; + $sqlfrom = "FROM {glossary_entries} ge + LEFT JOIN {glossary_alias} al ON al.entryid = ge.id"; + + if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { + $approvedsql = ''; + } else { + $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)'; + $params['myid'] = $USER->id; + } if ($order == 'CREATION') { - $qb->order_by('timecreated', 'entries', $sort); + $sqlorderby = "ORDER BY ge.timecreated $sort"; } else if ($order == 'UPDATE') { - $qb->order_by('timemodified', 'entries', $sort); + $sqlorderby = "ORDER BY ge.timemodified $sort"; } else { - $qb->order_by('concept', 'entries', $sort); + $sqlorderby = "ORDER BY ge.concept $sort"; } - $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. - $qb->limit($from, $limit); + $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value. + + $sqlwhere = "WHERE ($searchcond) $approvedsql"; // Fetching the entries. - $count = $qb->count_records(); - $entries = $qb->get_recordset(); + $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params); + + $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby"; + $entries = $DB->get_recordset_sql($query, $params, $from, $limit); return array($entries, $count); } @@ -3726,7 +3807,6 @@ function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $qb->add_field('*', 'entries'); $qb->join_alias(); - $qb->distinct('id', 'entries'); $qb->join_user(); $qb->add_user_fields(); $qb->filter_by_term($term);