diff --git a/src/Dev/SapphireTest.php b/src/Dev/SapphireTest.php index 3694f18ae42..0be668a49c4 100644 --- a/src/Dev/SapphireTest.php +++ b/src/Dev/SapphireTest.php @@ -20,6 +20,7 @@ use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\InjectorLoader; +use SilverStripe\Core\Kernel; use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Dev\Constraint\SSListContains; @@ -1231,4 +1232,19 @@ protected function mockSleep(int $seconds): DBDatetime return $now; } + + /** + * Execute a callback in a given environment type and return the result. + */ + protected function executeInEnvironmentType(string $env, callable $callback): mixed + { + $kernel = Injector::inst()->get(Kernel::class); + $origMode = $kernel->getEnvironment(); + $kernel->setEnvironment($env); + try { + return $callback(); + } finally { + $kernel->setEnvironment($origMode); + } + } } diff --git a/src/ORM/Connect/DBQueryBuilder.php b/src/ORM/Connect/DBQueryBuilder.php index b9a79794fff..fa71d9dccab 100644 --- a/src/ORM/Connect/DBQueryBuilder.php +++ b/src/ORM/Connect/DBQueryBuilder.php @@ -3,7 +3,15 @@ namespace SilverStripe\ORM\Connect; use InvalidArgumentException; +use SilverStripe\Control\Director; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Convert; +use SilverStripe\ORM\DataList; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataQuery; +use SilverStripe\ORM\DB; +use SilverStripe\ORM\ListDecorator; +use SilverStripe\ORM\Map; use SilverStripe\ORM\Queries\SQLExpression; use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLDelete; @@ -16,6 +24,12 @@ */ class DBQueryBuilder { + use Configurable; + + /** + * If true, a comment is added to each query indicating where that query's execution originated. + */ + private static bool $trace_query_origin = false; /** * Determines the line separator to use. @@ -57,9 +71,121 @@ public function buildSQL(SQLExpression $query, &$parameters) "Not implemented: query generation for type " . get_class($query) ); } + + if (static::config()->get('trace_query_origin')) { + $sql = $this->buildTraceComment() . $sql; + } + return $sql; } + /** + * Builds an SQL comment indicating where the query was executed from. + */ + protected function buildTraceComment(): string + { + $comment = '/* '; + + $ignoreBaseClasses = [ + self::class, + DataQuery::class, + SQLExpression::class, + DB::class, + Database::class, + DBConnector::class, + DBSchemaManager::class, + TransactionManager::class, + ListDecorator::class, + Map::class, + ]; + $ignoreMethods = [ + DataList::class => [ + // these are used in almost all DataList query executions + 'executeQuery', + 'getFinalisedQuery', + 'getIterator', + // these call a method on DataList (e.g. $this->toNestedArray()) + 'debug', + 'setByIDList', + ], + DataObject::class => [ + 'get_one', + 'get_by_id', + ] + ]; + + $line = null; + $file = null; + $class = null; + $function = null; + + // Don't include arguments in the trace (since we don't need them), and only go back 15 levels. + // Anything further than that and we've probably over-abstracted things. + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15); + foreach ($trace as $i => $item) { + // We need to be able to look ahead one item in the trace, because the class/function values + // are talking about what is being *called* on this line, not the function this line lives in. + if (!isset($trace[$i+1])) { + return '/* Could not identify source of query */' . $this->getSeparator(); + } + $caller = [ + 'file' => $item['file'] ?? null, + 'line' => $item['line'] ?? null, + 'class' => $trace[$i + 1]['class'] ?? null, + 'function' => $trace[$i + 1]['function'] ?? null, + ]; + + if ($caller['class'] !== null) { + // Don't report internal ORM operations for any of these classes + foreach ($ignoreBaseClasses as $ignore) { + if (is_a($caller['class'], $ignore, true)) { + // skip for both loops + continue 2; + } + } + if ($caller['function'] !== null) { + // Don't report internal ORM operations for any of these methods + foreach ($ignoreMethods as $class => $methodsToIgnore) { + if (is_a($caller['class'], $class, true) && in_array($caller['function'], $methodsToIgnore)) { + // skip for both loops + continue 2; + } + } + // Don't report internal ORM operations inside DataList which themselves directly call methods on DataQuery + if ($caller['class'] === DataList::class && is_a($item['class'] ?? '', DataQuery::class, true)) { + continue; + } + // Don't report internal ORM operations inside DataList for eagerloading or for any methods that iterate over the list itself + if ($caller['class'] === DataList::class && + (str_starts_with($caller['function'], 'fetchEagerLoad') + || is_a($item['class'] ?? '', DataList::class, true) && in_array($item['function'], $ignoreMethods[DataList::class])) + ) { + continue; + } + } + } + + // Get the relevant trace information if it's available + $file = $caller['file']; + $line = $caller['line']; + $class = $caller['class']; + $function = $caller['function']; + break; + } + + // Indicate where the query was executed from, if we have that information. + if ($line && $file) { + $comment .= "Query executed from $file line $line"; + } elseif ($class && $function) { + $comment .= "Query executed from {$class}::{$function}()"; + } else { + $comment .= 'Could not identify source of query'; + } + + $comment .= ' */' . $this->getSeparator(); + return $comment; + } + /** * Builds a query from a SQLSelect expression * diff --git a/tests/php/ORM/DataListEagerLoadingTest.php b/tests/php/ORM/DataListEagerLoadingTest.php index 2871ce834b7..3fa706a9306 100644 --- a/tests/php/ORM/DataListEagerLoadingTest.php +++ b/tests/php/ORM/DataListEagerLoadingTest.php @@ -139,6 +139,8 @@ private function stopCountingSelectQueries(): int { $s = ob_get_clean(); $s = preg_replace('/.*__START_ITERATE__/s', '', $s); + // Remove multi-line SQL comments and the whitespace after them + $s = preg_replace('#/\*[^\*]*\*/\s*#', '', $s); $this->resetShowQueries(); return substr_count($s, ': SELECT'); } diff --git a/tests/php/ORM/DataListTest.php b/tests/php/ORM/DataListTest.php index b5fbdf63a64..fccf7b4ca36 100755 --- a/tests/php/ORM/DataListTest.php +++ b/tests/php/ORM/DataListTest.php @@ -6,7 +6,9 @@ use Exception; use InvalidArgumentException; use SilverStripe\Core\Convert; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\InjectorNotFoundException; +use SilverStripe\Core\Kernel; use SilverStripe\Dev\SapphireTest; use SilverStripe\ORM\Connect\MySQLiConnector; use SilverStripe\ORM\DataList; @@ -301,7 +303,11 @@ public function testSql() . $db->quoteString(DataObjectTest\TeamComment::class) . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment"' . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC'; - $this->assertSQLEquals($expected, $list->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $list->sql()); + + $this->assertSQLEquals($expected, $sql); } public function provideJoin() @@ -348,7 +354,13 @@ public function testJoin(string $joinMethod, string $joinType) . '"DataObjectTest_TeamComment"."TeamID"' . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC'; - $this->assertSQLEquals($expected, $list->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $parameters = []; + $sql = $this->executeInEnvironmentType('live', function () use (&$parameters, $list) { + return $list->sql($parameters); + }); + + $this->assertSQLEquals($expected, $sql); $this->assertEmpty($parameters); } @@ -382,7 +394,13 @@ public function testJoinParameterised(string $joinMethod, string $joinType) . 'AND "DataObjectTest_Team"."Title" LIKE ?' . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC'; - $this->assertSQLEquals($expected, $list->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $parameters = []; + $sql = $this->executeInEnvironmentType('live', function () use (&$parameters, $list) { + return $list->sql($parameters); + }); + + $this->assertSQLEquals($expected, $sql); $this->assertEquals(['Team%'], $parameters); } diff --git a/tests/php/ORM/DataObjectLazyLoadingTest.php b/tests/php/ORM/DataObjectLazyLoadingTest.php index ad2ed19aba0..c562851f69c 100644 --- a/tests/php/ORM/DataObjectLazyLoadingTest.php +++ b/tests/php/ORM/DataObjectLazyLoadingTest.php @@ -2,6 +2,7 @@ namespace SilverStripe\ORM\Tests; +use SilverStripe\Core\Injector\Injector; use SilverStripe\ORM\DB; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; @@ -29,7 +30,11 @@ public function testQueriedColumnsID() $playerList = new DataList(SubTeam::class); $playerList = $playerList->setQueriedColumns(['ID']); $expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."LastEdited", ' . '"DataObjectTest_Team"."Created", "DataObjectTest_Team"."ID", CASE WHEN ' . '"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' . $db->quoteString(Team::class) . ' END AS "RecordClassName", "DataObjectTest_Team"."Title" ' . 'FROM "DataObjectTest_Team" ' . 'LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = "DataObjectTest_Team"."ID" ' . 'WHERE ("DataObjectTest_Team"."ClassName" IN (?))' . ' ORDER BY "DataObjectTest_Team"."Title" ASC'; - $this->assertSQLEquals($expected, $playerList->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $playerList->sql()); + + $this->assertSQLEquals($expected, $sql); } public function testQueriedColumnsFromBaseTableAndSubTable() @@ -38,7 +43,11 @@ public function testQueriedColumnsFromBaseTableAndSubTable() $playerList = new DataList(SubTeam::class); $playerList = $playerList->setQueriedColumns(['Title', 'SubclassDatabaseField']); $expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."LastEdited", ' . '"DataObjectTest_Team"."Created", "DataObjectTest_Team"."Title", ' . '"DataObjectTest_SubTeam"."SubclassDatabaseField", "DataObjectTest_Team"."ID", CASE WHEN ' . '"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' . $db->quoteString(Team::class) . ' END AS "RecordClassName" FROM "DataObjectTest_Team" ' . 'LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = "DataObjectTest_Team"."ID" WHERE ' . '("DataObjectTest_Team"."ClassName" IN (?)) ' . 'ORDER BY "DataObjectTest_Team"."Title" ASC'; - $this->assertSQLEquals($expected, $playerList->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $playerList->sql()); + + $this->assertSQLEquals($expected, $sql); } public function testQueriedColumnsFromBaseTable() @@ -47,7 +56,11 @@ public function testQueriedColumnsFromBaseTable() $playerList = new DataList(SubTeam::class); $playerList = $playerList->setQueriedColumns(['Title']); $expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."LastEdited", ' . '"DataObjectTest_Team"."Created", "DataObjectTest_Team"."Title", "DataObjectTest_Team"."ID", ' . 'CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' . $db->quoteString(Team::class) . ' END AS "RecordClassName" FROM "DataObjectTest_Team" ' . 'LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = "DataObjectTest_Team"."ID" WHERE ' . '("DataObjectTest_Team"."ClassName" IN (?)) ' . 'ORDER BY "DataObjectTest_Team"."Title" ASC'; - $this->assertSQLEquals($expected, $playerList->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $playerList->sql()); + + $this->assertSQLEquals($expected, $sql); } public function testQueriedColumnsFromSubTable() @@ -56,7 +69,11 @@ public function testQueriedColumnsFromSubTable() $playerList = new DataList(SubTeam::class); $playerList = $playerList->setQueriedColumns(['SubclassDatabaseField']); $expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."LastEdited", ' . '"DataObjectTest_Team"."Created", "DataObjectTest_SubTeam"."SubclassDatabaseField", ' . '"DataObjectTest_Team"."ID", CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN ' . '"DataObjectTest_Team"."ClassName" ELSE ' . $db->quoteString(Team::class) . ' END ' . 'AS "RecordClassName", "DataObjectTest_Team"."Title" ' . 'FROM "DataObjectTest_Team" LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = ' . '"DataObjectTest_Team"."ID" WHERE ("DataObjectTest_Team"."ClassName" IN (?)) ' . 'ORDER BY "DataObjectTest_Team"."Title" ASC'; - $this->assertSQLEquals($expected, $playerList->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $playerList->sql()); + + $this->assertSQLEquals($expected, $sql); } public function testNoSpecificColumnNamesBaseDataObjectQuery() diff --git a/tests/php/ORM/ManyManyListTest.php b/tests/php/ORM/ManyManyListTest.php index f29856568bc..03df971f625 100644 --- a/tests/php/ORM/ManyManyListTest.php +++ b/tests/php/ORM/ManyManyListTest.php @@ -7,6 +7,8 @@ use SilverStripe\ORM\FieldType\DBMoney; use SilverStripe\ORM\ManyManyList; use SilverStripe\Core\Convert; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\Core\Kernel; use SilverStripe\Dev\SapphireTest; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\Tests\DataObjectTest\Player; @@ -437,7 +439,10 @@ public function testAppendExtraFieldsToQuery() . ' "ManyManyListTest_ExtraFields_Clients"."ManyManyListTest_ExtraFieldsID" =' . ' "ManyManyListTest_ExtraFields"."ID"'; - $this->assertSQLEquals($expected, $list->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $list->sql()); + + $this->assertSQLEquals($expected, $sql); } /** diff --git a/tests/php/ORM/SQLSelectTest.php b/tests/php/ORM/SQLSelectTest.php index 1d4b2f8f50b..2d3b4068ee0 100755 --- a/tests/php/ORM/SQLSelectTest.php +++ b/tests/php/ORM/SQLSelectTest.php @@ -4,6 +4,8 @@ use InvalidArgumentException; use LogicException; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\Core\Kernel; use SilverStripe\ORM\DB; use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Queries\SQLSelect; @@ -147,7 +149,11 @@ public function testSelectFrom(array $from, string $expected) { $query = new SQLSelect(); $query->setFrom($from); - $this->assertSQLEquals($expected, $query->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals($expected, $sql); } public function testSelectFromUserSpecifiedFields() @@ -155,7 +161,11 @@ public function testSelectFromUserSpecifiedFields() $query = new SQLSelect(); $query->setSelect(["Name", "Title", "Description"]); $query->setFrom("MyTable"); - $this->assertSQLEquals("SELECT Name, Title, Description FROM MyTable", $query->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals("SELECT Name, Title, Description FROM MyTable", $sql); } public function testSelectWithWhereClauseFilter() @@ -165,28 +175,38 @@ public function testSelectWithWhereClauseFilter() $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) - ); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals("SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')", $sql); } public function testSelectWithConstructorParameters() { $query = new SQLSelect(["Foo", "Bar"], "FooBarTable"); - $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable", $query->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable", $sql); $query = new SQLSelect(["Foo", "Bar"], "FooBarTable", ["Foo = 'Boo'"]); - $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable WHERE (Foo = 'Boo')", $query->sql($parameters)); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable WHERE (Foo = 'Boo')", $sql); } 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) - ); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + + $this->assertSQLEquals("SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')", $sql); } public function testCanSortBy() @@ -204,9 +224,13 @@ public function testAddOrderBy() { $query = new SQLSelect(); $query->setSelect('ID', "Title")->setFrom('Page')->addOrderBy('(ID % 2) = 0', 'ASC')->addOrderBy('ID > 50', 'ASC'); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals( 'SELECT ID, Title, (ID % 2) = 0 AS "_SortColumn0", ID > 50 AS "_SortColumn1" FROM Page ORDER BY "_SortColumn0" ASC, "_SortColumn1" ASC', - $query->sql($parameters) + $sql ); } @@ -215,9 +239,13 @@ public function testSelectWithChainedFilterParameters() $query = new SQLSelect(); $query->setSelect(["Name","Meta"])->setFrom("MyTable"); $query->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'")->addWhere("Beta != 'Gamma'"); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals( "SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test') AND (Beta != 'Gamma')", - $query->sql($parameters) + $sql ); } @@ -232,13 +260,17 @@ public function testSelectWithLimitClause() $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setLimit(99); - $this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99", $query->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99", $sql); // 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)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99 OFFSET 97", $sql); } @@ -247,55 +279,73 @@ public function testSelectWithOrderbyClause() $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy('MyName'); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC', $query->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC', $sql); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy('MyName desc'); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $sql); $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)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color DESC', $sql); $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)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color ASC', $sql); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy(['MyName' => 'desc']); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $sql); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy(['MyName' => 'desc', 'Color']); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color ASC', $query->sql($parameters)); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color ASC', $sql); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy('implode("MyName","Color")'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC', - $query->sql($parameters) + $sql ); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy('implode("MyName","Color") DESC'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" DESC', - $query->sql($parameters) + $sql ); $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setOrderBy('RAND()'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT *, RAND() AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC', - $query->sql($parameters) + $sql ); $query = new SQLSelect(); @@ -303,12 +353,14 @@ public function testSelectWithOrderbyClause() $query->addFrom('INNER JOIN SecondTable USING (ID)'); $query->addFrom('INNER JOIN ThirdTable USING (ID)'); $query->setOrderBy('MyName'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT * FROM MyTable ' . 'INNER JOIN SecondTable USING (ID) ' . 'INNER JOIN ThirdTable USING (ID) ' . 'ORDER BY MyName ASC', - $query->sql($parameters) + $sql ); } @@ -317,11 +369,10 @@ public function testNullLimit() $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setLimit(null); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals( - 'SELECT * FROM MyTable', - $query->sql($parameters) - ); + $this->assertSQLEquals('SELECT * FROM MyTable', $sql); } public function testZeroLimit() @@ -329,11 +380,10 @@ public function testZeroLimit() $query = new SQLSelect(); $query->setFrom("MyTable"); $query->setLimit(0); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals( - 'SELECT * FROM MyTable LIMIT 0', - $query->sql($parameters) - ); + $this->assertSQLEquals('SELECT * FROM MyTable LIMIT 0', $sql); } public function testNegativeLimit() @@ -365,30 +415,40 @@ public function testReverseOrderBy() // default is ASC $query->setOrderBy("Name"); $query->reverseOrderBy(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $query->sql($parameters)); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $sql); $query->setOrderBy("Name DESC"); $query->reverseOrderBy(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC', $query->sql($parameters)); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC', $sql); $query->setOrderBy(["Name" => "ASC"]); $query->reverseOrderBy(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $query->sql($parameters)); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $sql); $query->setOrderBy(["Name" => 'DESC', 'Color' => 'asc']); $query->reverseOrderBy(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); - $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC, Color DESC', $query->sql($parameters)); + $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC, Color DESC', $sql); $query->setOrderBy('implode("MyName","Color") DESC'); $query->reverseOrderBy(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC', - $query->sql($parameters) + $sql ); } @@ -510,13 +570,15 @@ public function testJoinSQL() $query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2'); $query->addRightJoin('MySecondTable', 'MyOtherTable.ID = MySecondTable.ID'); $query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT * FROM MyTable ' . 'INNER JOIN "MyOtherTable" ON MyOtherTable.ID = 2 ' . 'RIGHT JOIN "MySecondTable" ON MyOtherTable.ID = MySecondTable.ID ' . 'LEFT JOIN "MyLastTable" ON MyOtherTable.ID = MyLastTable.ID', - $query->sql($parameters) + $sql ); $query = new SQLSelect(); @@ -524,13 +586,15 @@ public function testJoinSQL() $query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2', 'table1'); $query->addRightJoin('MySecondTable', 'MyOtherTable.ID = MySecondTable.ID', 'table2'); $query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID', 'table3'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT * FROM MyTable ' . 'INNER JOIN "MyOtherTable" AS "table1" ON MyOtherTable.ID = 2 ' . 'RIGHT JOIN "MySecondTable" AS "table2" ON MyOtherTable.ID = MySecondTable.ID ' . 'LEFT JOIN "MyLastTable" AS "table3" ON MyOtherTable.ID = MyLastTable.ID', - $query->sql($parameters) + $sql ); } @@ -551,6 +615,8 @@ public function testJoinSubSelect() 'Mlt' ); $query->setOrderBy('COALESCE(Mlt.MyLastTableCount, 0) DESC'); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT *, COALESCE(Mlt.MyLastTableCount, 0) AS "_SortColumn0" FROM MyTable ' . @@ -558,7 +624,7 @@ public function testJoinSubSelect() 'LEFT JOIN (SELECT MyLastTable.MyOtherTableID, COUNT(1) as MyLastTableCount FROM MyLastTable ' . 'GROUP BY MyOtherTableID) AS "Mlt" ON Mlt.MyOtherTableID = Mot.ID ' . 'ORDER BY "_SortColumn0" DESC', - $query->sql($parameters) + $sql ); } @@ -573,7 +639,11 @@ public function testSetWhereAny() 'Color' => 'Brown' ] ); - $sql = $query->sql($parameters); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $parameters = []; + $sql = $this->executeInEnvironmentType('live', function () use (&$parameters, $query) { + return $query->sql($parameters); + }); $this->assertSQLEquals("SELECT * FROM MyTable WHERE ((Monkey = ?) OR (Color = ?))", $sql); $this->assertEquals(['Chimp', 'Brown'], $parameters); } @@ -766,10 +836,9 @@ public function testSelect() { $query = new SQLSelect('"Title"', '"MyTable"'); $query->addSelect('"TestField"'); - $this->assertSQLEquals( - 'SELECT "Title", "TestField" FROM "MyTable"', - $query->sql() - ); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT "Title", "TestField" FROM "MyTable"', $sql); // Test replacement of select $query->setSelect( @@ -778,10 +847,9 @@ public function testSelect() 'AnotherAlias' => '"AnotherField"' ] ); - $this->assertSQLEquals( - 'SELECT "Field", "AnotherField" AS "AnotherAlias" FROM "MyTable"', - $query->sql() - ); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT "Field", "AnotherField" AS "AnotherAlias" FROM "MyTable"', $sql); // Check that ' as ' selects don't get mistaken as aliases $query->addSelect( @@ -789,17 +857,21 @@ public function testSelect() 'Relevance' => "MATCH (Title, MenuTitle) AGAINST ('Two as One')" ] ); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT "Field", "AnotherField" AS "AnotherAlias", MATCH (Title, MenuTitle) AGAINST (' . '\'Two as One\') AS "Relevance" FROM "MyTable"', - $query->sql() + $sql ); } public function testSelectWithNoTable() { $query = new SQLSelect('200'); - $this->assertSQLEquals('SELECT 200 AS "200"', $query->sql()); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); + $this->assertSQLEquals('SELECT 200 AS "200"', $sql); $this->assertSame([['200' => 200]], iterator_to_array($query->execute(), true)); } @@ -852,6 +924,12 @@ public function testParameterisedJoinSQL($joinMethod, $joinType) ['%MyName%'] ); $query->addWhere(['"SQLSelectTest_DO"."Date" > ?' => '2012-08-08 12:00']); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $parameters = []; + $sql = $this->executeInEnvironmentType('live', function () use (&$parameters, $query) { + return $query->sql($parameters); + }); $this->assertSQLEquals( 'SELECT "SQLSelectTest_DO"."Name", "SubSelect"."Count" @@ -859,7 +937,7 @@ public function testParameterisedJoinSQL($joinMethod, $joinType) GROUP BY "Title" HAVING "Title" NOT LIKE ?) AS "SubSelect" ON "SQLSelectTest_DO"."Name" = "SubSelect"."Title" WHERE ("SQLSelectTest_DO"."Date" > ?)', - $query->sql($parameters) + $sql ); $this->assertEquals(['%MyName%', '2012-08-08 12:00'], $parameters); $query->execute(); @@ -918,18 +996,21 @@ public function testUnion(SQLSelect $unionQuery, ?string $type, string|array $ex public function testBaseTableAliases() { $query = SQLSelect::create('*', ['"MyTableAlias"' => '"MyTable"']); - $sql = $query->sql(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals('SELECT * FROM "MyTable" AS "MyTableAlias"', $sql); $query = SQLSelect::create('*', ['MyTableAlias' => '"MyTable"']); - $sql = $query->sql(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals('SELECT * FROM "MyTable" AS "MyTableAlias"', $sql); $query = SQLSelect::create('*', ['"MyTableAlias"' => '"MyTable"']); $query->addLeftJoin('OtherTable', '"Thing" = "OtherThing"', 'OtherTableAlias'); - $sql = $query->sql(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT * @@ -945,7 +1026,8 @@ public function testBaseTableAliases() 'MyTableAlias' => '"MyTable"', 'explicitAlias' => '(SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin"' ]); - $sql = $query->sql(); + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $sql = $this->executeInEnvironmentType('live', fn () => $query->sql()); $this->assertSQLEquals( 'SELECT * FROM "MyTable" AS "MyTableAlias", ' . diff --git a/tests/php/ORM/SQLUpdateTest.php b/tests/php/ORM/SQLUpdateTest.php index 69af48638e1..3a8f24ac038 100644 --- a/tests/php/ORM/SQLUpdateTest.php +++ b/tests/php/ORM/SQLUpdateTest.php @@ -32,7 +32,12 @@ public function testBasicUpdate() ->setTable('"SQLUpdateTestBase"') ->assign('"Description"', 'Description 1a') ->addWhere(['"Title" = ?' => 'Object 1']); - $sql = $query->sql($parameters); + + // Must be tested in live mode, since dev mode adds SQL comments to the SQL output. + $parameters = []; + $sql = $this->executeInEnvironmentType('live', function () use (&$parameters, $query) { + return $query->sql($parameters); + }); // Check SQL $this->assertSQLEquals('UPDATE "SQLUpdateTestBase" SET "Description" = ? WHERE ("Title" = ?)', $sql);