When crafting a query, a Query\Expression
instance can be used in the 'selectPrimary'
option. The expression wraps a string formatted according to a domain-specific language (DSL) for queries.
The primary PostgreSqlQueryBuilder
does not have any special knowledge what an Expression
is and how to parse it. Instead, the builder relies on visitors to extend existing functionality with new cases.
The visitor ExpressionQueryBuilderVisitor
is registered in the builder and is responsible for parsing the expression and adjusting the options of the query. When an Expression
instance is encountered in this visitor, the expression is parsed into an abstract syntax tree (AST). The original query is modified so that all columns which are mentioned in the expression are included to be SELECT'ed. E.g., the expression SUM("table"."column1" + "table"."column2")
will instruct the query builder to generate SQL code for "table"."column1"
and "table"."column2"
.
Once all columns have their SQL code generated, the expression is parsed once again and it's time to evaluate it. For this purpose a platform-specific class is instantiated. This will usually be PostgreSqlCodeGeneratorNodeVisitor
.
Note the latter is a class that implements NodeVisitorInterface
which is different from QueryBuilderVisitorInterface
. A NodeVisitorInterface
instance is responsible for traversing an AST.
The node visitor PostgreSqlCodeGeneratorNodeVisitor
traverses the AST and emits Query\Expression\CG\AbstractPart
instances as it encounters nodes. E.g., for a LiteralNode
the node visitor will emit a CG\ConstantPart
. In summary, the AST is converted to a different tree which represents the platform-specific code behind the expression.
A special case is encountered whenever a FunctionCallNode
is visited. The logic of handling such an instance is delegated to PostgreSqlFunctionsRegistry
. The registry, in turn, creates an instance of FunctionFile
which is a convenient wrapper around including external files.
The FunctionFile
class maps functions, e.g., MAX
to files on disk. In addition, it exports magic methods for creating CG\AbstractPart
instances. Example (MAX):
<?php
/**
* @file Resources/functions/MAX.php
*/
list ($expression) = $this->UnpackArguments('Expression');
return $this->AggregateFunctionCall('MAX', array($expression));
The result of including this file is an CG\AggregateFunctionCallPart
instance that wraps any arguments given to the function.
Once the AST is traversed and the code tree is generated as a result, the expression has to be processed further by ExpressionQueryBuilderVisitor
. When an expression contains an aggregate function call, we must add an implicit GROUP BY
for the rest of the columns not wrapped in aggregate functions. E.g., [MAX("table"."salary"), "table"."city"]
implies grouping over "table"."city"
.