Base node classes and interfaces

\sad_spirit\pg_builder\Node is an interface implemented by all AST nodes. Its descendant \sad_spirit\pg_builder\NodeList is a interface for nodes representing lists in SQL expressions (e.g. list of columns returned by a SELECT or list of tables in FROM).

Note that all relative class and interface names below assume \sad_spirit\pg_builder\ prefix, which is omitted for readability.

Node interface

namespace sad_spirit\pg_builder;

interface Node
{
    public function dispatch(TreeWalker $walker) : mixed;

    public function getParser() : ?Parser;

    public function setParentNode(?Node $parent) : void;
    public function getParentNode() : ?Node;

    public function replaceChild(Node $oldChild, Node $newChild) : ?Node;
    public function removeChild(Node $child) : ?Node;
}
dispatch()

This is a double-dispatch method supposed to call the method of TreeWalker relevant for the current Node.

getParser()

Returns the Parser, if one is available. It will usually be added to a Statement instance that contains the current Node. The GenericNode implementation of this just method calls getParser() of its parent Node.

Parent node references

setParentNode()

Adds the link to the Node containing current one.

getParentNode()

Returns the Node containing current one.

Nodes in the AST keep track of their parent nodes and can only be a child of one parent. This means that if you want to copy some node from one branch of the AST to the other, you actually need to clone it. Otherwise the result might be unexpected,

use sad_spirit\pg_builder\StatementFactory;

$factory = new StatementFactory();

/** @var \sad_spirit\pg_builder\SetOpSelect $select  */
$select = $factory->createFromString(
    'select foo_id, title, description, pub_date from foosourse union all select bar_id from barsource'
);
// let's copy the field list to the second argument of union
foreach ($select->left->list as $k => $v) {
    if ($k > 0) {
        $select->right->list[] = $v;
    }
}

echo $factory->createFromAST($select)->getSql();

will print

select foo_id
from foosourse
union all
select bar_id, title, description, pub_date
from barsource

If you change the assignment in the loop to $select->right->list[] = clone $v; the result will be

select foo_id, title, description, pub_date
from foosourse
union all
select bar_id, title, description, pub_date
from barsource

Handling “any child”

Usually you work with node’s children through exposed properties, but Node defines two special methods that allow working with any child:

replaceChild()

Replaces the child Node with another one.

removeChild()

Removes the child Node (more precisely, tries to store a null in a relevant property).

These methods are useful for applications that transform AST: e.g. when ParameterWalker instance needs to replace a node for a named parameter :foo with a node for a positional parameter $1 it just calls replaceChild() on the Parameter’s parent node. It doesn’t care about that node’s type and doesn’t know to what property of the parent the Parameter node is mapped.

nodes\ScalarExpression interface

namespace sad_spirit\pg_builder\nodes;

use sad_spirit\pg_builder\enums\ScalarExpressionAssociativity;
use sad_spirit\pg_builder\enums\ScalarExpressionPrecedence;
use sad_spirit\pg_builder\Node;

interface ScalarExpression extends Node
{
    public function getPrecedence() : ScalarExpressionPrecedence;
    public function getAssociativity() : ScalarExpressionAssociativity;
}

This is implemented by Nodes that are used in scalar expressions. It is widely used for type hints and defines methods used to properly add parentheses when generating SQL:

getPrecedence()

Returns the integer-backed value specifying relative precedence of this ScalarExpression.

getAssociativity()

Returns the associativity (left / right / non-associative) for this ScalarExpression.

nodes\FunctionLike interface

namespace sad_spirit\pg_builder\nodes;

use sad_spirit\pg_builder\Node;

interface FunctionLike extends Node
{
}

This interface is implemented by all Nodes that are considered functions in Postgres grammar. Those Nodes can be used instead of “normal” function calls in FROM clause, e.g.

select * from localtimestamp;

is allowed in Postgres and thus nodes\expressions\SQLValueFunction node backing localtimestamp expression implements FunctionLike.

nodes\GenericNode class

This abstract class is a default implementation of Node, it implements all its methods except dispatch(). All the node classes in pg_builder extend GenericNode.

Additionally, GenericNode implements the following magic methods:

__get() / __set() / __isset()

These allow access to child nodes as properties.

__clone()

This performs deep cloning of child nodes, which is needed for correct handling of parent node references

__serialize() / __unserialize()

These are needed to support caching of ASTs.

NodeList interface

namespace sad_spirit\pg_builder;

/**
 * @template TKey of array-key
 * @template T
 * @template TListInput
 */
interface NodeList extends Node, \ArrayAccess<TKey, T>, \Countable, \IteratorAggregate<TKey, T>
{
    public function merge(TListInput ...$lists) : void;
    public function replace(TListInput $list) : void;
}

Instances of NodeList behave like typed arrays / collections, allowing only objects of specific class or implementing specific interfaces as their elements.

Its additional methods are

merge()

Merges one or more lists with the current one.

replace()

Replaces the elements of the list with the given ones.

The TListInput template usually is a union type having a string as one of the options, those strings will be processed by Parser if one is available.

Parseable and ElementParseable interfaces

namespace sad_spirit\pg_builder;

interface Parseable
{
    public static function createFromString(Parser $parser, string $sql) : self;
}

/**
 * @template T of Node
 */
interface ElementParseable
{
    public function createElementFromString(string $sql) : T;
}

Classes implementing Parseable allow string arguments for merge() and replace() calls.

Classes implementing ElementParseable allow string arguments for offsetSet() and consequently for array offset assignment.

$select->list->merge('foo.id as foo_id, bar.title as bar_title');

$select->list[] = 'baz.*';

Tip

If you want to add several elements to the list at once, one merge() call with a string argument will be cheaper in terms of overhead than several assignments with string arguments.

nodes\lists\GenericNodeList class

This abstract class extending nodes\GenericNode is a default implementation of NodeList. It also updates methods of nodes\GenericNode to work with array offset as well as properties.

All the lists in the package, except nodes\lists\FunctionArgumentList, inherit from its subclass nodes\lists\NonAssociativeList which disallows non-numeric array keys.

Notable NodeList implementations

The following implementations of NodeList appear as properties of Statement objects, all of them implement Parseable and ElementParseable interfaces:

NodeList subclass

Allowed elements

nodes\lists\ExpressionList

objects implementing nodes\ScalarExpression

nodes\lists\FromList

instances of nodes\range\FromElement

nodes\group\GroupByClause

objects implementing nodes\ScalarExpression or nodes\group\GroupByElement

nodes\lists\LockList

instances of nodes\LockingElement

nodes\merge\MergeWhenList

instances of nodes\merge\MergeWhenClause

nodes\lists\OrderByList

instances of nodes\OrderByElement

nodes\lists\RowList

instances of nodes\expressions\RowExpression

nodes\lists\SetClauseList

instances of nodes\SingleSetClause or nodes\MultipleSetClause

nodes\lists\SetTargetList

instances of nodes\SetTargetElement

nodes\lists\TargetList

instances of nodes\TargetElement or nodes\Star

nodes\lists\WindowList

instances of nodes\WindowDefinition

nodes\WithClause

instances of nodes\CommonTableExpression