diff --git a/Changelog.md b/Changelog.md index 521c4a2..d615c7b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,13 +4,14 @@ ### Added -Support new syntax of PostgreSQL 16 (as of beta 3) +Support for new syntax of PostgreSQL 16 (as of beta 3) * SQL/JSON functions and expressions: * `IS JSON` predicate represented by `nodes\expressions\IsJsonExpression`; * Aggregate functions `json_arrayagg()` and `json_objectagg()` represented by `nodes\json\JsonArrayAgg` and `nodes\json\JsonObjectAgg`; * Constructor functions `json_array()` and `json_object()` represented by `nodes\json\JsonArrayValueList`, `nodes\json\JsonArraySubselect`, `nodes\json\JsonObject` classes. + * Aliases for subqueries in `FROM` are now optional. * `SYSTEM_USER` server variable backed by `nodes\expressions\SQLValueFunction`. * `[NO] INDENT` option for `XMLSERIALIZE()` expression. diff --git a/src/sad_spirit/pg_builder/Parser.php b/src/sad_spirit/pg_builder/Parser.php index 3519267..79efbf0 100644 --- a/src/sad_spirit/pg_builder/Parser.php +++ b/src/sad_spirit/pg_builder/Parser.php @@ -3582,17 +3582,11 @@ protected function TableReference(): nodes\range\FromElement protected function RangeSubselect(): nodes\range\Subselect { - $token = $this->stream->getCurrent(); $reference = new nodes\range\Subselect($this->SelectWithParentheses()); - if (!($alias = $this->OptionalAliasClause())) { - throw exceptions\SyntaxException::atPosition( - 'Subselects in FROM clause should have an alias', - $this->stream->getSource(), - $token->getPosition() - ); + if (null !== ($alias = $this->OptionalAliasClause())) { + $reference->setAlias($alias[0], $alias[1]); } - $reference->setAlias($alias[0], $alias[1]); return $reference; } diff --git a/src/sad_spirit/pg_builder/SqlBuilderWalker.php b/src/sad_spirit/pg_builder/SqlBuilderWalker.php index 06c03c4..38d80ff 100644 --- a/src/sad_spirit/pg_builder/SqlBuilderWalker.php +++ b/src/sad_spirit/pg_builder/SqlBuilderWalker.php @@ -1166,6 +1166,9 @@ public function walkColumnDefinition(nodes\range\ColumnDefinition $node): string protected function getFromItemAliases(nodes\range\FromElement $rangeItem): string { + if (null === $rangeItem->tableAlias && null === $rangeItem->columnAliases) { + return ''; + } return ' as' . (null !== $rangeItem->tableAlias ? ' ' . $rangeItem->tableAlias->dispatch($this) : '') . ( @@ -1179,11 +1182,7 @@ public function walkRangeFunctionCall(nodes\range\FunctionCall $rangeItem): stri { return ($rangeItem->lateral ? 'lateral ' : '') . $rangeItem->function->dispatch($this) . ($rangeItem->withOrdinality ? ' with ordinality' : '') - . ( - null !== $rangeItem->tableAlias || null !== $rangeItem->columnAliases - ? $this->getFromItemAliases($rangeItem) - : '' - ); + . $this->getFromItemAliases($rangeItem); } public function walkRowsFrom(nodes\range\RowsFrom $rangeItem): string @@ -1191,11 +1190,7 @@ public function walkRowsFrom(nodes\range\RowsFrom $rangeItem): string return ($rangeItem->lateral ? 'lateral ' : '') . 'rows from(' . implode(', ', $rangeItem->functions->dispatch($this)) . ')' . ($rangeItem->withOrdinality ? ' with ordinality' : '') - . ( - null !== $rangeItem->tableAlias || null !== $rangeItem->columnAliases - ? $this->getFromItemAliases($rangeItem) - : '' - ); + . $this->getFromItemAliases($rangeItem); } public function walkRowsFromElement(nodes\range\RowsFromElement $node): string @@ -1226,9 +1221,8 @@ public function walkJoinExpression(nodes\range\JoinExpression $rangeItem): strin $sql .= ' ' . $rangeItem->using->dispatch($this); } - return null !== $rangeItem->tableAlias || null !== $rangeItem->columnAliases - ? '(' . $sql . ')' . $this->getFromItemAliases($rangeItem) - : $sql; + $alias = $this->getFromItemAliases($rangeItem); + return '' === $alias ? $sql : '(' . $sql . ')' . $alias; } public function walkRelationReference(nodes\range\RelationReference $rangeItem): string @@ -1236,11 +1230,7 @@ public function walkRelationReference(nodes\range\RelationReference $rangeItem): return (false === $rangeItem->inherit ? 'only ' : '') . $rangeItem->name->dispatch($this) . (true === $rangeItem->inherit ? ' *' : '') - . ( - null !== $rangeItem->tableAlias || null !== $rangeItem->columnAliases - ? $this->getFromItemAliases($rangeItem) - : '' - ); + . $this->getFromItemAliases($rangeItem); } public function walkRangeSubselect(nodes\range\Subselect $rangeItem): string @@ -1354,13 +1344,10 @@ public function walkXmlTable(nodes\range\XmlTable $table): string $lines[] = $this->getIndent() . 'columns ' . implode($glue, $this->walkGenericNodeList($table->columns)); $this->indentLevel--; - $sql = implode($this->options['linebreak'] ?: ' ', $lines) - . $this->options['linebreak'] . $this->getIndent() . ')'; - if ($table->tableAlias || $table->columnAliases) { - $sql .= $this->getFromItemAliases($table); - } - return $sql; + return implode($this->options['linebreak'] ?: ' ', $lines) + . $this->options['linebreak'] . $this->getIndent() . ')' + . $this->getFromItemAliases($table); } public function walkXmlTypedColumnDefinition(nodes\xml\XmlTypedColumnDefinition $column): string diff --git a/tests/ParseFromClauseTest.php b/tests/ParseFromClauseTest.php index 3357516..74d8699 100644 --- a/tests/ParseFromClauseTest.php +++ b/tests/ParseFromClauseTest.php @@ -28,13 +28,13 @@ }; use sad_spirit\pg_builder\exceptions\SyntaxException; use sad_spirit\pg_builder\nodes\{ + Star, TargetElement, FunctionCall, ColumnReference, Identifier, TypeName, - QualifiedName -}; + QualifiedName}; use sad_spirit\pg_builder\nodes\expressions\{ KeywordConstant, NumericConstant, @@ -90,13 +90,17 @@ protected function setUp(): void public function testBasicItems(): void { $list = $this->parser->parseFromList(<<setAlias(new Identifier('quux')); + $select = new Select(new TargetList([new Star()])); + $select->from[] = new RelationReference(new QualifiedName('unaliased')); + $unaliased = new Subselect($select); + $this->assertEquals( new FromList([ new RelationReference(new QualifiedName('foo', 'bar')), @@ -104,7 +108,8 @@ public function testBasicItems(): void new QualifiedName('baz'), new FunctionArgumentList([new NumericConstant('1'), new StringConstant('string')]) )), - $subselect + $subselect, + $unaliased ]), $list ); @@ -247,13 +252,6 @@ public function testNoMoreThanTwoDots(): void $this->parser->parseFromList('foo.bar.baz.quux'); } - public function testSubselectsRequireAnAlias(): void - { - $this->expectException(SyntaxException::class); - $this->expectExceptionMessage('should have an alias'); - $this->parser->parseFromList("(select 'foo')"); - } - public function testWithOrdinality(): void { $list = $this->parser->parseFromList( diff --git a/tests/SqlBuilderTest.php b/tests/SqlBuilderTest.php index 3839ed8..219531d 100644 --- a/tests/SqlBuilderTest.php +++ b/tests/SqlBuilderTest.php @@ -198,6 +198,7 @@ public function testBuildSelectStatement(): void as three on xyzzy.id = three.xyzzy_id, some_function(1, 'two', array[3, 4]) with ordinality as sf (id integer, name text collate somecollation), (select five, six, seven from yetanothertable where id = $2) as ya, + (select * from unaliased), rows from (generate_series(1,5), generate_series(1,10) as (gs integer)) with ordinality, xyzzy as a (b,c) tablesample bernoulli (50) repeatable (seed), LATERAL XMLTABLE(