@@ -424,123 +424,76 @@ <h1><a id="flow-php-pg-query" href="#content-pg-query" class="mr-2" aria-hidden=
424424< li > < a href ="/documentation/introduction "> ⬅️️ Back</ a > </ li >
425425</ ul >
426426< p > PostgreSQL Query Parser library provides strongly-typed AST (Abstract Syntax Tree) parsing for PostgreSQL SQL queries using the < a rel ="noopener noreferrer " target ="_blank " href ="https://github.com/pganalyze/libpg_query "> libpg_query</ a > library through a PHP extension.</ p >
427- < p > This library wraps the low-level extension functions and provides:</ p >
428- < ul >
429- < li > Strongly-typed AST nodes generated from protobuf definitions</ li >
430- < li > A < code > Parser</ code > class for object-oriented access</ li >
431- < li > DSL helper functions for convenient usage</ li >
432- </ ul >
433427< h2 > < a id ="flow-php-requirements " href ="#content-requirements " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Requirements</ h2 >
434428< p > This library requires the < code > pg_query</ code > PHP extension. See < a href ="/documentation/components/extensions/pg-query-ext "> pg-query-ext documentation</ a > for installation instructions.</ p >
435429< h2 > < a id ="flow-php-installation " href ="#content-installation " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Installation</ h2 >
436430< pre class ="language-plain "> < code class ="language-plain " data-controller ="syntax-highlight "> composer require flow-php/pg-query:~0.28.0
437431</ code > </ pre >
438- < h2 > < a id ="flow-php-usage " href ="#content-usage " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Usage</ h2 >
439- < h3 > < a id ="flow-php-using-the-parser-class " href ="#content-using-the-parser-class " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Using the Parser Class</ h3 >
440- < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
441-
442- use Flow\PgQuery\Parser;
443-
444- $parser = new Parser();
445-
446- // Parse SQL into AST
447- $result = $parser->parse('SELECT id, name FROM users WHERE active = true');
448-
449- // Access the AST
450- foreach ($result->getStmts() as $stmt) {
451- $node = $stmt->getStmt();
452- $selectStmt = $node->getSelectStmt();
453- // Work with strongly-typed AST nodes...
454- }
455- </ code > </ pre >
456- < h3 > < a id ="flow-php-using-dsl-functions " href ="#content-using-dsl-functions " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Using DSL Functions</ h3 >
432+ < h2 > < a id ="flow-php-quick-start " href ="#content-quick-start " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Quick Start</ h2 >
457433< pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
458434
459435use function Flow\PgQuery\DSL\pg_parse;
460- use function Flow\PgQuery\DSL\pg_parser;
461- use function Flow\PgQuery\DSL\pg_fingerprint;
462- use function Flow\PgQuery\DSL\pg_normalize;
463- use function Flow\PgQuery\DSL\pg_split;
464-
465- // Parse SQL
466- $result = pg_parse('SELECT * FROM users');
467436
468- // Get a reusable parser instance
469- $parser = pg_parser();
470-
471- // Generate fingerprint
472- $fingerprint = pg_fingerprint('SELECT id FROM users WHERE id = 1');
473-
474- // Normalize query
475- $normalized = pg_normalize('SELECT * FROM users WHERE id = 1');
476-
477- // Split multiple statements
478- $statements = pg_split('SELECT 1; SELECT 2;');
479- </ code > </ pre >
480- < h2 > < a id ="flow-php-features " href ="#content-features " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Features</ h2 >
481- < h3 > < a id ="flow-php-query-parsing " href ="#content-query-parsing " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Query Parsing</ h3 >
482- < p > Parse PostgreSQL SQL into a strongly-typed AST:</ p >
483- < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
437+ $query = pg_parse('SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id');
484438
485- use Flow\PgQuery\Parser;
486-
487- $parser = new Parser();
488- $result = $parser->parse('SELECT id, name FROM users WHERE active = true ORDER BY name');
439+ // Get all tables
440+ foreach ($query->tables() as $table) {
441+ echo $table->name(); // 'users', 'orders'
442+ echo $table->alias(); // 'u', 'o'
443+ }
489444
490- foreach ($result->getStmts() as $stmt) {
491- $selectStmt = $stmt->getStmt()->getSelectStmt();
445+ // Get all columns
446+ foreach ($query->columns() as $column) {
447+ echo $column->name(); // 'id', 'name', 'id', 'user_id'
448+ echo $column->table(); // 'u', 'u', 'u', 'o'
449+ }
492450
493- // Access FROM clause
494- foreach ($selectStmt->getFromClause() as $fromItem) {
495- $rangeVar = $fromItem->getRangeVar();
496- echo "Table: " . $rangeVar->getRelname() . "\n";
497- }
451+ // Get columns for specific table
452+ $userColumns = $query->columns('u');
498453
499- // Access target list (SELECT columns)
500- foreach ($selectStmt->getTargetList() as $target) {
501- $columnRef = $target->getResTarget()->getVal()->getColumnRef();
502- // Process column references...
503- }
454+ // Get all function calls
455+ foreach ($query->functions() as $func) {
456+ echo $func->name(); // function name
457+ echo $func->schema(); // schema if qualified (e.g., 'pg_catalog')
504458}
505459</ code > </ pre >
506- < h3 > < a id ="flow-php-query-fingerprinting " href ="#content-query-fingerprinting " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Query Fingerprinting</ h3 >
507- < p > Generate unique fingerprints for structurally equivalent queries. This is useful for grouping similar queries regardless of their literal values:</ p >
460+ < h2 > < a id ="flow-php-parser-class " href ="#content-parser-class " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Parser Class</ h2 >
508461< pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
509462
510463use Flow\PgQuery\Parser;
511464
512465$parser = new Parser();
513466
514- // These queries produce the same fingerprint
515- $fp1 = $parser->fingerprint('SELECT * FROM users WHERE id = 1');
516- $fp2 = $parser->fingerprint('SELECT * FROM users WHERE id = 999');
467+ // Parse SQL into ParsedQuery
468+ $query = $parser->parse('SELECT * FROM users WHERE id = 1');
517469
518- var_dump($fp1 === $fp2); // true
519- </ code > </ pre >
520- < h3 > < a id ="flow-php-query-normalization " href ="#content-query-normalization " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Query Normalization</ h3 >
521- < p > Replace literal values with parameter placeholders:</ p >
522- < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
470+ // Generate fingerprint (same for structurally equivalent queries)
471+ $fingerprint = $parser->fingerprint('SELECT * FROM users WHERE id = 1');
523472
524- use Flow\PgQuery\Parser;
473+ // Normalize query (replace literals with positional parameters)
474+ $normalized = $parser->normalize("SELECT * FROM users WHERE name = 'John'");
475+ // Returns: SELECT * FROM users WHERE name = $1
525476
526- $parser = new Parser();
477+ // Normalize also handles Doctrine-style named parameters
478+ $normalized = $parser->normalize('SELECT * FROM users WHERE id = :id');
479+ // Returns: SELECT * FROM users WHERE id = $1
527480
528- $normalized = $parser->normalize("SELECT * FROM users WHERE name = 'John' AND age = 25");
529- // Returns: SELECT * FROM users WHERE name = $1 AND age = $2
481+ // Split multiple statements
482+ $statements = $parser->split('SELECT 1; SELECT 2;');
483+ // Returns: ['SELECT 1', ' SELECT 2']
530484</ code > </ pre >
531- < h3 > < a id ="flow-php-statement-splitting " href ="#content-statement-splitting " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Statement Splitting</ h3 >
532- < p > Split a string containing multiple SQL statements:</ p >
485+ < h2 > < a id ="flow-php-dsl-functions " href ="#content-dsl-functions " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > DSL Functions</ h2 >
533486< pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
534487
535- use Flow\PgQuery\Parser ;
488+ use function Flow\PgQuery\DSL\{pg_parse, pg_parser, pg_fingerprint, pg_normalize, pg_split} ;
536489
537- $parser = new Parser();
538-
539- $statements = $parser->split('SELECT 1; SELECT 2; SELECT 3');
540- // Returns: ['SELECT 1', ' SELECT 2', ' SELECT 3']
490+ $query = pg_parse('SELECT * FROM users');
491+ $parser = pg_parser();
492+ $fingerprint = pg_fingerprint('SELECT * FROM users WHERE id = 1');
493+ $normalized = pg_normalize('SELECT * FROM users WHERE id = 1');
494+ $statements = pg_split('SELECT 1; SELECT 2;');
541495</ code > </ pre >
542- < h2 > < a id ="flow-php-api-reference " href ="#content-api-reference " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > API Reference</ h2 >
543- < h3 > < a id ="flow-php-parser-class " href ="#content-parser-class " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Parser Class</ h3 >
496+ < h2 > < a id ="flow-php-parsedquery-methods " href ="#content-parsedquery-methods " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > ParsedQuery Methods</ h2 >
544497< table >
545498< thead >
546499< tr >
@@ -551,84 +504,120 @@ <h3><a id="flow-php-parser-class" href="#content-parser-class" class="mr-2" aria
551504</ thead >
552505< tbody >
553506< tr >
554- < td > < code > parse(string $sql )</ code > </ td >
555- < td > Parse SQL into AST </ td >
556- < td > < code > ParseResult </ code > </ td >
507+ < td > < code > tables( )</ code > </ td >
508+ < td > Get all tables referenced in the query </ td >
509+ < td > < code > array<Table> </ code > </ td >
557510</ tr >
558511< tr >
559- < td > < code > fingerprint( string $sql )</ code > </ td >
560- < td > Generate query fingerprint </ td >
561- < td > < code > ?string </ code > </ td >
512+ < td > < code > columns(? string $tableName )</ code > </ td >
513+ < td > Get columns, optionally filtered by table/alias </ td >
514+ < td > < code > array<Column> </ code > </ td >
562515</ tr >
563516< tr >
564- < td > < code > normalize(string $sql)</ code > </ td >
565- < td > Normalize query with placeholders</ td >
566- < td > < code > ?string</ code > </ td >
567- </ tr >
568- < tr >
569- < td > < code > split(string $sql)</ code > </ td >
570- < td > Split multiple statements</ td >
571- < td > < code > array<string></ code > </ td >
572- </ tr >
573- </ tbody >
574- </ table >
575- < h3 > < a id ="flow-php-dsl-functions " href ="#content-dsl-functions " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > DSL Functions</ h3 >
576- < table >
577- < thead >
578- < tr >
579- < th > Function</ th >
580- < th > Description</ th >
581- < th > Returns</ th >
517+ < td > < code > functions()</ code > </ td >
518+ < td > Get all function calls</ td >
519+ < td > < code > array<FunctionCall></ code > </ td >
582520</ tr >
583- </ thead >
584- < tbody >
585521< tr >
586- < td > < code > pg_parser( )</ code > </ td >
587- < td > Create a new Parser instance </ td >
588- < td > < code > Parser </ code > </ td >
522+ < td > < code > traverse(NodeVisitor ...$visitors )</ code > </ td >
523+ < td > Traverse AST with custom visitors </ td >
524+ < td > < code > void </ code > </ td >
589525</ tr >
590526< tr >
591- < td > < code > pg_parse(string $sql )</ code > </ td >
592- < td > Parse SQL into AST </ td >
527+ < td > < code > raw( )</ code > </ td >
528+ < td > Access underlying protobuf ParseResult </ td >
593529< td > < code > ParseResult</ code > </ td >
594530</ tr >
595- < tr >
596- < td > < code > pg_fingerprint(string $sql)</ code > </ td >
597- < td > Generate query fingerprint</ td >
598- < td > < code > ?string</ code > </ td >
599- </ tr >
600- < tr >
601- < td > < code > pg_normalize(string $sql)</ code > </ td >
602- < td > Normalize query</ td >
603- < td > < code > ?string</ code > </ td >
604- </ tr >
605- < tr >
606- < td > < code > pg_split(string $sql)</ code > </ td >
607- < td > Split statements</ td >
608- < td > < code > array<string></ code > </ td >
609- </ tr >
610531</ tbody >
611532</ table >
612- < h2 > < a id ="flow-php-ast-node-types " href ="#content-ast-node-types " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > AST Node Types</ h2 >
613- < p > The library includes 343 strongly-typed AST node classes generated from PostgreSQL's protobuf definitions. All classes are in the < code > Flow\PgQuery\Protobuf\AST</ code > namespace.</ p >
614- < p > Common node types include:</ p >
533+ < h2 > < a id ="flow-php-custom-ast-traversal " href ="#content-custom-ast-traversal " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Custom AST Traversal</ h2 >
534+ < p > For advanced use cases, you can traverse the AST with custom visitors:</ p >
535+ < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
536+
537+ use Flow\PgQuery\AST\NodeVisitor;
538+ use Flow\PgQuery\Protobuf\AST\ColumnRef;
539+
540+ use function Flow\PgQuery\DSL\pg_parse;
541+
542+ class ColumnCounter implements NodeVisitor
543+ {
544+ public int $count = 0;
545+
546+ public static function nodeClass(): string
547+ {
548+ return ColumnRef::class;
549+ }
550+
551+ public function enter(object $node): ?int
552+ {
553+ $this->count++;
554+ return null;
555+ }
556+
557+ public function leave(object $node): ?int
558+ {
559+ return null;
560+ }
561+ }
562+
563+ $query = pg_parse('SELECT id, name, email FROM users');
564+
565+ $counter = new ColumnCounter();
566+ $query->traverse($counter);
567+
568+ echo $counter->count; // 3
569+ </ code > </ pre >
570+ < h3 > < a id ="flow-php-nodevisitor-interface " href ="#content-nodevisitor-interface " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > NodeVisitor Interface</ h3 >
571+ < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> interface NodeVisitor
572+ {
573+ public const DONT_TRAVERSE_CHILDREN = 1;
574+ public const STOP_TRAVERSAL = 2;
575+
576+ /** @return class-string */
577+ public static function nodeClass(): string;
578+
579+ public function enter(object $node): ?int;
580+ public function leave(object $node): ?int;
581+ }
582+ </ code > </ pre >
583+ < p > Visitors declare which node type they handle via < code > nodeClass()</ code > . Return values:</ p >
584+ < ul >
585+ < li > < code > null</ code > - continue traversal</ li >
586+ < li > < code > DONT_TRAVERSE_CHILDREN</ code > - skip children (from < code > enter()</ code > only)</ li >
587+ < li > < code > STOP_TRAVERSAL</ code > - stop entire traversal</ li >
588+ </ ul >
589+ < h3 > < a id ="flow-php-built-in-visitors " href ="#content-built-in-visitors " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Built-in Visitors</ h3 >
615590< ul >
616- < li > < code > SelectStmt</ code > - SELECT statement</ li >
617- < li > < code > InsertStmt</ code > - INSERT statement</ li >
618- < li > < code > UpdateStmt</ code > - UPDATE statement</ li >
619- < li > < code > DeleteStmt</ code > - DELETE statement</ li >
620- < li > < code > ColumnRef</ code > - Column reference</ li >
621- < li > < code > A_Expr</ code > - Expression node</ li >
622- < li > < code > FuncCall</ code > - Function call</ li >
623- < li > < code > JoinExpr</ code > - JOIN expression</ li >
624- < li > < code > RangeVar</ code > - Table/view reference</ li >
591+ < li > < code > ColumnRefCollector</ code > - collects all < code > ColumnRef</ code > nodes</ li >
592+ < li > < code > FuncCallCollector</ code > - collects all < code > FuncCall</ code > nodes</ li >
593+ < li > < code > RangeVarCollector</ code > - collects all < code > RangeVar</ code > nodes</ li >
625594</ ul >
595+ < h2 > < a id ="flow-php-raw-ast-access " href ="#content-raw-ast-access " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Raw AST Access</ h2 >
596+ < p > For full control, access the protobuf AST directly:</ p >
597+ < pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
598+
599+ use function Flow\PgQuery\DSL\pg_parse;
600+
601+ $query = pg_parse('SELECT id FROM users WHERE active = true');
602+
603+ foreach ($query->raw()->getStmts() as $stmt) {
604+ $select = $stmt->getStmt()->getSelectStmt();
605+
606+ // Access FROM clause
607+ foreach ($select->getFromClause() as $from) {
608+ echo $from->getRangeVar()->getRelname();
609+ }
610+
611+ // Access WHERE clause
612+ $where = $select->getWhereClause();
613+ // ...
614+ }
615+ </ code > </ pre >
626616< h2 > < a id ="flow-php-exception-handling " href ="#content-exception-handling " class ="mr-2 " aria-hidden ="true " title ="Permalink "> #</ a > Exception Handling</ h2 >
627617< pre class ="language-php "> < code class ="language-php " data-controller ="syntax-highlight "> <?php
628618
629619use Flow\PgQuery\Parser;
630- use Flow\PgQuery\Exception\ParserException;
631- use Flow\PgQuery\Exception\ExtensionNotLoadedException;
620+ use Flow\PgQuery\Exception\{ParserException, ExtensionNotLoadedException};
632621
633622try {
634623 $parser = new Parser();
@@ -637,7 +626,7 @@ <h2><a id="flow-php-exception-handling" href="#content-exception-handling" class
637626}
638627
639628try {
640- $result = $ parser->parse('INVALID SQL SYNTAX HERE ');
629+ $parser->parse('INVALID SQL');
641630} catch (ParserException $e) {
642631 echo "Parse error: " . $e->getMessage();
643632}
@@ -646,7 +635,7 @@ <h2><a id="flow-php-performance" href="#content-performance" class="mr-2" aria-h
646635< p > For optimal protobuf parsing performance, install the < code > ext-protobuf</ code > PHP extension:</ p >
647636< pre class ="language-bash "> < code class ="language-bash " data-controller ="syntax-highlight "> pecl install protobuf
648637</ code > </ pre >
649- < p > The library will work without it using the pure PHP implementation from < code > google/protobuf</ code > , but the native extension provides significantly better performance for AST deserialization .</ p >
638+ < p > The library works without it using the pure PHP implementation from < code > google/protobuf</ code > , but the native extension provides significantly better performance.</ p >
650639
651640
652641 < hr class ="!mt-8 !mb-8 "/>
0 commit comments