Skip to content

Commit

Permalink
Fluent: add Lateral subqueries
Browse files Browse the repository at this point in the history
  • Loading branch information
forrest79 committed Dec 29, 2023
1 parent 15a5ac5 commit 0a1378f
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 27 deletions.
51 changes: 27 additions & 24 deletions docs/fluent.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ parameters:
# === PHPStan imperfection ===

-
message: "#^Property Forrest79\\\\PhPgSql\\\\Fluent\\\\Query::\\$params \\(array\\{select: array\\<int\\|string, BackedEnum\\|Forrest79\\\\PhPgSql\\\\Db\\\\Sql\\|Forrest79\\\\PhPgSql\\\\Fluent\\\\Query\\|int\\|string\\>, distinct: bool, tables: array\\<string, array\\{string, string\\}\\>, table\\-types: array\\{main: string\\|null, from: list\\<string\\>, joins: list\\<string\\>\\}, join\\-conditions: array\\<string, Forrest79\\\\PhPgSql\\\\Fluent\\\\Complex\\>, where: Forrest79\\\\PhPgSql\\\\Fluent\\\\Complex\\|null, groupBy: array\\<string\\>, having: Forrest79\\\\PhPgSql\\\\Fluent\\\\Complex\\|null, \\.\\.\\.\\}\\) does not accept .+\\.$#"
message: "#^Property Forrest79\\\\PhPgSql\\\\Fluent\\\\Query::\\$params \\(array\\{select: array\\<int\\|string, BackedEnum\\|Forrest79\\\\PhPgSql\\\\Db\\\\Sql\\|Forrest79\\\\PhPgSql\\\\Fluent\\\\Query\\|int\\|string\\>, distinct: bool, tables: array\\<string, array\\{string, string\\}\\>, table\\-types: array\\{main: string\\|null, from: list\\<string\\>, joins: list\\<string\\>\\}, join\\-conditions: array\\<string, Forrest79\\\\PhPgSql\\\\Fluent\\\\Complex\\>, lateral\\-tables: array\\<string, string\\>, where: Forrest79\\\\PhPgSql\\\\Fluent\\\\Complex\\|null, groupBy: array\\<string\\>, \\.\\.\\.\\}\\) does not accept .+\\.$#"
path: src/Fluent/Query.php
count: 4

Expand Down
10 changes: 10 additions & 0 deletions src/Fluent/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ public function crossJoin($join, ?string $alias = NULL): Query
}


/**
* @return QueryExecute
* @throws Exceptions\QueryException
*/
public function lateral(string $alias): Query
{
return $this->createQuery()->lateral($alias);
}


/**
* @param string|Complex|Db\Sql $condition
* @param mixed ...$params
Expand Down
12 changes: 12 additions & 0 deletions src/Fluent/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Query implements Sql
public const PARAM_TABLES = 'tables';
public const PARAM_TABLE_TYPES = 'table-types';
public const PARAM_JOIN_CONDITIONS = 'join-conditions';
public const PARAM_LATERAL_TABLES = 'lateral-tables';
public const PARAM_WHERE = 'where';
public const PARAM_GROUPBY = 'groupBy';
public const PARAM_HAVING = 'having';
Expand Down Expand Up @@ -59,6 +60,7 @@ class Query implements Sql
self::TABLE_TYPE_JOINS => [],
],
self::PARAM_JOIN_CONDITIONS => [],
self::PARAM_LATERAL_TABLES => [],
self::PARAM_WHERE => NULL,
self::PARAM_GROUPBY => [],
self::PARAM_HAVING => NULL,
Expand Down Expand Up @@ -318,6 +320,16 @@ public function on(string $alias, $condition, ...$params): self
}


/**
* @return static
*/
public function lateral(string $alias): self
{
$this->params[self::PARAM_LATERAL_TABLES][$alias] = $alias;
return $this;
}


/**
* @param string|Complex|Db\Sql $condition
* @param mixed ...$params
Expand Down
35 changes: 33 additions & 2 deletions src/Fluent/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,27 @@
use Forrest79\PhPgSql\Db;

/**
* @phpstan-type QueryParams array{select: array<int|string, string|int|\BackedEnum|Query|Db\Sql>, distinct: bool, tables: array<string, array{0: string, 1: string}>, table-types: array{main: string|NULL, from: list<string>, joins: list<string>}, join-conditions: array<string, Complex>, where: Complex|NULL, groupBy: array<string>, having: Complex|NULL, orderBy: array<string|Db\Sql|Query>, limit: int|NULL, offset: int|NULL, combine-queries: list<array{0: string|Query|Db\Sql, 1: string}>, insert-columns: array<string>, returning: array<int|string, string|int|Query|Db\Sql>, data: array<string, mixed>, rows: array<int, array<string, mixed>>, prefix: list<array<mixed>>, suffix: list<array<mixed>>}
* @phpstan-type QueryParams array{
* select: array<int|string, string|int|\BackedEnum|Query|Db\Sql>,
* distinct: bool,
* tables: array<string, array{0: string, 1: string}>,
* table-types: array{main: string|NULL, from: list<string>, joins: list<string>},
* join-conditions: array<string, Complex>,
* lateral-tables: array<string, string>,
* where: Complex|NULL,
* groupBy: array<string>,
* having: Complex|NULL,
* orderBy: array<string|Db\Sql|Query>,
* limit: int|NULL,
* offset: int|NULL,
* combine-queries: list<array{0: string|Query|Db\Sql, 1: string}>,
* insert-columns: array<string>,
* returning: array<int|string, string|int|Query|Db\Sql>,
* data: array<string, mixed>,
* rows: array<int, array<string, mixed>>,
* prefix: list<array<mixed>>,
* suffix: list<array<mixed>>
* }
*/
class QueryBuilder
{
Expand Down Expand Up @@ -90,6 +110,7 @@ private function createInsert(array $queryParams, array &$params): string
'INTO',
$mainTable,
$mainTableAlias,
FALSE,
$params
);

Expand Down Expand Up @@ -195,6 +216,7 @@ private function createUpdate(array $queryParams, array &$params): string
NULL,
$mainTable,
$mainTableAlias,
FALSE,
$params
) . ' SET ' . \implode(', ', $set) .
$this->getFrom($queryParams, $params, FALSE) .
Expand All @@ -218,6 +240,7 @@ private function createDelete(array $queryParams, array &$params): string
'FROM',
$mainTable,
$mainTableAlias,
FALSE,
$params
) .
$this->getWhere($queryParams, $params) .
Expand Down Expand Up @@ -296,6 +319,7 @@ private function getFrom(array $queryParams, array &$params, bool $useMainTable
NULL,
$queryParams[Query::PARAM_TABLES][$mainTableAlias][self::TABLE_NAME],
$mainTableAlias,
isset($queryParams[Query::PARAM_LATERAL_TABLES][$mainTableAlias]),
$params
);
}
Expand All @@ -306,6 +330,7 @@ private function getFrom(array $queryParams, array &$params, bool $useMainTable
NULL,
$queryParams[Query::PARAM_TABLES][$tableAlias][self::TABLE_NAME],
$tableAlias,
isset($queryParams[Query::PARAM_LATERAL_TABLES][$tableAlias]),
$params
);
}
Expand Down Expand Up @@ -339,6 +364,7 @@ private function getJoins(array $queryParams, array &$params): string
$joinType,
$queryParams[Query::PARAM_TABLES][$tableAlias][self::TABLE_NAME],
$tableAlias,
isset($queryParams[Query::PARAM_LATERAL_TABLES][$tableAlias]),
$params
);

Expand Down Expand Up @@ -596,7 +622,7 @@ final protected function getMainTableMetadata(array $queryParams): array
* @param string|Db\Sql|Query $table
* @param list<mixed> $params
*/
private function processTable(?string $type, $table, string $alias, array &$params): string
private function processTable(?string $type, $table, string $alias, bool $isLateral, array &$params): string
{
if ($table instanceof Db\Sql) {
$params[] = $table;
Expand All @@ -609,6 +635,11 @@ private function processTable(?string $type, $table, string $alias, array &$para
$params[] = $table->createSqlQuery();
$table = '(?)';
}

if ($isLateral) {
$table = 'LATERAL ' . $table;
}

return (($type === NULL) ? '' : ($type . ' ')) . ($table === $alias ? $table : ($table . ' AS ' . $alias));
}

Expand Down
3 changes: 3 additions & 0 deletions src/Fluent/Sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ function crossJoin($join, ?string $alias = NULL): Query;
function on(string $alias, $condition, ...$params): Query;


function lateral(string $alias): Query;


/**
* @param string|Complex|Db\Sql $condition
* @param mixed ...$params
Expand Down
45 changes: 45 additions & 0 deletions tests/Unit/FluentQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,51 @@ public function testJoinNoOn(): void
}


public function testLateralFrom(): void
{
$query = $this->query()
->select(['t1.column1', 't2.column2'])
->from('table1', 't1')
->from($this->query()->select(['column2'])->from('table2'), 't2')
->lateral('t2')
->createSqlQuery()
->createQuery();

Tester\Assert::same('SELECT t1.column1, t2.column2 FROM table1 AS t1, LATERAL (SELECT column2 FROM table2) AS t2', $query->getSql());
Tester\Assert::same([], $query->getParams());
}


public function testLateralJoinWithSqlQuery(): void
{
$query = $this->query()
->select(['column1' => 't1.column', 'column2' => 't2.column'])
->from('table1', 't1')
->join(new Db\Sql\Query('SELECT column FROM table2'), 't2', 't2.column = t1.column')
->lateral('t2')
->createSqlQuery()
->createQuery();

Tester\Assert::same('SELECT t1.column AS "column1", t2.column AS "column2" FROM table1 AS t1 INNER JOIN LATERAL (SELECT column FROM table2) AS t2 ON t2.column = t1.column', $query->getSql());
Tester\Assert::same([], $query->getParams());
}


public function testLateralJoinWithFluentQuery(): void
{
$query = $this->query()
->select(['column1' => 't1.column', 'column2' => 't2.column'])
->from('table1', 't1')
->join($this->query()->select(['column'])->from('table2'), 't2', 't2.column = t1.column')
->lateral('t2')
->createSqlQuery()
->createQuery();

Tester\Assert::same('SELECT t1.column AS "column1", t2.column AS "column2" FROM table1 AS t1 INNER JOIN LATERAL (SELECT column FROM table2) AS t2 ON t2.column = t1.column', $query->getSql());
Tester\Assert::same([], $query->getParams());
}


public function testSelectCombine(): void
{
$query = $this->query()
Expand Down

0 comments on commit 0a1378f

Please sign in to comment.