* Handles selecting tags, and filtering.
* Needs $tables to be set up properly, so we can figure out which join conditions to use.
*
+ * WARNING: If $filter_tag contains more than one tag, this function will add DISTINCT,
+ * which may cause performance problems for your query unless you put the ID field of your
+ * table at the end of the ORDER BY, and set a GROUP BY equal to the ORDER BY. For example,
+ * if you had ORDER BY foo_timestamp DESC, you will now need GROUP BY foo_timestamp, foo_id
+ * ORDER BY foo_timestamp DESC, foo_id DESC.
+ *
* @param string|array $tables Table names, see Database::select
* @param string|array $fields Fields used in query, see Database::select
* @param string|array $conds Conditions used in query, see Database::select
* @param array $join_conds Join conditions, see Database::select
- * @param array $options Options, see Database::select
- * @param bool|string $filter_tag Tag to select on
+ * @param string|array $options Options, see Database::select
+ * @param string|array $filter_tag Tag(s) to select on
*
* @throws MWException When unable to determine appropriate JOIN condition for tagging
*/
public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
- &$join_conds, &$options, $filter_tag = false ) {
- global $wgRequest, $wgUseTagFilter;
+ &$join_conds, &$options, $filter_tag = '' ) {
+ global $wgUseTagFilter;
- if ( $filter_tag === false ) {
- $filter_tag = $wgRequest->getVal( 'tagfilter' );
- }
+ // Normalize to arrays
+ $tables = (array)$tables;
+ $fields = (array)$fields;
+ $conds = (array)$conds;
+ $options = (array)$options;
- // Figure out which conditions can be done.
+ // Figure out which ID field to use
if ( in_array( 'recentchanges', $tables ) ) {
$join_cond = 'ct_rc_id=rc_id';
} elseif ( in_array( 'logging', $tables ) ) {
$tables[] = 'change_tag';
$join_conds['change_tag'] = [ 'INNER JOIN', $join_cond ];
- $conds['ct_tag'] = explode( '|', $filter_tag );
+ $conds['ct_tag'] = $filter_tag;
+ if (
+ is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
+ !in_array( 'DISTINCT', $options )
+ ) {
+ $options[] = 'DISTINCT';
+ }
}
}
// tags cannot contain commas (used as a delimiter in tag_summary table),
// pipe (used as a delimiter between multiple tags in
- // modifyDisplayQuery), or slashes (would break tag description messages in
+ // SpecialRecentchanges and friends), or slashes (would break tag description messages in
// MediaWiki namespace)
if ( strpos( $tag, ',' ) !== false || strpos( $tag, '|' ) !== false
|| strpos( $tag, '/' ) !== false ) {
--- /dev/null
+<?php
+
+/**
+ * @covers ChangeTags
+ */
+class ChangeTagsTest extends MediaWikiTestCase {
+
+ // TODO only modifyDisplayQuery is tested, nothing else is
+
+ /** @dataProvider provideModifyDisplayQuery */
+ public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
+ $this->setMwGlobals( 'wgUseTagFilter', $useTags );
+ // HACK resolve deferred group concats (see comment in provideModifyDisplayQuery)
+ if ( isset( $modifiedQuery['fields']['ts_tags'] ) ) {
+ $modifiedQuery['fields']['ts_tags'] = call_user_func_array(
+ [ wfGetDB( DB_REPLICA ), 'buildGroupConcatField' ],
+ $modifiedQuery['fields']['ts_tags']
+ );
+ }
+ if ( isset( $modifiedQuery['exception'] ) ) {
+ $this->setExpectedException( $modifiedQuery['exception'] );
+ }
+ ChangeTags::modifyDisplayQuery(
+ $origQuery['tables'],
+ $origQuery['fields'],
+ $origQuery['conds'],
+ $origQuery['join_conds'],
+ $origQuery['options'],
+ $filter_tag
+ );
+ if ( !isset( $modifiedQuery['exception'] ) ) {
+ $this->assertArrayEquals(
+ $modifiedQuery,
+ $origQuery,
+ /* ordered = */ false,
+ /* named = */ true
+ );
+ }
+ }
+
+ public function provideModifyDisplayQuery() {
+ // HACK if we call $dbr->buildGroupConcatField() now, it will return the wrong table names
+ // We have to have the test runner call it instead
+ $groupConcats = [
+ 'recentchanges' => [ ',', 'change_tag', 'ct_tag', 'ct_rc_id=rc_id' ],
+ 'logging' => [ ',', 'change_tag', 'ct_tag', 'ct_log_id=log_id' ],
+ 'revision' => [ ',', 'change_tag', 'ct_tag', 'ct_rev_id=rev_id' ],
+ 'archive' => [ ',', 'change_tag', 'ct_tag', 'ct_rev_id=ar_rev_id' ],
+ ];
+
+ return [
+ 'simple recentchanges query' => [
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp' ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
+ ],
+ '', // no tag filter
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
+ ]
+ ],
+ 'simple query with strings' => [
+ [
+ 'tables' => 'recentchanges',
+ 'fields' => 'rc_id',
+ 'conds' => "rc_timestamp > '20170714183203'",
+ 'join_conds' => [],
+ 'options' => 'ORDER BY rc_timestamp DESC',
+ ],
+ '', // no tag filter
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY rc_timestamp DESC' ],
+ ]
+ ],
+ 'recentchanges query with single tag filter' => [
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp' ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
+ ],
+ 'foo',
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges', 'change_tag' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
+ ]
+ ],
+ 'logging query with single tag filter and strings' => [
+ [
+ 'tables' => 'logging',
+ 'fields' => 'log_id',
+ 'conds' => "log_timestamp > '20170714183203'",
+ 'join_conds' => [],
+ 'options' => 'ORDER BY log_timestamp DESC',
+ ],
+ 'foo',
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'logging', 'change_tag' ],
+ 'fields' => [ 'log_id', 'ts_tags' => $groupConcats['logging'] ],
+ 'conds' => [ "log_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id=log_id' ] ],
+ 'options' => [ 'ORDER BY log_timestamp DESC' ],
+ ]
+ ],
+ 'revision query with single tag filter' => [
+ [
+ 'tables' => [ 'revision' ],
+ 'fields' => [ 'rev_id', 'rev_timestamp' ],
+ 'conds' => [ "rev_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
+ ],
+ 'foo',
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'revision', 'change_tag' ],
+ 'fields' => [ 'rev_id', 'rev_timestamp', 'ts_tags' => $groupConcats['revision'] ],
+ 'conds' => [ "rev_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=rev_id' ] ],
+ 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
+ ]
+ ],
+ 'archive query with single tag filter' => [
+ [
+ 'tables' => [ 'archive' ],
+ 'fields' => [ 'ar_id', 'ar_timestamp' ],
+ 'conds' => [ "ar_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
+ ],
+ 'foo',
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'archive', 'change_tag' ],
+ 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
+ 'conds' => [ "ar_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=ar_rev_id' ] ],
+ 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
+ ]
+ ],
+ 'unsupported table name throws exception (even without tag filter)' => [
+ [
+ 'tables' => [ 'foobar' ],
+ 'fields' => [ 'fb_id', 'fb_timestamp' ],
+ 'conds' => [ "fb_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ],
+ ],
+ '',
+ true, // tag filtering enabled
+ [ 'exception' => MWException::class ]
+ ],
+ 'tag filter ignored when tag filtering is disabled' => [
+ [
+ 'tables' => [ 'archive' ],
+ 'fields' => [ 'ar_id', 'ar_timestamp' ],
+ 'conds' => [ "ar_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
+ ],
+ 'foo',
+ false, // tag filtering disabled
+ [
+ 'tables' => [ 'archive' ],
+ 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
+ 'conds' => [ "ar_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
+ ]
+ ],
+ 'recentchanges query with multiple tag filter' => [
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp' ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
+ ],
+ [ 'foo', 'bar' ],
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges', 'change_tag' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
+ 'options' => [ 'ORDER BY' => 'rc_timestamp DESC', 'DISTINCT' ],
+ ]
+ ],
+ 'recentchanges query with multiple tag filter that already has DISTINCT' => [
+ [
+ 'tables' => [ 'recentchanges' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp' ],
+ 'conds' => [ "rc_timestamp > '20170714183203'" ],
+ 'join_conds' => [],
+ 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
+ ],
+ [ 'foo', 'bar' ],
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges', 'change_tag' ],
+ 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
+ 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
+ ]
+ ],
+ 'recentchanges query with multiple tag filter with strings' => [
+ [
+ 'tables' => 'recentchanges',
+ 'fields' => 'rc_id',
+ 'conds' => "rc_timestamp > '20170714183203'",
+ 'join_conds' => [],
+ 'options' => 'ORDER BY rc_timestamp DESC',
+ ],
+ [ 'foo', 'bar' ],
+ true, // tag filtering enabled
+ [
+ 'tables' => [ 'recentchanges', 'change_tag' ],
+ 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
+ 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
+ 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
+ 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ],
+ ]
+ ],
+ ];
+ }
+
+}