Skip to content

Instantly share code, notes, and snippets.

@StanAngeloff
Created May 28, 2015 15:17
Show Gist options
  • Save StanAngeloff/839e8c3306b64c138c2a to your computer and use it in GitHub Desktop.
Save StanAngeloff/839e8c3306b64c138c2a to your computer and use it in GitHub Desktop.
Query Expressions (short introduction)

Query Expressions

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.

Evaluation

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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment