2023-10-09 20:08:03 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Tests\Unit\Libs\Rdbms;
|
|
|
|
|
|
|
|
|
|
use InvalidArgumentException;
|
2024-02-16 22:07:23 +00:00
|
|
|
use PHPUnit\Framework\TestCase;
|
2023-10-09 20:08:03 +00:00
|
|
|
use Wikimedia\Rdbms\AndExpressionGroup;
|
|
|
|
|
use Wikimedia\Rdbms\Expression;
|
2023-12-05 21:12:40 +00:00
|
|
|
use Wikimedia\Rdbms\IExpression;
|
|
|
|
|
use Wikimedia\Rdbms\LikeMatch;
|
|
|
|
|
use Wikimedia\Rdbms\LikeValue;
|
2023-10-09 20:08:03 +00:00
|
|
|
use Wikimedia\Rdbms\OrExpressionGroup;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \Wikimedia\Rdbms\Expression
|
|
|
|
|
* @covers \Wikimedia\Rdbms\ExpressionGroup
|
|
|
|
|
* @covers \Wikimedia\Rdbms\AndExpressionGroup
|
|
|
|
|
* @covers \Wikimedia\Rdbms\OrExpressionGroup
|
2024-03-26 15:36:19 +00:00
|
|
|
* @covers \Wikimedia\Rdbms\LikeMatch
|
|
|
|
|
* @covers \Wikimedia\Rdbms\LikeValue
|
2023-10-09 20:08:03 +00:00
|
|
|
*/
|
2024-02-16 22:07:23 +00:00
|
|
|
class ExpressionTest extends TestCase {
|
2023-10-09 20:08:03 +00:00
|
|
|
|
|
|
|
|
public static function provideSimple() {
|
|
|
|
|
return [
|
2023-12-05 21:12:40 +00:00
|
|
|
[
|
|
|
|
|
'rev_timestamp', '>', '20221025145309',
|
|
|
|
|
"rev_timestamp > '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '>=', '20221025145309',
|
|
|
|
|
"rev_timestamp >= '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '<', '20221025145309',
|
|
|
|
|
"rev_timestamp < '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '<=', '20221025145309',
|
|
|
|
|
"rev_timestamp <= '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '=', '20221025145309',
|
|
|
|
|
"rev_timestamp = '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '!=', '20221025145309',
|
|
|
|
|
"rev_timestamp != '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'revision.rev_timestamp', '!=', '20221025145309',
|
|
|
|
|
"revision.rev_timestamp != '20221025145309'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '>', 12345,
|
|
|
|
|
"rev_id > 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '>=', 12345,
|
|
|
|
|
"rev_id >= 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '<', 12345,
|
|
|
|
|
"rev_id < 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '<=', 12345,
|
|
|
|
|
"rev_id <= 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '=', 12345,
|
|
|
|
|
"rev_id = 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '=', [ 'x' ],
|
|
|
|
|
"rev_id = 'x'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '!=', 12345,
|
|
|
|
|
"rev_id != 12345"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '!=', [ 'x' ],
|
|
|
|
|
"rev_id != 'x'"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '=', [ 12345, 456 ],
|
|
|
|
|
"rev_id IN (12345,456)"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_id', '!=', [ 12345, 456 ],
|
|
|
|
|
"rev_id NOT IN (12345,456)"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rc_old_len', '=', null,
|
|
|
|
|
"rc_old_len IS NULL"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rc_old_len', '!=', null,
|
|
|
|
|
"rc_old_len IS NOT NULL"
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'rev_timestamp', '=', [ '20221025145309', '20230919152602' ],
|
2023-10-09 20:08:03 +00:00
|
|
|
"rev_timestamp IN ('20221025145309','20230919152602')"
|
|
|
|
|
],
|
|
|
|
|
[
|
2023-12-05 21:12:40 +00:00
|
|
|
'rev_timestamp', '!=', [ '20221025145309', '20230919152602' ],
|
2023-10-09 20:08:03 +00:00
|
|
|
"rev_timestamp NOT IN ('20221025145309','20230919152602')"
|
|
|
|
|
],
|
2023-12-05 21:12:40 +00:00
|
|
|
[
|
|
|
|
|
'user_name', IExpression::LIKE, new LikeValue( 'foo', new LikeMatch( '%' ) ),
|
2024-03-06 16:56:22 +00:00
|
|
|
"user_name LIKE 'foo%' ESCAPE '`'"
|
2023-12-05 21:12:40 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'user_name', IExpression::NOT_LIKE, new LikeValue( new LikeMatch( '%' ), 'foo' ),
|
2024-03-06 16:56:22 +00:00
|
|
|
"user_name NOT LIKE '%foo' ESCAPE '`'"
|
2023-12-05 21:12:40 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'user_name', IExpression::LIKE, new LikeValue( 'foo' ),
|
2024-03-06 16:56:22 +00:00
|
|
|
"user_name LIKE 'foo' ESCAPE '`'"
|
2023-12-05 21:12:40 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'user_name', IExpression::LIKE, new LikeValue( new LikeMatch( '_' ), '%_', new LikeMatch( '%' ) ),
|
2024-03-06 16:56:22 +00:00
|
|
|
"user_name LIKE '_`%`_%' ESCAPE '`'"
|
2023-12-05 21:12:40 +00:00
|
|
|
],
|
2023-10-09 20:08:03 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideSimple
|
|
|
|
|
*/
|
|
|
|
|
public function testSimple( $field, $op, $value, $sql ) {
|
|
|
|
|
$expr = new Expression( $field, $op, $value );
|
|
|
|
|
$this->assertSame( $sql, $expr->toSql( new AddQuoterMock() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function provideSimpleInvalid() {
|
|
|
|
|
return [
|
|
|
|
|
[ 'rev_timestamp', 'bigger', '12345' ],
|
|
|
|
|
[ 'rev_timestamp', 'bigger', 1234 ],
|
2024-05-11 23:18:25 +00:00
|
|
|
[ 'rev_timestamp', '=', [] ],
|
|
|
|
|
[ 'rev_timestamp', '=', [ 1234, null, 456 ] ],
|
2023-10-09 20:08:03 +00:00
|
|
|
[ 'rev_timestamp', '>', [ 1234, 456 ] ],
|
|
|
|
|
[ 'rev_timestamp', '<', [ 1234, 456 ] ],
|
|
|
|
|
[ 'rev_timestamp', '>', null ],
|
|
|
|
|
[ 'rev_timestamp', '<', null ],
|
|
|
|
|
[ 'rev_timestamp', '=<', '12345' ],
|
2024-05-11 20:58:12 +00:00
|
|
|
'associative array' => [ 'rev_timestamp', '=', [ 'a' => 1234, 'b' => 456 ] ],
|
|
|
|
|
'associative array (single)' => [ 'rev_timestamp', '=', [ 'a' => 1234 ] ],
|
|
|
|
|
'array with gaps' => [ 'rev_timestamp', '=', array_unique( [ 1234, 1234, 456, 456 ] ) ],
|
2023-12-05 21:12:40 +00:00
|
|
|
[ 'user_name', IExpression::LIKE, 'foo' ],
|
|
|
|
|
[ 'user_name', IExpression::LIKE, [ 'foo' ] ],
|
|
|
|
|
[ 'user_name', '=', new LikeValue( 'foo' ) ],
|
2023-11-20 21:51:03 +00:00
|
|
|
[ 'SELECT user_password from user where user_id = 1', '>', 1234 ],
|
2023-10-09 20:08:03 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @dataProvider provideSimpleInvalid */
|
|
|
|
|
public function testSimpleInvalid( $field, $op, $value ): void {
|
|
|
|
|
$this->expectException( InvalidArgumentException::class );
|
|
|
|
|
$expr = new Expression( $field, $op, $value );
|
|
|
|
|
$expr->toSql( new AddQuoterMock() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testSimpleChain() {
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->and( 'rev_timestamp', '<', '20230919152602' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' AND rev_timestamp < '20230919152602')",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->or( 'rev_timestamp', '<', '20230919152602' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rev_timestamp < '20230919152602')",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->and( 'rev_id', '=', [ 1234, 4567 ] );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' AND rev_id IN (1234,4567))",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->or( 'rev_id', '=', [ 1234, 4567 ] );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rev_id IN (1234,4567))",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->and( 'rc_old_len', '!=', null );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' AND rc_old_len IS NOT NULL)",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->or( 'rc_old_len', '!=', null );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NOT NULL)",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->and( 'rc_old_len', '=', null );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' AND rc_old_len IS NULL)",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->or( 'rc_old_len', '=', null );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL)",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testComplexChain() {
|
|
|
|
|
$expr = ( new Expression( 'rev_timestamp', '>', '20221025145309' ) )
|
|
|
|
|
->or( 'rc_old_len', '=', null )
|
|
|
|
|
->or( 'rc_new_len', '=', null );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL OR rc_new_len IS NULL)",
|
|
|
|
|
$expr->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr2 = ( new Expression( 'rev_id', '=', [ 123, 456 ] ) )
|
|
|
|
|
->andExpr( $expr );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_id IN (123,456) AND " .
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL OR rc_new_len IS NULL))",
|
|
|
|
|
$expr2->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr3 = ( new Expression( 'rev_actor', '>', 3 ) )
|
|
|
|
|
->orExpr( $expr2 );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_actor > 3 OR (rev_id IN (123,456) AND " .
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL OR rc_new_len IS NULL)))",
|
|
|
|
|
$expr3->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr4 = ( new AndExpressionGroup( new Expression( 'rev_id', '=', [ 123, 456 ] ) ) )
|
|
|
|
|
->andExpr( $expr );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_id IN (123,456) AND " .
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL OR rc_new_len IS NULL))",
|
|
|
|
|
$expr4->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$expr5 = ( new OrExpressionGroup( new Expression( 'rev_id', '=', [ 123, 456 ] ) ) )
|
|
|
|
|
->orExpr( $expr2 );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
"(rev_id IN (123,456) OR (rev_id IN (123,456) AND " .
|
|
|
|
|
"(rev_timestamp > '20221025145309' OR rc_old_len IS NULL OR rc_new_len IS NULL)))",
|
|
|
|
|
$expr5->toSql( new AddQuoterMock() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|