silverstripe-framework/tests/model/SQLQueryTest.php
Damian Mooyman d8e9af8af8 API New Database abstraction layer. Ticket #7429
Database abstraction broken up into controller, connector, query builder, and schema manager, each independently configurable via YAML / Injector
Creation of new DBQueryGenerator for database specific generation of SQL
Support for parameterised queries, move of code base to use these over escaped conditions
Refactor of SQLQuery into separate query classes for each of INSERT UPDATE DELETE and SELECT
Support for PDO
Installation process upgraded to use new ORM
SS_DatabaseException created to handle database errors, maintaining details of raw sql and parameter details for user code designed interested in that data.
Renamed DB static methods to conform correctly to naming conventions (e.g. DB::getConn -> DB::get_conn)
3.2 upgrade docs
Performance Optimisation and simplification of code to use more concise API
API Ability for database adapters to register extensions to ConfigureFromEnv.php
2014-07-09 18:04:05 +12:00

586 lines
17 KiB
PHP
Executable File

<?php
/**
* @package framework
* @subpackage tests
*/
class SQLQueryTest extends SapphireTest {
protected static $fixture_file = 'SQLQueryTest.yml';
protected $extraDataObjects = array(
'SQLQueryTest_DO',
);
public function testEmptyQueryReturnsNothing() {
$query = new SQLSelect();
$this->assertSQLEquals('', $query->sql($parameters));
}
public function testSelectFromBasicTable() {
$query = new SQLSelect();
$query->setFrom('MyTable');
$this->assertSQLEquals("SELECT * FROM MyTable", $query->sql($parameters));
$query->addFrom('MyJoin');
$this->assertSQLEquals("SELECT * FROM MyTable MyJoin", $query->sql($parameters));
}
public function testSelectFromUserSpecifiedFields() {
$query = new SQLSelect();
$query->setSelect(array("Name", "Title", "Description"));
$query->setFrom("MyTable");
$this->assertSQLEquals("SELECT Name, Title, Description FROM MyTable", $query->sql($parameters));
}
public function testSelectWithWhereClauseFilter() {
$query = new SQLSelect();
$query->setSelect(array("Name","Meta"));
$query->setFrom("MyTable");
$query->setWhere("Name = 'Name'");
$query->addWhere("Meta = 'Test'");
$this->assertSQLEquals(
"SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')",
$query->sql($parameters)
);
}
public function testSelectWithConstructorParameters() {
$query = new SQLSelect(array("Foo", "Bar"), "FooBarTable");
$this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable", $query->sql($parameters));
$query = new SQLSelect(array("Foo", "Bar"), "FooBarTable", array("Foo = 'Boo'"));
$this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable WHERE (Foo = 'Boo')", $query->sql($parameters));
}
public function testSelectWithChainedMethods() {
$query = new SQLSelect();
$query->setSelect("Name","Meta")->setFrom("MyTable")->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'");
$this->assertSQLEquals(
"SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')",
$query->sql($parameters)
);
}
public function testCanSortBy() {
$query = new SQLSelect();
$query->setSelect("Name","Meta")->setFrom("MyTable")->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'");
$this->assertTrue($query->canSortBy('Name ASC'));
$this->assertTrue($query->canSortBy('Name'));
}
public function testSelectWithChainedFilterParameters() {
$query = new SQLSelect();
$query->setSelect(array("Name","Meta"))->setFrom("MyTable");
$query->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'")->addWhere("Beta != 'Gamma'");
$this->assertSQLEquals(
"SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test') AND (Beta != 'Gamma')",
$query->sql($parameters)
);
}
public function testSelectWithLimitClause() {
if(!(DB::get_conn() instanceof MySQLDatabase || DB::get_conn() instanceof SQLite3Database
|| DB::get_conn() instanceof PostgreSQLDatabase)) {
$this->markTestIncomplete();
}
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setLimit(99);
$this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99", $query->sql($parameters));
// array limit with start (MySQL specific)
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setLimit(99, 97);
$this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99 OFFSET 97", $query->sql($parameters));
}
public function testSelectWithOrderbyClause() {
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('MyName');
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('MyName desc');
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('MyName ASC, Color DESC');
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color DESC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('MyName ASC, Color');
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color ASC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy(array('MyName' => 'desc'));
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy(array('MyName' => 'desc', 'Color'));
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color ASC', $query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('implode("MyName","Color")');
$this->assertSQLEquals(
'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
$query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('implode("MyName","Color") DESC');
$this->assertSQLEquals(
'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" DESC',
$query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setOrderBy('RAND()');
$this->assertSQLEquals(
'SELECT *, RAND() AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
$query->sql($parameters));
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->addFrom('INNER JOIN SecondTable USING (ID)');
$query->addFrom('INNER JOIN ThirdTable USING (ID)');
$query->setOrderBy('MyName');
$this->assertSQLEquals(
'SELECT * FROM MyTable '
. 'INNER JOIN SecondTable USING (ID) '
. 'INNER JOIN ThirdTable USING (ID) '
. 'ORDER BY MyName ASC',
$query->sql($parameters));
}
public function testNullLimit() {
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setLimit(null);
$this->assertSQLEquals(
'SELECT * FROM MyTable',
$query->sql($parameters)
);
}
public function testZeroLimit() {
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setLimit(0);
$this->assertSQLEquals(
'SELECT * FROM MyTable',
$query->sql($parameters)
);
}
public function testZeroLimitWithOffset() {
if(!(DB::get_conn() instanceof MySQLDatabase || DB::get_conn() instanceof SQLite3Database
|| DB::get_conn() instanceof PostgreSQLDatabase)) {
$this->markTestIncomplete();
}
$query = new SQLSelect();
$query->setFrom("MyTable");
$query->setLimit(0, 99);
$this->assertSQLEquals(
'SELECT * FROM MyTable LIMIT 0 OFFSET 99',
$query->sql($parameters)
);
}
/**
* @expectedException InvalidArgumentException
*/
public function testNegativeLimit() {
$query = new SQLSelect();
$query->setLimit(-10);
}
/**
* @expectedException InvalidArgumentException
*/
public function testNegativeOffset() {
$query = new SQLSelect();
$query->setLimit(1, -10);
}
/**
* @expectedException InvalidArgumentException
*/
public function testNegativeOffsetAndLimit() {
$query = new SQLSelect();
$query->setLimit(-10, -10);
}
public function testReverseOrderBy() {
$query = new SQLSelect();
$query->setFrom('MyTable');
// default is ASC
$query->setOrderBy("Name");
$query->reverseOrderBy();
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC',$query->sql($parameters));
$query->setOrderBy("Name DESC");
$query->reverseOrderBy();
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC',$query->sql($parameters));
$query->setOrderBy(array("Name" => "ASC"));
$query->reverseOrderBy();
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC',$query->sql($parameters));
$query->setOrderBy(array("Name" => 'DESC', 'Color' => 'asc'));
$query->reverseOrderBy();
$this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC, Color DESC',$query->sql($parameters));
$query->setOrderBy('implode("MyName","Color") DESC');
$query->reverseOrderBy();
$this->assertSQLEquals(
'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
$query->sql($parameters));
}
public function testFiltersOnID() {
$query = new SQLSelect();
$query->setWhere("ID = 5");
$this->assertTrue(
$query->filtersOnID(),
"filtersOnID() is true with simple unquoted column name"
);
$query = new SQLSelect();
$query->setWhere("ID=5");
$this->assertTrue(
$query->filtersOnID(),
"filtersOnID() is true with simple unquoted column name and no spaces in equals sign"
);
$query = new SQLSelect();
$query->setWhere("Identifier = 5");
$this->assertFalse(
$query->filtersOnID(),
"filtersOnID() is false with custom column name (starting with 'id')"
);
$query = new SQLSelect();
$query->setWhere("ParentID = 5");
$this->assertFalse(
$query->filtersOnID(),
"filtersOnID() is false with column name ending in 'ID'"
);
$query = new SQLSelect();
$query->setWhere("MyTable.ID = 5");
$this->assertTrue(
$query->filtersOnID(),
"filtersOnID() is true with table and column name"
);
$query = new SQLSelect();
$query->setWhere("MyTable.ID = 5");
$this->assertTrue(
$query->filtersOnID(),
"filtersOnID() is true with table and quoted column name "
);
}
public function testFiltersOnFK() {
$query = new SQLSelect();
$query->setWhere("ID = 5");
$this->assertFalse(
$query->filtersOnFK(),
"filtersOnFK() is true with simple unquoted column name"
);
$query = new SQLSelect();
$query->setWhere("Identifier = 5");
$this->assertFalse(
$query->filtersOnFK(),
"filtersOnFK() is false with custom column name (starting with 'id')"
);
$query = new SQLSelect();
$query->setWhere("MyTable.ParentID = 5");
$this->assertTrue(
$query->filtersOnFK(),
"filtersOnFK() is true with table and column name"
);
$query = new SQLSelect();
$query->setWhere("MyTable.`ParentID`= 5");
$this->assertTrue(
$query->filtersOnFK(),
"filtersOnFK() is true with table and quoted column name "
);
}
public function testInnerJoin() {
$query = new SQLSelect();
$query->setFrom('MyTable');
$query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2');
$query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID');
$this->assertSQLEquals('SELECT * FROM MyTable '.
'INNER JOIN "MyOtherTable" ON MyOtherTable.ID = 2 '.
'LEFT JOIN "MyLastTable" ON MyOtherTable.ID = MyLastTable.ID',
$query->sql($parameters)
);
$query = new SQLSelect();
$query->setFrom('MyTable');
$query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2', 'table1');
$query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID', 'table2');
$this->assertSQLEquals('SELECT * FROM MyTable '.
'INNER JOIN "MyOtherTable" AS "table1" ON MyOtherTable.ID = 2 '.
'LEFT JOIN "MyLastTable" AS "table2" ON MyOtherTable.ID = MyLastTable.ID',
$query->sql($parameters)
);
}
public function testSetWhereAny() {
$query = new SQLSelect();
$query->setFrom('MyTable');
$query->setWhereAny(array(
'Monkey' => 'Chimp',
'Color' => 'Brown'
));
$sql = $query->sql($parameters);
$this->assertSQLEquals("SELECT * FROM MyTable WHERE ((Monkey = ?) OR (Color = ?))", $sql);
$this->assertEquals(array('Chimp', 'Brown'), $parameters);
}
public function testSelectFirst() {
// Test first from sequence
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$result = $query->firstRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(1, $records);
$this->assertEquals('Object 1', $records[0]['Name']);
// Test first from empty sequence
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$query->setWhere(array('"Name"' => 'Nonexistent Object'));
$result = $query->firstRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(0, $records);
// Test that given the last item, the 'first' in this list matches the last
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$query->setLimit(1, 1);
$result = $query->firstRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(1, $records);
$this->assertEquals('Object 2', $records[0]['Name']);
}
public function testSelectLast() {
// Test last in sequence
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$result = $query->lastRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(1, $records);
$this->assertEquals('Object 2', $records[0]['Name']);
// Test last from empty sequence
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$query->setWhere(array("\"Name\" = 'Nonexistent Object'"));
$result = $query->lastRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(0, $records);
// Test that given the first item, the 'last' in this list matches the first
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('"Name"');
$query->setLimit(1);
$result = $query->lastRow()->execute();
$records = array();
foreach($result as $row) {
$records[] = $row;
}
$this->assertCount(1, $records);
$this->assertEquals('Object 1', $records[0]['Name']);
}
/**
* Tests aggregate() function
*/
public function testAggregate() {
$query = new SQLSelect('"Common"');
$query->setFrom('"SQLQueryTest_DO"');
$query->setGroupBy('"Common"');
$queryClone = $query->aggregate('COUNT(*)', 'cnt');
$result = $queryClone->execute();
$this->assertEquals(array(2), $result->column('cnt'));
}
/**
* Tests that an ORDER BY is only added if a LIMIT is set.
*/
public function testAggregateNoOrderByIfNoLimit() {
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('Common');
$query->setLimit(array());
$aggregate = $query->aggregate('MAX("ID")');
$limit = $aggregate->getLimit();
$this->assertEquals(array(), $aggregate->getOrderBy());
$this->assertEquals(array(), $limit);
$query = new SQLSelect();
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy('Common');
$query->setLimit(2);
$aggregate = $query->aggregate('MAX("ID")');
$limit = $aggregate->getLimit();
$this->assertEquals(array('Common' => 'ASC'), $aggregate->getOrderBy());
$this->assertEquals(array('start' => 0, 'limit' => 2), $limit);
}
/**
* Test that "_SortColumn0" is added for an aggregate in the ORDER BY
* clause, in combination with a LIMIT and GROUP BY clause.
* For some databases, like MSSQL, this is a complicated scenario
* because a subselect needs to be done to query paginated data.
*/
public function testOrderByContainingAggregateAndLimitOffset() {
$query = new SQLSelect();
$query->setSelect(array('"Name"', '"Meta"'));
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy(array('MAX("Date")'));
$query->setGroupBy(array('"Name"', '"Meta"'));
$query->setLimit('1', '1');
$records = array();
foreach($query->execute() as $record) {
$records[] = $record;
}
$this->assertCount(1, $records);
$this->assertEquals('Object 2', $records[0]['Name']);
$this->assertEquals('2012-05-01 09:00:00', $records['0']['_SortColumn0']);
}
/**
* Test that multiple order elements are maintained in the given order
*/
public function testOrderByMultiple() {
if(DB::get_conn() instanceof MySQLDatabase) {
$query = new SQLSelect();
$query->setSelect(array('"Name"', '"Meta"'));
$query->setFrom('"SQLQueryTest_DO"');
$query->setOrderBy(array('MID("Name", 8, 1) DESC', '"Name" ASC'));
$records = array();
foreach($query->execute() as $record) {
$records[] = $record;
}
$this->assertCount(2, $records);
$this->assertEquals('Object 2', $records[0]['Name']);
$this->assertEquals('2', $records[0]['_SortColumn0']);
$this->assertEquals('Object 1', $records[1]['Name']);
$this->assertEquals('1', $records[1]['_SortColumn0']);
}
}
/**
* Test passing in a LIMIT with OFFSET clause string.
*/
public function testLimitSetFromClauseString() {
$query = new SQLSelect();
$query->setSelect('*');
$query->setFrom('"SQLQueryTest_DO"');
$query->setLimit('20 OFFSET 10');
$limit = $query->getLimit();
$this->assertEquals(20, $limit['limit']);
$this->assertEquals(10, $limit['start']);
}
}
class SQLQueryTest_DO extends DataObject implements TestOnly {
private static $db = array(
"Name" => "Varchar",
"Meta" => "Varchar",
"Common" => "Varchar",
"Date" => "SS_Datetime"
);
}
class SQLQueryTestBase extends DataObject implements TestOnly {
private static $db = array(
"Title" => "Varchar",
);
}
class SQLQueryTestChild extends SQLQueryTestBase {
private static $db = array(
"Name" => "Varchar",
);
private static $has_one = array(
);
}