mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
ENH Allow selecting multiple (or no) tables (#10953)
This commit is contained in:
parent
b3b1d07616
commit
b28749db44
@ -242,9 +242,25 @@ class DBQueryBuilder
|
|||||||
public function buildFromFragment(SQLConditionalExpression $query, array &$parameters)
|
public function buildFromFragment(SQLConditionalExpression $query, array &$parameters)
|
||||||
{
|
{
|
||||||
$from = $query->getJoins($joinParameters);
|
$from = $query->getJoins($joinParameters);
|
||||||
|
$tables = [];
|
||||||
|
$joins = [];
|
||||||
|
|
||||||
|
// E.g. a naive "Select 1" statement is valid SQL
|
||||||
|
if (empty($from)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($from as $joinOrTable) {
|
||||||
|
if (preg_match(SQLConditionalExpression::getJoinRegex(), $joinOrTable)) {
|
||||||
|
$joins[] = $joinOrTable;
|
||||||
|
} else {
|
||||||
|
$tables[] = $joinOrTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$parameters = array_merge($parameters, $joinParameters);
|
$parameters = array_merge($parameters, $joinParameters);
|
||||||
$nl = $this->getSeparator();
|
$nl = $this->getSeparator();
|
||||||
return "{$nl}FROM " . implode(' ', $from);
|
return "{$nl}FROM " . implode(', ', $tables) . ' ' . implode(' ', $joins);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,7 +8,6 @@ namespace SilverStripe\ORM\Queries;
|
|||||||
*/
|
*/
|
||||||
abstract class SQLConditionalExpression extends SQLExpression
|
abstract class SQLConditionalExpression extends SQLExpression
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of WHERE clauses.
|
* An array of WHERE clauses.
|
||||||
*
|
*
|
||||||
@ -226,7 +225,7 @@ abstract class SQLConditionalExpression extends SQLExpression
|
|||||||
foreach ($this->from as $key => $tableClause) {
|
foreach ($this->from as $key => $tableClause) {
|
||||||
if (is_array($tableClause)) {
|
if (is_array($tableClause)) {
|
||||||
$table = '"' . $tableClause['table'] . '"';
|
$table = '"' . $tableClause['table'] . '"';
|
||||||
} elseif (is_string($tableClause) && preg_match('/JOIN +("[^"]+") +(AS|ON) +/i', $tableClause ?? '', $matches)) {
|
} elseif (is_string($tableClause) && preg_match(self::getJoinRegex(), $tableClause ?? '', $matches)) {
|
||||||
$table = $matches[1];
|
$table = $matches[1];
|
||||||
} else {
|
} else {
|
||||||
$table = $tableClause;
|
$table = $tableClause;
|
||||||
@ -325,11 +324,16 @@ abstract class SQLConditionalExpression extends SQLExpression
|
|||||||
return $from;
|
return $from;
|
||||||
}
|
}
|
||||||
|
|
||||||
// shift the first FROM table out from so we only deal with the JOINs
|
// Remove the regular FROM tables out so we only deal with the JOINs
|
||||||
reset($from);
|
$regularTables = [];
|
||||||
$baseFromAlias = key($from ?? []);
|
foreach ($from as $alias => $tableClause) {
|
||||||
$baseFrom = array_shift($from);
|
if (is_string($tableClause) && !preg_match(self::getJoinRegex(), $tableClause)) {
|
||||||
|
$regularTables[$alias] = $tableClause;
|
||||||
|
unset($from[$alias]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the joins
|
||||||
$this->mergesort($from, function ($firstJoin, $secondJoin) {
|
$this->mergesort($from, function ($firstJoin, $secondJoin) {
|
||||||
if (!is_array($firstJoin)
|
if (!is_array($firstJoin)
|
||||||
|| !is_array($secondJoin)
|
|| !is_array($secondJoin)
|
||||||
@ -341,11 +345,14 @@ abstract class SQLConditionalExpression extends SQLExpression
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Put the first FROM table back into the results
|
// Put the regular FROM tables back into the results
|
||||||
if (!empty($baseFromAlias) && !is_numeric($baseFromAlias)) {
|
$regularTables = array_reverse($regularTables, true);
|
||||||
$from = array_merge([$baseFromAlias => $baseFrom], $from);
|
foreach ($regularTables as $alias => $tableName) {
|
||||||
} else {
|
if (!empty($alias) && !is_numeric($alias)) {
|
||||||
array_unshift($from, $baseFrom);
|
$from = array_merge([$alias => $tableName], $from);
|
||||||
|
} else {
|
||||||
|
array_unshift($from, $tableName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $from;
|
return $from;
|
||||||
@ -773,4 +780,12 @@ abstract class SQLConditionalExpression extends SQLExpression
|
|||||||
$this->copyTo($update);
|
$this->copyTo($update);
|
||||||
return $update;
|
return $update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the regular expression pattern used to identify JOIN statements
|
||||||
|
*/
|
||||||
|
public static function getJoinRegex(): string
|
||||||
|
{
|
||||||
|
return '/JOIN +.*? +(AS|ON|USING\(?) +/i';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,9 @@ class SQLSelect extends SQLConditionalExpression
|
|||||||
$fields = [$fields];
|
$fields = [$fields];
|
||||||
}
|
}
|
||||||
foreach ($fields as $idx => $field) {
|
foreach ($fields as $idx => $field) {
|
||||||
|
if ($field === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$this->selectField($field, is_numeric($idx) ? null : $idx);
|
$this->selectField($field, is_numeric($idx) ? null : $idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,4 +716,10 @@ class SQLSelect extends SQLConditionalExpression
|
|||||||
$query->setLimit(1, $index);
|
$query->setLimit(1, $index);
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
// Empty if there's no select, or we're trying to select '*' but there's no FROM clause
|
||||||
|
return empty($this->select) || (empty($this->from) && array_key_exists('*', $this->select));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,19 +67,80 @@ class SQLSelectTest extends SapphireTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function provideIsEmpty()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect(),
|
||||||
|
'expected' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect(from: 'someTable'),
|
||||||
|
'expected' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect(''),
|
||||||
|
'expected' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect('', 'someTable'),
|
||||||
|
'expected' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect('column', 'someTable'),
|
||||||
|
'expected' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'query' => new SQLSelect('value'),
|
||||||
|
'expected' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideIsEmpty
|
||||||
|
*/
|
||||||
|
public function testIsEmpty(SQLSelect $query, $expected)
|
||||||
|
{
|
||||||
|
$this->assertSame($expected, $query->isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
public function testEmptyQueryReturnsNothing()
|
public function testEmptyQueryReturnsNothing()
|
||||||
{
|
{
|
||||||
$query = new SQLSelect();
|
$query = new SQLSelect();
|
||||||
$this->assertSQLEquals('', $query->sql($parameters));
|
$this->assertSQLEquals('', $query->sql($parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSelectFromBasicTable()
|
public function provideSelectFrom()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'from' => ['MyTable'],
|
||||||
|
'expected' => 'SELECT * FROM MyTable',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'from' => ['MyTable', 'MySecondTable'],
|
||||||
|
'expected' => 'SELECT * FROM MyTable, MySecondTable',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'from' => ['MyTable', 'INNER JOIN AnotherTable on AnotherTable.ID = MyTable.SomeFieldID'],
|
||||||
|
'expected' => 'SELECT * FROM MyTable INNER JOIN AnotherTable on AnotherTable.ID = MyTable.SomeFieldID',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'from' => ['MyTable', 'MySecondTable', 'INNER JOIN AnotherTable on AnotherTable.ID = MyTable.SomeFieldID'],
|
||||||
|
'expected' => 'SELECT * FROM MyTable, MySecondTable INNER JOIN AnotherTable on AnotherTable.ID = MyTable.SomeFieldID',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideSelectFrom
|
||||||
|
*/
|
||||||
|
public function testSelectFrom(array $from, string $expected)
|
||||||
{
|
{
|
||||||
$query = new SQLSelect();
|
$query = new SQLSelect();
|
||||||
$query->setFrom('MyTable');
|
$query->setFrom($from);
|
||||||
$this->assertSQLEquals("SELECT * FROM MyTable", $query->sql($parameters));
|
$this->assertSQLEquals($expected, $query->sql($parameters));
|
||||||
$query->addFrom('MyJoin');
|
|
||||||
$this->assertSQLEquals("SELECT * FROM MyTable MyJoin", $query->sql($parameters));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSelectFromUserSpecifiedFields()
|
public function testSelectFromUserSpecifiedFields()
|
||||||
@ -724,6 +785,13 @@ class SQLSelectTest extends SapphireTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSelectWithNoTable()
|
||||||
|
{
|
||||||
|
$query = new SQLSelect('200');
|
||||||
|
$this->assertSQLEquals('SELECT 200 AS "200"', $query->sql());
|
||||||
|
$this->assertSame([['200' => 200]], iterator_to_array($query->execute(), true));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test passing in a LIMIT with OFFSET clause string.
|
* Test passing in a LIMIT with OFFSET clause string.
|
||||||
*/
|
*/
|
||||||
@ -819,12 +887,12 @@ class SQLSelectTest extends SapphireTest
|
|||||||
// In SS4 the "explicitAlias" would be ignored
|
// In SS4 the "explicitAlias" would be ignored
|
||||||
$query = SQLSelect::create('*', [
|
$query = SQLSelect::create('*', [
|
||||||
'MyTableAlias' => '"MyTable"',
|
'MyTableAlias' => '"MyTable"',
|
||||||
'explicitAlias' => ', (SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin"'
|
'explicitAlias' => '(SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin"'
|
||||||
]);
|
]);
|
||||||
$sql = $query->sql();
|
$sql = $query->sql();
|
||||||
|
|
||||||
$this->assertSQLEquals(
|
$this->assertSQLEquals(
|
||||||
'SELECT * FROM "MyTable" AS "MyTableAlias" , ' .
|
'SELECT * FROM "MyTable" AS "MyTableAlias", ' .
|
||||||
'(SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin" AS "explicitAlias"',
|
'(SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin" AS "explicitAlias"',
|
||||||
$sql
|
$sql
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user