2009-11-22 06:16:38 +01:00
< ? php
2016-10-14 03:30:05 +02:00
namespace SilverStripe\ORM\Tests ;
2016-06-15 06:03:16 +02:00
use SilverStripe\ORM\DataQuery ;
use SilverStripe\ORM\DataObject ;
use SilverStripe\ORM\DB ;
2016-08-19 00:51:35 +02:00
use SilverStripe\Dev\SapphireTest ;
2023-10-02 04:25:14 +02:00
use SilverStripe\ORM\ArrayList ;
use SilverStripe\ORM\Queries\SQLSelect ;
2019-04-26 04:13:43 +02:00
use SilverStripe\ORM\Tests\DataQueryTest\ObjectE ;
2016-10-14 03:30:05 +02:00
use SilverStripe\Security\Member ;
2016-06-15 06:03:16 +02:00
2016-12-16 05:34:21 +01:00
class DataQueryTest extends SapphireTest
{
protected static $fixture_file = 'DataQueryTest.yml' ;
2020-04-20 19:58:09 +02:00
protected static $extra_dataobjects = [
2023-09-05 05:06:53 +02:00
DataQueryTest\DataObjectAddsToQuery :: class ,
2023-10-02 04:25:14 +02:00
DataQueryTest\DateAndPriceObject :: class ,
2016-12-16 05:34:21 +01:00
DataQueryTest\ObjectA :: class ,
DataQueryTest\ObjectB :: class ,
DataQueryTest\ObjectC :: class ,
DataQueryTest\ObjectD :: class ,
DataQueryTest\ObjectE :: class ,
DataQueryTest\ObjectF :: class ,
DataQueryTest\ObjectG :: class ,
2019-07-29 15:45:10 +02:00
DataQueryTest\ObjectH :: class ,
DataQueryTest\ObjectI :: class ,
2023-10-02 04:25:14 +02:00
SQLSelectTest\CteRecursiveObject :: class ,
2016-12-16 05:34:21 +01:00
SQLSelectTest\TestObject :: class ,
SQLSelectTest\TestBase :: class ,
SQLSelectTest\TestChild :: class ,
2020-04-20 19:58:09 +02:00
];
2016-12-16 05:34:21 +01:00
public function testSortByJoinedFieldRetainsSourceInformation ()
{
$bar = new DataQueryTest\ObjectC ();
$bar -> Title = " Bar " ;
$bar -> write ();
$foo = new DataQueryTest\ObjectB ();
$foo -> Title = " Foo " ;
$foo -> TestC = $bar -> ID ;
$foo -> write ();
$query = new DataQuery ( DataQueryTest\ObjectB :: class );
$result = $query -> leftJoin (
'DataQueryTest_C' ,
" \" DataQueryTest_B \" . \" TestCID \" = \" DataQueryTest_B \" . \" ID \" "
) -> sort ( '"DataQueryTest_B"."Title"' , 'ASC' );
$result = $result -> execute () -> record ();
$this -> assertEquals ( 'Foo' , $result [ 'Title' ]);
}
2023-09-25 01:32:26 +02:00
public function provideJoins ()
{
return [
[
'joinMethod' => 'innerJoin' ,
'joinType' => 'INNER' ,
],
[
'joinMethod' => 'leftJoin' ,
'joinType' => 'LEFT' ,
],
[
'joinMethod' => 'rightJoin' ,
'joinType' => 'RIGHT' ,
],
];
}
2016-12-16 05:34:21 +01:00
/**
2023-09-25 01:32:26 +02:00
* @ dataProvider provideJoins
2016-12-16 05:34:21 +01:00
*/
2023-09-25 01:32:26 +02:00
public function testJoins ( $joinMethod , $joinType )
2016-12-16 05:34:21 +01:00
{
$dq = new DataQuery ( Member :: class );
2023-09-25 01:32:26 +02:00
$dq -> $joinMethod ( " Group_Members " , " \" Group_Members \" . \" MemberID \" = \" Member \" . \" ID \" " );
2016-12-16 05:34:21 +01:00
$this -> assertSQLContains (
2023-09-25 01:32:26 +02:00
" $joinType JOIN \" Group_Members \" ON \" Group_Members \" . \" MemberID \" = \" Member \" . \" ID \" " ,
2016-12-16 05:34:21 +01:00
$dq -> sql ( $parameters )
);
}
public function testApplyRelation ()
{
// Test applyRelation with two has_ones pointing to the same class
$dq = new DataQuery ( DataQueryTest\ObjectB :: class );
$dq -> applyRelation ( 'TestC' );
2017-05-19 04:07:45 +02:00
$this -> assertTrue ( $dq -> query () -> isJoinedTo ( 'testc_DataQueryTest_C' ));
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( '"testc_DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCID"' , $dq -> sql ());
2016-12-16 05:34:21 +01:00
$dq = new DataQuery ( DataQueryTest\ObjectB :: class );
$dq -> applyRelation ( 'TestCTwo' );
2017-05-19 04:07:45 +02:00
$this -> assertTrue ( $dq -> query () -> isJoinedTo ( 'testctwo_DataQueryTest_C' ));
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( '"testctwo_DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCTwoID"' , $dq -> sql ());
2016-12-16 05:34:21 +01:00
}
2017-11-25 17:56:50 +01:00
public function testApplyRelationDeepInheritance ()
2016-12-16 05:34:21 +01:00
{
//test has_one relation
$newDQ = new DataQuery ( DataQueryTest\ObjectE :: class );
//apply a relation to a relation from an ancestor class
$newDQ -> applyRelation ( 'TestA' );
$this -> assertTrue ( $newDQ -> query () -> isJoinedTo ( 'DataQueryTest_C' ));
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( '"testa_DataQueryTest_A"."ID" = "DataQueryTest_C"."TestAID"' , $newDQ -> sql ( $params ));
2016-12-16 05:34:21 +01:00
//test many_many relation
//test many_many with separate inheritance
$newDQ = new DataQuery ( DataQueryTest\ObjectC :: class );
$baseDBTable = DataObject :: getSchema () -> baseDataTable ( DataQueryTest\ObjectC :: class );
$newDQ -> applyRelation ( 'ManyTestAs' );
//check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
$this -> assertTrue ( $newDQ -> query () -> isJoinedTo ( $baseDBTable ));
//check we are explicitly selecting "FROM" the DO's table
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( " FROM \" $baseDBTable\ " " , $newDQ->sql ());
2016-12-16 05:34:21 +01:00
//test many_many with shared inheritance
$newDQ = new DataQuery ( DataQueryTest\ObjectE :: class );
$baseDBTable = DataObject :: getSchema () -> baseDataTable ( DataQueryTest\ObjectE :: class );
//check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
$this -> assertTrue ( $newDQ -> query () -> isJoinedTo ( $baseDBTable ));
//check we are explicitly selecting "FROM" the DO's table
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( " FROM \" $baseDBTable\ " " , $newDQ->sql (), 'The FROM clause is missing from the query');
2016-12-16 05:34:21 +01:00
$newDQ -> applyRelation ( 'ManyTestGs' );
//confirm we are still joined to the base table
$this -> assertTrue ( $newDQ -> query () -> isJoinedTo ( $baseDBTable ));
//double check it is the "FROM" clause
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( " FROM \" $baseDBTable\ " " , $newDQ->sql (), 'The FROM clause has been removed from the query');
2016-12-16 05:34:21 +01:00
//another (potentially less crude check) for checking "FROM" clause
$fromTables = $newDQ -> query () -> getFrom ();
$this -> assertEquals ( '"' . $baseDBTable . '"' , $fromTables [ $baseDBTable ]);
}
public function testRelationReturn ()
{
$dq = new DataQuery ( DataQueryTest\ObjectC :: class );
$this -> assertEquals (
DataQueryTest\ObjectA :: class ,
$dq -> applyRelation ( 'TestA' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$this -> assertEquals (
DataQueryTest\ObjectA :: class ,
$dq -> applyRelation ( 'TestAs' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$this -> assertEquals (
DataQueryTest\ObjectA :: class ,
$dq -> applyRelation ( 'ManyTestAs' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$this -> assertEquals (
DataQueryTest\ObjectB :: class ,
$dq -> applyRelation ( 'TestB' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$this -> assertEquals (
DataQueryTest\ObjectB :: class ,
$dq -> applyRelation ( 'TestBs' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$this -> assertEquals (
DataQueryTest\ObjectB :: class ,
$dq -> applyRelation ( 'ManyTestBs' ),
'DataQuery::applyRelation should return the name of the related object.'
);
$newDQ = new DataQuery ( DataQueryTest\ObjectE :: class );
$this -> assertEquals (
DataQueryTest\ObjectA :: class ,
$newDQ -> applyRelation ( 'TestA' ),
'DataQuery::applyRelation should return the name of the related object.'
);
}
public function testRelationOrderWithCustomJoin ()
{
$dataQuery = new DataQuery ( DataQueryTest\ObjectB :: class );
$dataQuery -> innerJoin ( 'DataQueryTest_D' , '"DataQueryTest_D"."RelationID" = "DataQueryTest_B"."ID"' );
$dataQuery -> execute ();
2021-10-27 04:39:47 +02:00
$this -> assertTrue ( true );
2016-12-16 05:34:21 +01:00
}
2023-09-25 01:32:40 +02:00
public function provideFieldCollision ()
{
return [
'allow collisions' => [ true ],
'disallow collisions' => [ false ],
];
}
/**
* @ dataProvider provideFieldCollision
*/
public function testFieldCollision ( $allowCollisions )
{
$dataQuery = new DataQuery ( DataQueryTest\ObjectB :: class );
$dataQuery -> selectField ( 'COALESCE(NULL, 1) AS "Title"' );
$dataQuery -> setAllowCollidingFieldStatements ( $allowCollisions );
if ( $allowCollisions ) {
$this -> assertSQLContains ( 'THEN "DataQueryTest_B"."Title" WHEN COALESCE(NULL, 1) AS "Title" IS NOT NULL THEN COALESCE(NULL, 1) AS "Title" ELSE NULL END AS "Title"' , $dataQuery -> sql ());
} else {
$this -> expectError ();
$this -> expectErrorMessageMatches ( '/^Bad collision item /' );
}
$dataQuery -> getFinalisedQuery ();
}
2016-12-16 05:34:21 +01:00
public function testDisjunctiveGroup ()
{
$dq = new DataQuery ( DataQueryTest\ObjectA :: class );
$dq -> where ( 'DataQueryTest_A.ID = 2' );
$subDq = $dq -> disjunctiveGroup ();
$subDq -> where ( 'DataQueryTest_A.Name = \'John\'' );
$subDq -> where ( 'DataQueryTest_A.Name = \'Bob\'' );
$this -> assertSQLContains (
" WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') OR (DataQueryTest_A.Name = 'Bob')) " ,
$dq -> sql ( $parameters )
);
}
public function testConjunctiveGroup ()
{
$dq = new DataQuery ( DataQueryTest\ObjectA :: class );
$dq -> where ( 'DataQueryTest_A.ID = 2' );
$subDq = $dq -> conjunctiveGroup ();
$subDq -> where ( 'DataQueryTest_A.Name = \'John\'' );
$subDq -> where ( 'DataQueryTest_A.Name = \'Bob\'' );
$this -> assertSQLContains (
" WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') AND (DataQueryTest_A.Name = 'Bob')) " ,
$dq -> sql ( $parameters )
);
}
/**
* @ todo Test paramaterised
*/
public function testNestedGroups ()
{
$dq = new DataQuery ( DataQueryTest\ObjectA :: class );
$dq -> where ( 'DataQueryTest_A.ID = 2' );
$subDq = $dq -> disjunctiveGroup ();
$subDq -> where ( 'DataQueryTest_A.Name = \'John\'' );
$subSubDq = $subDq -> conjunctiveGroup ();
$subSubDq -> where ( 'DataQueryTest_A.Age = 18' );
$subSubDq -> where ( 'DataQueryTest_A.Age = 50' );
$subDq -> where ( 'DataQueryTest_A.Name = \'Bob\'' );
$this -> assertSQLContains (
" WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') OR ((DataQueryTest_A.Age = 18) "
. " AND (DataQueryTest_A.Age = 50)) OR (DataQueryTest_A.Name = 'Bob')) " ,
$dq -> sql ( $parameters )
);
}
public function testEmptySubgroup ()
{
$dq = new DataQuery ( DataQueryTest\ObjectA :: class );
$dq -> conjunctiveGroup ();
// Empty groups should have no where condition at all
$this -> assertSQLNotContains ( 'WHERE' , $dq -> sql ( $parameters ));
}
public function testSubgroupHandoff ()
{
$dq = new DataQuery ( DataQueryTest\ObjectA :: class );
$subDq = $dq -> disjunctiveGroup ();
$orgDq = clone $dq ;
$subDq -> sort ( '"DataQueryTest_A"."Name"' );
$orgDq -> sort ( '"DataQueryTest_A"."Name"' );
$this -> assertSQLEquals ( $dq -> sql ( $parameters ), $orgDq -> sql ( $parameters ));
$subDq -> limit ( 5 , 7 );
$orgDq -> limit ( 5 , 7 );
$this -> assertSQLEquals ( $dq -> sql ( $parameters ), $orgDq -> sql ( $parameters ));
}
public function testOrderByMultiple ()
{
$dq = new DataQuery ( SQLSelectTest\TestObject :: class );
$dq = $dq -> sort ( '"Name" ASC, MID("Name", 8, 1) DESC' );
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString (
2016-12-16 05:34:21 +01:00
'ORDER BY "SQLSelectTest_DO"."Name" ASC, "_SortColumn0" DESC' ,
$dq -> sql ( $parameters )
);
}
public function testDefaultSort ()
{
$query = new DataQuery ( DataQueryTest\ObjectE :: class );
$result = $query -> column ( 'Title' );
2020-04-20 19:58:09 +02:00
$this -> assertEquals ([ 'First' , 'Second' , 'Last' ], $result );
2016-12-16 05:34:21 +01:00
}
public function testDistinct ()
{
$query = new DataQuery ( DataQueryTest\ObjectE :: class );
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( 'SELECT DISTINCT' , $query -> sql ( $params ), 'Query is set as distinct by default' );
2016-12-16 05:34:21 +01:00
$query = $query -> distinct ( false );
2021-10-27 04:39:47 +02:00
$this -> assertStringNotContainsString ( 'SELECT DISTINCT' , $query -> sql ( $params ), 'Query does not contain distinct' );
2016-12-16 05:34:21 +01:00
$query = $query -> distinct ( true );
2021-10-27 04:39:47 +02:00
$this -> assertStringContainsString ( 'SELECT DISTINCT' , $query -> sql ( $params ), 'Query contains distinct' );
2016-12-16 05:34:21 +01:00
}
public function testComparisonClauseInt ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" SortOrder \" ) VALUES (2) " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"SortOrder"' , '2' ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find SortOrder " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
public function testComparisonClauseDateFull ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" MyDate \" ) VALUES ('1988-03-04 06:30') " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"MyDate"' , '1988-03-04%' ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find MyDate " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
2019-05-15 01:42:10 +02:00
public function testSurrogateFieldSort ()
{
$query = new DataQuery ( DataQueryTest\ObjectE :: class );
$query -> sort (
sprintf (
'(case when "Title" = %s then 1 else 0 end)' ,
DB :: get_conn () -> quoteString ( 'Second' )
),
'DESC' ,
true
);
$query -> sort ( 'SortOrder' , 'ASC' , false );
$query -> sort (
sprintf (
'(case when "Title" = %s then 0 else 1 end)' ,
DB :: get_conn () -> quoteString ( 'Fourth' )
),
'DESC' ,
false
);
$this -> assertEquals (
$query -> execute () -> column ( 'Title' ),
$query -> column ( 'Title' )
);
}
2021-12-10 03:45:38 +01:00
public function testCustomFieldWithAliasSort ()
{
$query = new DataQuery ( DataQueryTest\ObjectE :: class );
$query -> selectField ( sprintf (
'(case when "Title" = %s then 1 else 0 end)' ,
DB :: get_conn () -> quoteString ( 'Second' )
), 'CustomColumn' );
$query -> sort ( 'CustomColumn' , 'DESC' , true );
$query -> sort ( 'SortOrder' , 'ASC' , false );
$this -> assertEquals (
[ 'Second' , 'First' , 'Last' ],
$query -> column ( 'Title' )
);
}
2016-12-16 05:34:21 +01:00
public function testComparisonClauseDateStartsWith ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" MyDate \" ) VALUES ('1988-03-04 06:30') " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"MyDate"' , '1988%' ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find MyDate " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
public function testComparisonClauseDateStartsPartial ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" MyDate \" ) VALUES ('1988-03-04 06:30') " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"MyDate"' , '%03-04%' ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find MyDate " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
public function testComparisonClauseTextCaseInsensitive ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" MyString \" ) VALUES ('HelloWorld') " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"MyString"' , 'helloworld' ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find MyString " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
public function testComparisonClauseTextCaseSensitive ()
{
DB :: query ( " INSERT INTO \" DataQueryTest_F \" ( \" MyString \" ) VALUES ('HelloWorld') " );
$query = new DataQuery ( DataQueryTest\ObjectF :: class );
$query -> where ( DB :: get_conn () -> comparisonClause ( '"MyString"' , 'HelloWorld' , false , false , true ));
$this -> assertGreaterThan ( 0 , $query -> count (), " Couldn't find MyString " );
$query2 = new DataQuery ( DataQueryTest\ObjectF :: class );
$query2 -> where ( DB :: get_conn () -> comparisonClause ( '"MyString"' , 'helloworld' , false , false , true ));
$this -> assertEquals ( 0 , $query2 -> count (), " Found mystring. Shouldn't be able too. " );
2017-03-24 12:17:26 +01:00
static :: resetDBSchema ( true );
2016-12-16 05:34:21 +01:00
}
2023-09-05 05:06:53 +02:00
public function testAddToQueryIsCalled ()
{
// Including filter on parent table only doesn't pull in second
$query = new DataQuery ( DataQueryTest\DataObjectAddsToQuery :: class );
$result = $query -> getFinalisedQuery ();
2023-09-14 01:01:36 +02:00
// The `DBFieldAddsToQuery` test field adds a new field to the select query
$this -> assertArrayHasKey ( 'FieldTwo2' , $result -> getSelect ());
$this -> assertSame ( '"DataQueryTest_AddsToQuery"."FieldTwo"' , $result -> getSelect ()[ 'FieldTwo2' ]);
2023-09-05 05:06:53 +02:00
}
2016-12-16 05:34:21 +01:00
/**
* Tests that getFinalisedQuery can include all tables
*/
public function testConditionsIncludeTables ()
{
// Including filter on parent table only doesn't pull in second
$query = new DataQuery ( DataQueryTest\ObjectC :: class );
$query -> sort ( '"SortOrder"' );
$query -> where (
2020-04-20 19:58:09 +02:00
[
'"DataQueryTest_C"."Title" = ?' => [ 'First' ]
]
2016-12-16 05:34:21 +01:00
);
2020-04-20 19:58:09 +02:00
$result = $query -> getFinalisedQuery ([ 'Title' ]);
2016-12-16 05:34:21 +01:00
$from = $result -> getFrom ();
2022-04-14 03:12:59 +02:00
$this -> assertContains ( 'DataQueryTest_C' , array_keys ( $from ? ? []));
$this -> assertNotContains ( 'DataQueryTest_E' , array_keys ( $from ? ? []));
2016-12-16 05:34:21 +01:00
// Including filter on sub-table requires it
$query = new DataQuery ( DataQueryTest\ObjectC :: class );
$query -> sort ( '"SortOrder"' );
$query -> where (
2019-04-26 04:13:43 +02:00
[ '"DataQueryTest_C"."Title" = ? OR "DataQueryTest_E"."SortOrder" > ?' => [ 'First' , 2 ]]
2016-12-16 05:34:21 +01:00
);
2020-04-20 19:58:09 +02:00
$result = $query -> getFinalisedQuery ([ 'Title' ]);
2016-12-16 05:34:21 +01:00
$from = $result -> getFrom ();
// Check that including "SortOrder" prompted inclusion of DataQueryTest_E table
2022-04-14 03:12:59 +02:00
$this -> assertContains ( 'DataQueryTest_C' , array_keys ( $from ? ? []));
$this -> assertContains ( 'DataQueryTest_E' , array_keys ( $from ? ? []));
2016-12-16 05:34:21 +01:00
$arrayResult = iterator_to_array ( $result -> execute ());
$first = array_shift ( $arrayResult );
$this -> assertNotNull ( $first );
$this -> assertEquals ( 'First' , $first [ 'Title' ]);
$second = array_shift ( $arrayResult );
$this -> assertNotNull ( $second );
$this -> assertEquals ( 'Last' , $second [ 'Title' ]);
$this -> assertEmpty ( array_shift ( $arrayResult ));
}
2018-07-05 11:26:31 +02:00
public function testColumnReturnsAllValues ()
{
$first = new DataQueryTest\ObjectA ();
$first -> Name = 'Bar' ;
$first -> write ();
$second = new DataQueryTest\ObjectA ();
$second -> Name = 'Foo' ;
$second -> write ();
$third = new DataQueryTest\ObjectA ();
$third -> Name = 'Bar' ;
$third -> write ();
$result = DataQueryTest\ObjectA :: get () -> column ( 'Name' );
$this -> assertEquals ([ 'Bar' , 'Foo' , 'Bar' ], $result );
}
2018-07-12 03:44:32 +02:00
public function testColumnUniqueReturnsAllValues ()
{
$first = new DataQueryTest\ObjectA ();
$first -> Name = 'Bar' ;
$first -> write ();
$second = new DataQueryTest\ObjectA ();
$second -> Name = 'Foo' ;
$second -> write ();
$third = new DataQueryTest\ObjectA ();
$third -> Name = 'Bar' ;
$third -> write ();
$result = DataQueryTest\ObjectA :: get () -> columnUnique ( 'Name' );
$this -> assertCount ( 2 , $result );
$this -> assertContains ( 'Bar' , $result );
$this -> assertContains ( 'Foo' , $result );
}
2019-08-13 23:30:41 +02:00
2019-07-29 15:45:10 +02:00
/**
* Tests that sorting against multiple relationships is working
*/
public function testMultipleRelationSort ()
{
$query = new DataQuery ( DataQueryTest\ObjectH :: class );
$query -> applyRelation ( 'ManyTestEs' );
$query -> applyRelation ( 'ManyTestIs' );
$query -> sort ([
'"manytestes_DataQueryTest_E"."SortOrder"' ,
'"manytestis_DataQueryTest_I"."SortOrder"' ,
'"SortOrder"' ,
]);
$titles = $query -> column ( 'Name' );
$this -> assertEquals ( 'First' , $titles [ 0 ]);
$this -> assertEquals ( 'Second' , $titles [ 1 ]);
$this -> assertEquals ( 'Last' , $titles [ 2 ]);
}
2019-04-26 04:13:43 +02:00
public function testExistsCreatesFunctionalQueries ()
{
2021-05-31 06:50:58 +02:00
$this -> assertTrue (
ObjectE :: get () -> exists (),
'Query for ObjectE exists because there\'s more than 1 record'
);
$this -> assertFalse (
ObjectE :: get () -> where ([ '"Title" = ?' => 'Foo' ]) -> exists (),
'Query for ObjectE with Title Foo does NOT exists because there\'s no matching record'
);
$this -> assertTrue (
ObjectE :: get () -> dataQuery () -> groupby ( '"SortOrder"' ) -> exists (),
'Existence of query for ObjectE is not affected by group by'
);
$this -> assertTrue (
ObjectE :: get () -> limit ( 1 ) -> exists (),
'Existence of query for ObjectE is not affected by limit if records are returned'
);
$this -> assertFalse (
ObjectE :: get () -> limit ( 4 , 9999 ) -> exists (),
'Existence of query for ObjectE is affected by limit if no records are returned'
);
$query = new DataQuery ( ObjectE :: class );
$this -> assertTrue (
$query -> exists (),
'exist returns true if query return results'
);
$query = new DataQuery ( ObjectE :: class );
$this -> assertFalse (
$query -> where ([ '"Title" = ?' => 'Foo' ]) -> exists (),
'exist returns false if there\'s no results'
);
$query = new DataQuery ( ObjectE :: class );
$this -> assertTrue (
$query -> groupby ( '"SortOrder"' ) -> exists (),
'exist is unaffected by group by'
);
$query = new DataQuery ( ObjectE :: class );
$this -> assertTrue (
$query -> limit ( 1 ) -> exists (),
'exist is unaffected by limit as long as one recard is returned'
);
$this -> assertFalse (
$query -> limit ( 1 , 9999 ) -> exists (),
'exist is false when a limit returns no results'
);
2019-04-26 04:13:43 +02:00
}
2023-10-02 04:25:14 +02:00
public function provideWith ()
{
return [
// Simple scenarios to test auto-join functionality
'naive CTE query with array join' => [
'dataClass' => DataQueryTest\DateAndPriceObject :: class ,
'name' => 'cte' ,
'query' => new SQLSelect (
[ '"DataQueryTest_DateAndPriceObject"."ID"' ],
'"DataQueryTest_DateAndPriceObject"' ,
[ '"DataQueryTest_DateAndPriceObject"."Price" > 200' ]
),
'cteFields' => [ 'cte_id' ],
'recursive' => false ,
'extraManipulations' => [
'innerJoin' => [ 'cte' , '"DataQueryTest_DateAndPriceObject"."ID" = "cte"."cte_id"' ],
],
'expectedItems' => [
'fixtures' => [
'obj4' ,
'obj5' ,
],
],
],
'naive CTE query with string join' => [
'dataClass' => DataQueryTest\DateAndPriceObject :: class ,
'name' => 'cte' ,
'query' => new SQLSelect ( '200' ),
'cteFields' => [ 'value' ],
'recursive' => false ,
'extraManipulations' => [
'innerJoin' => [ 'cte' , '"DataQueryTest_DateAndPriceObject"."Price" < "cte"."value"' ],
],
'expectedItems' => [
'fixtures' => [
'nullobj' ,
'obj1' ,
'obj2' ,
]
],
],
// Simple scenario to test where the query is another DataQuery
'naive CTE query with DataQuery' => [
'dataClass' => DataQueryTest\DateAndPriceObject :: class ,
'name' => 'cte' ,
'query' => DataQueryTest\ObjectF :: class ,
'cteFields' => [ 'MyDate' ],
'recursive' => false ,
'extraManipulations' => [
'innerJoin' => [ 'cte' , '"DataQueryTest_DateAndPriceObject"."Date" = "cte"."MyDate"' ],
],
'expectedItems' => [
'fixtures' => [
'obj1' ,
'obj2' ,
]
],
],
// Extrapolate missing data with a recursive query
// Missing data will be returned as records with no ID
'recursive CTE with extrapolated data' => [
'dataClass' => DataQueryTest\DateAndPriceObject :: class ,
'name' => 'dates' ,
'query' => ( new SQLSelect (
'MIN("DataQueryTest_DateAndPriceObject"."Date")' ,
" DataQueryTest_DateAndPriceObject " ,
'"DataQueryTest_DateAndPriceObject"."Date" IS NOT NULL'
)) -> addUnion (
new SQLSelect (
'Date + INTERVAL 1 DAY' ,
'dates' ,
[ 'Date + INTERVAL 1 DAY <= (SELECT MAX("DataQueryTest_DateAndPriceObject"."Date") FROM "DataQueryTest_DateAndPriceObject")' ]
),
SQLSelect :: UNION_ALL
),
'cteFields' => [ 'Date' ],
'recursive' => true ,
'extraManipulations' => [
'selectField' => [ 'COALESCE("DataQueryTest_DateAndPriceObject"."Date", "dates"."Date")' , 'Date' ],
'setAllowCollidingFieldStatements' => [ true ],
'sort' => [ 'dates.Date' ],
'rightJoin' => [ 'dates' , '"DataQueryTest_DateAndPriceObject"."Date" = "dates"."Date"' ],
],
'expectedItems' => [
'data' => [
[ 'fixtureName' => 'obj5' ],
[ 'fixtureName' => 'obj4' ],
[ 'Date' => '2023-01-06' ],
[ 'Date' => '2023-01-05' ],
[ 'fixtureName' => 'obj3' ],
[ 'Date' => '2023-01-03' ],
[ 'fixtureName' => 'obj2' ],
[ 'fixtureName' => 'obj1' ],
]
],
],
// Get the ancestors of a given record with a recursive query
'complex hierarchical CTE with explicit columns' => [
'dataClass' => SQLSelectTest\CteRecursiveObject :: class ,
'name' => 'hierarchy' ,
'query' => (
new SQLSelect (
'"SQLSelectTestCteRecursive"."ParentID"' ,
" SQLSelectTestCteRecursive " ,
[[ '"SQLSelectTestCteRecursive"."ParentID" > 0 AND "SQLSelectTestCteRecursive"."Title" = ?' => 'child of child1' ]]
)
) -> addUnion ( new SQLSelect (
'"SQLSelectTestCteRecursive"."ParentID"' ,
[ '"hierarchy"' , '"SQLSelectTestCteRecursive"' ],
[ '"SQLSelectTestCteRecursive"."ParentID" > 0 AND "SQLSelectTestCteRecursive"."ID" = "hierarchy"."parent_id"' ]
)),
'cteFields' => [ 'parent_id' ],
'recursive' => true ,
'extraManipulations' => [
'innerJoin' => [ 'hierarchy' , '"SQLSelectTestCteRecursive"."ID" = "hierarchy"."parent_id"' ],
],
'expected' => [
'fixtures' => [
'grandparent' ,
'parent' ,
'child1' ,
],
],
],
];
}
/**
* @ dataProvider provideWith
*/
public function testWith (
string $dataClass ,
string $name ,
string | SQLSelect $query ,
array $cteFields ,
bool $recursive ,
array $extraManipulations ,
array $expectedItems
) {
if ( ! DB :: get_conn () -> supportsCteQueries ()) {
$this -> markTestSkipped ( 'The current database does not support WITH clauses' );
}
if ( $recursive && ! DB :: get_conn () -> supportsCteQueries ( true )) {
$this -> markTestSkipped ( 'The current database does not support recursive WITH clauses' );
}
// We can't instantiate a DataQuery in a provider method because it requires the injector, which isn't
// initialised that early. So we just pass the dataclass instead and instiate the query here.
if ( is_string ( $query )) {
$query = new DataQuery ( $query );
}
$dataQuery = new DataQuery ( $dataClass );
$dataQuery -> with ( $name , $query , $cteFields , $recursive );
foreach ( $extraManipulations as $method => $args ) {
$dataQuery -> $method ( ... $args );
}
$expected = [];
if ( isset ( $expectedItems [ 'fixtures' ])) {
foreach ( $expectedItems [ 'fixtures' ] as $fixtureName ) {
$expected [] = $this -> idFromFixture ( $dataClass , $fixtureName );
}
$this -> assertEquals ( $expected , $dataQuery -> execute () -> column ( 'ID' ));
}
if ( isset ( $expectedItems [ 'data' ])) {
foreach ( $expectedItems [ 'data' ] as $data ) {
if ( isset ( $data [ 'fixtureName' ])) {
$data = $this -> objFromFixture ( $dataClass , $data [ 'fixtureName' ]) -> toMap ();
} else {
$data [ 'ClassName' ] = null ;
$data [ 'LastEdited' ] = null ;
$data [ 'Created' ] = null ;
$data [ 'Price' ] = null ;
$data [ 'ID' ] = null ;
}
$expected [] = $data ;
}
$this -> assertListEquals ( $expected , new ArrayList ( iterator_to_array ( $dataQuery -> execute (), true )));
}
}
/**
* tests the WITH clause , using a DataQuery as the CTE query
*/
public function testWithUsingDataQuery ()
{
if ( ! DB :: get_conn () -> supportsCteQueries ( true )) {
$this -> markTestSkipped ( 'The current database does not support recursive WITH clauses' );
}
$dataQuery = new DataQuery ( SQLSelectTest\CteRecursiveObject :: class );
$cteQuery = new DataQuery ( SQLSelectTest\CteRecursiveObject :: class );
$cteQuery -> where ([
'"SQLSelectTestCteRecursive"."ParentID" > 0' ,
'"SQLSelectTestCteRecursive"."Title" = ?' => 'child of child2'
]);
$cteQuery -> union ( new SQLSelect (
'"SQLSelectTestCteRecursive"."ParentID"' ,
[ '"hierarchy"' , '"SQLSelectTestCteRecursive"' ],
[
'"SQLSelectTestCteRecursive"."ParentID" > 0' ,
'"SQLSelectTestCteRecursive"."ID" = "hierarchy"."ParentID"'
]
));
$dataQuery -> with ( 'hierarchy' , $cteQuery , [ 'ParentID' ], true );
$dataQuery -> innerJoin ( 'hierarchy' , '"SQLSelectTestCteRecursive"."ID" = "hierarchy"."ParentID"' );
$expectedFixtures = [
'child2' ,
'parent' ,
'grandparent' ,
];
$expectedData = [];
foreach ( $expectedFixtures as $fixtureName ) {
$expectedData [] = $this -> objFromFixture ( SQLSelectTest\CteRecursiveObject :: class , $fixtureName ) -> toMap ();
}
$this -> assertListEquals ( $expectedData , new ArrayList ( iterator_to_array ( $dataQuery -> execute (), true )));
}
/**
* tests the WITH clause , using a DataQuery as the CTE query and as the unioned recursive query
*/
public function testWithUsingOnlyDataQueries ()
{
if ( ! DB :: get_conn () -> supportsCteQueries ( true )) {
$this -> markTestSkipped ( 'The current database does not support recursive WITH clauses' );
}
$dataQuery = new DataQuery ( SQLSelectTest\CteRecursiveObject :: class );
$cteQuery = new DataQuery ( SQLSelectTest\CteRecursiveObject :: class );
$cteQuery -> where ([
'"SQLSelectTestCteRecursive"."ParentID" > 0' ,
'"SQLSelectTestCteRecursive"."Title" = ?' => 'child of child2'
]);
$cteQuery -> union (( new DataQuery ( SQLSelectTest\CteRecursiveObject :: class ))
-> innerJoin ( 'hierarchy' , '"SQLSelectTestCteRecursive"."ID" = "hierarchy"."ParentID"' )
-> where ( '"SQLSelectTestCteRecursive"."ParentID" > 0' )
-> sort ( null )
-> distinct ( false ));
// This test exists because previously when $cteFields was empty, it would cause an error with the above setup.
$dataQuery -> with ( 'hierarchy' , $cteQuery , [], true );
$dataQuery -> innerJoin ( 'hierarchy' , '"SQLSelectTestCteRecursive"."ID" = "hierarchy"."ParentID"' );
$expectedFixtures = [
'child2' ,
'parent' ,
'grandparent' ,
];
$expectedData = [];
foreach ( $expectedFixtures as $fixtureName ) {
$expectedData [] = $this -> objFromFixture ( SQLSelectTest\CteRecursiveObject :: class , $fixtureName ) -> toMap ();
}
$this -> assertListEquals ( $expectedData , new ArrayList ( iterator_to_array ( $dataQuery -> execute (), true )));
}
/**
* Tests that CTE queries have appropriate JOINs for subclass tables etc .
* If `$query->query()->` was replaced with `$query->query->` in DataQuery :: with (), this test would throw an exception .
* @ doesNotPerformAssertions
*/
public function testWithUsingDataQueryAppliesRelations ()
{
if ( ! DB :: get_conn () -> supportsCteQueries ()) {
$this -> markTestSkipped ( 'The current database does not support WITH clauses' );
}
$dataQuery = new DataQuery ( DataQueryTest\ObjectG :: class );
$cteQuery = new DataQuery ( DataQueryTest\ObjectG :: class );
$cteQuery -> where ([ '"DataQueryTest_G"."SubClassOnlyField" = ?' => 'This is the one' ]);
$dataQuery -> with ( 'test_implicit_joins' , $cteQuery , [ 'ID' ]);
$dataQuery -> innerJoin ( 'test_implicit_joins' , '"DataQueryTest_G"."ID" = "test_implicit_joins"."ID"' );
// This will throw an exception if it fails - it passes if there's no exception.
$dataQuery -> execute ();
}
2012-06-25 01:34:02 +02:00
}