Skip to content

Commit 8142b6b

Browse files
author
flow-php
committed
1 parent f1e5daa commit 8142b6b

File tree

6 files changed

+143
-154
lines changed

6 files changed

+143
-154
lines changed

documentation/components/libs/pg-query/index.html

Lines changed: 138 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -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">&lt;?php
441-
442-
use Flow\PgQuery\Parser;
443-
444-
$parser = new Parser();
445-
446-
// Parse SQL into AST
447-
$result = $parser-&gt;parse('SELECT id, name FROM users WHERE active = true');
448-
449-
// Access the AST
450-
foreach ($result-&gt;getStmts() as $stmt) {
451-
$node = $stmt-&gt;getStmt();
452-
$selectStmt = $node-&gt;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">&lt;?php
458434

459435
use 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">&lt;?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-&gt;parse('SELECT id, name FROM users WHERE active = true ORDER BY name');
439+
// Get all tables
440+
foreach ($query-&gt;tables() as $table) {
441+
echo $table-&gt;name(); // 'users', 'orders'
442+
echo $table-&gt;alias(); // 'u', 'o'
443+
}
489444

490-
foreach ($result-&gt;getStmts() as $stmt) {
491-
$selectStmt = $stmt-&gt;getStmt()-&gt;getSelectStmt();
445+
// Get all columns
446+
foreach ($query-&gt;columns() as $column) {
447+
echo $column-&gt;name(); // 'id', 'name', 'id', 'user_id'
448+
echo $column-&gt;table(); // 'u', 'u', 'u', 'o'
449+
}
492450

493-
// Access FROM clause
494-
foreach ($selectStmt-&gt;getFromClause() as $fromItem) {
495-
$rangeVar = $fromItem-&gt;getRangeVar();
496-
echo "Table: " . $rangeVar-&gt;getRelname() . "\n";
497-
}
451+
// Get columns for specific table
452+
$userColumns = $query-&gt;columns('u');
498453

499-
// Access target list (SELECT columns)
500-
foreach ($selectStmt-&gt;getTargetList() as $target) {
501-
$columnRef = $target-&gt;getResTarget()-&gt;getVal()-&gt;getColumnRef();
502-
// Process column references...
503-
}
454+
// Get all function calls
455+
foreach ($query-&gt;functions() as $func) {
456+
echo $func-&gt;name(); // function name
457+
echo $func-&gt;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">&lt;?php
509462

510463
use Flow\PgQuery\Parser;
511464

512465
$parser = new Parser();
513466

514-
// These queries produce the same fingerprint
515-
$fp1 = $parser-&gt;fingerprint('SELECT * FROM users WHERE id = 1');
516-
$fp2 = $parser-&gt;fingerprint('SELECT * FROM users WHERE id = 999');
467+
// Parse SQL into ParsedQuery
468+
$query = $parser-&gt;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">&lt;?php
470+
// Generate fingerprint (same for structurally equivalent queries)
471+
$fingerprint = $parser-&gt;fingerprint('SELECT * FROM users WHERE id = 1');
523472

524-
use Flow\PgQuery\Parser;
473+
// Normalize query (replace literals with positional parameters)
474+
$normalized = $parser-&gt;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-&gt;normalize('SELECT * FROM users WHERE id = :id');
479+
// Returns: SELECT * FROM users WHERE id = $1
527480

528-
$normalized = $parser-&gt;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-&gt;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">&lt;?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-&gt;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&lt;Table&gt;</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&lt;Column&gt;</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&lt;string&gt;</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&lt;FunctionCall&gt;</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&lt;string&gt;</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">&lt;?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-&gt;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-&gt;traverse($counter);
567+
568+
echo $counter-&gt;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">&lt;?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-&gt;raw()-&gt;getStmts() as $stmt) {
604+
$select = $stmt-&gt;getStmt()-&gt;getSelectStmt();
605+
606+
// Access FROM clause
607+
foreach ($select-&gt;getFromClause() as $from) {
608+
echo $from-&gt;getRangeVar()-&gt;getRelname();
609+
}
610+
611+
// Access WHERE clause
612+
$where = $select-&gt;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">&lt;?php
628618

629619
use Flow\PgQuery\Parser;
630-
use Flow\PgQuery\Exception\ParserException;
631-
use Flow\PgQuery\Exception\ExtensionNotLoadedException;
620+
use Flow\PgQuery\Exception\{ParserException, ExtensionNotLoadedException};
632621

633622
try {
634623
$parser = new Parser();
@@ -637,7 +626,7 @@ <h2><a id="flow-php-exception-handling" href="#content-exception-handling" class
637626
}
638627

639628
try {
640-
$result = $parser-&gt;parse('INVALID SQL SYNTAX HERE');
629+
$parser-&gt;parse('INVALID SQL');
641630
} catch (ParserException $e) {
642631
echo "Parse error: " . $e-&gt;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"/>

sitemap.blog.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ><url><loc>https://flow-php.com/blog/2025-03-16/flow-php-release-cycle</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2025-01-25/data-processing-in-php</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2024-08-08/scalar-functions</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2024-04-04/building-custom-extractor-google-analytics</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url></urlset>
1+
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ><url><loc>https://flow-php.com/blog/2025-03-16/flow-php-release-cycle</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2025-01-25/data-processing-in-php</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2024-08-08/scalar-functions</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/blog/2024-04-04/building-custom-extractor-google-analytics</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url></urlset>

sitemap.default.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ><url><loc>https://flow-php.com/blog</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/documentation</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/changelog</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/</loc><lastmod>2025-11-27T03:42:28+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url></urlset>
1+
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ><url><loc>https://flow-php.com/blog</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/documentation</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/changelog</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://flow-php.com/</loc><lastmod>2025-11-27T13:15:27+00:00</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url></urlset>

sitemap.documentation.xml

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)