Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .nix/pkgs/php-pg-query-ext/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
let
libpg_query = stdenv.mkDerivation {
pname = "libpg_query";
version = "17-6.1.0";
version = "17-latest";

src = fetchFromGitHub {
owner = "pganalyze";
repo = "libpg_query";
rev = "17-6.1.0";
hash = "sha256-UXba2WYyIO7RcFcNZeLL+Q9CwlloMZ5oFfHfL7+j4dU=";
rev = "03e2f436c999a1d22dbce439573e8cfabced5720"; # 17-latest branch as of 2025-11-28
hash = "sha256-0fnQF4KSIVpNqxzdvS0UtHnqUmLXgBKI/XRZjNrYLSo=";
};

buildPhase = ''
Expand All @@ -25,7 +25,7 @@ let
installPhase = ''
mkdir -p $out/lib $out/include
cp libpg_query.a $out/lib/
cp pg_query.h $out/include/
cp pg_query.h postgres_deparse.h $out/include/
'';
};

Expand Down
52 changes: 50 additions & 2 deletions documentation/components/extensions/pg-query-ext.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ This extension provides low-level functions for parsing PostgreSQL SQL queries.

## Features

- Parse PostgreSQL SQL queries into JSON AST
- Parse PostgreSQL SQL queries into JSON or protobuf AST
- Generate query fingerprints for query grouping
- Normalize SQL queries (replace literals with placeholders)
- Deparse AST back to SQL with optional pretty-printing
- Parse PL/pgSQL functions
- Split multiple SQL statements
- Scan SQL into tokens
- Generate query summaries for logging/monitoring

## Requirements

Expand Down Expand Up @@ -70,6 +72,9 @@ php -d extension=/path/to/pg_query.so your_script.php
$json = pg_query_parse('SELECT * FROM users WHERE id = 1');
$ast = json_decode($json, true);

// Parse SQL and return protobuf AST (more efficient for programmatic use)
$protobuf = pg_query_parse_protobuf('SELECT * FROM users WHERE id = 1');

// Generate fingerprint (same for structurally equivalent queries)
$fp = pg_query_fingerprint('SELECT * FROM users WHERE id = 1');
// Returns same fingerprint for: SELECT * FROM users WHERE id = 2
Expand All @@ -78,6 +83,9 @@ $fp = pg_query_fingerprint('SELECT * FROM users WHERE id = 1');
$normalized = pg_query_normalize("SELECT * FROM users WHERE name = 'John'");
// Returns: SELECT * FROM users WHERE name = $1

// Normalize utility/DDL statements
$normalized = pg_query_normalize_utility('CREATE TABLE users (id INT, name VARCHAR(255))');

// Split multiple statements
$statements = pg_query_split('SELECT 1; SELECT 2; SELECT 3');
// Returns: ['SELECT 1', ' SELECT 2', ' SELECT 3']
Expand All @@ -92,19 +100,59 @@ $plpgsql = pg_query_parse_plpgsql('
');

// Scan SQL into tokens (returns protobuf data)
$protobuf = pg_query_scan('SELECT 1');
$tokens = pg_query_scan('SELECT 1');

// Deparse protobuf AST back to SQL
$protobuf = pg_query_parse_protobuf('SELECT id, name FROM users WHERE active = true');
$sql = pg_query_deparse($protobuf);
// Returns: SELECT id, name FROM users WHERE active = true

// Deparse with pretty-printing options
$sql = pg_query_deparse_opts(
$protobuf,
true, // pretty_print
4, // indent_size
80, // max_line_length
false, // trailing_newline
false // commas_start_of_line
);
// Returns:
// SELECT id, name
// FROM users
// WHERE active = true

// Generate query summary (protobuf format, useful for logging)
$summary = pg_query_summary('SELECT * FROM users WHERE id = 1');
```

## Functions Reference

| Function | Description | Returns |
|----------|-------------|---------|
| `pg_query_parse(string $sql)` | Parse SQL to JSON AST | `string` (JSON) |
| `pg_query_parse_protobuf(string $sql)` | Parse SQL to protobuf AST | `string` (protobuf) |
| `pg_query_fingerprint(string $sql)` | Generate query fingerprint | `string\|false` |
| `pg_query_normalize(string $sql)` | Normalize query with placeholders | `string\|false` |
| `pg_query_normalize_utility(string $sql)` | Normalize DDL/utility statements | `string\|false` |
| `pg_query_parse_plpgsql(string $sql)` | Parse PL/pgSQL function | `string` (JSON) |
| `pg_query_split(string $sql)` | Split multiple statements | `array<string>` |
| `pg_query_scan(string $sql)` | Scan SQL into tokens | `string` (protobuf) |
| `pg_query_deparse(string $protobuf)` | Convert protobuf AST back to SQL | `string` |
| `pg_query_deparse_opts(...)` | Deparse with formatting options | `string` |
| `pg_query_summary(string $sql, int $options, int $truncate)` | Generate query summary | `string` (protobuf) |

### pg_query_deparse_opts Parameters

```php
pg_query_deparse_opts(
string $protobuf, // Protobuf AST from pg_query_parse_protobuf()
bool $pretty_print = false, // Enable pretty printing
int $indent_size = 4, // Spaces per indentation level
int $max_line_length = 80, // Maximum line length before wrapping
bool $trailing_newline = false, // Add trailing newline
bool $commas_start_of_line = false // Place commas at line start
): string
```

## Error Handling

Expand Down
78 changes: 77 additions & 1 deletion documentation/components/libs/pg-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,53 @@ $normalized = $parser->normalize("SELECT * FROM users WHERE name = 'John'");
$normalized = $parser->normalize('SELECT * FROM users WHERE id = :id');
// Returns: SELECT * FROM users WHERE id = $1

// Normalize utility/DDL statements
$normalized = $parser->normalizeUtility('CREATE TABLE users (id INT, name VARCHAR(255))');

// Split multiple statements
$statements = $parser->split('SELECT 1; SELECT 2;');
// Returns: ['SELECT 1', ' SELECT 2']

// Generate query summary (protobuf format, useful for logging)
$summary = $parser->summary('SELECT * FROM users WHERE id = 1');
```

## DSL Functions

```php
<?php

use function Flow\PgQuery\DSL\{pg_parse, pg_parser, pg_fingerprint, pg_normalize, pg_split};
use function Flow\PgQuery\DSL\{
pg_parse,
pg_parser,
pg_fingerprint,
pg_normalize,
pg_normalize_utility,
pg_split,
pg_deparse,
pg_deparse_options,
pg_format,
pg_summary
};

$query = pg_parse('SELECT * FROM users');
$parser = pg_parser();
$fingerprint = pg_fingerprint('SELECT * FROM users WHERE id = 1');
$normalized = pg_normalize('SELECT * FROM users WHERE id = 1');
$normalizedDdl = pg_normalize_utility('CREATE TABLE users (id INT)');
$statements = pg_split('SELECT 1; SELECT 2;');
$summary = pg_summary('SELECT * FROM users');

// Deparse (convert AST back to SQL)
$sql = pg_deparse($query); // Simple output
$sql = pg_deparse($query, pg_deparse_options()->indentSize(2)); // Pretty printed

// Format SQL (parse + deparse with pretty printing)
$formatted = pg_format('SELECT id,name FROM users WHERE active=true');
// Returns:
// SELECT id, name
// FROM users
// WHERE active = true
```

## ParsedQuery Methods
Expand All @@ -94,9 +124,55 @@ $statements = pg_split('SELECT 1; SELECT 2;');
| `tables()` | Get all tables referenced in the query | `array<Table>` |
| `columns(?string $tableName)` | Get columns, optionally filtered by table/alias | `array<Column>` |
| `functions()` | Get all function calls | `array<FunctionCall>` |
| `deparse(?DeparseOptions $options)` | Convert AST back to SQL string | `string` |
| `traverse(NodeVisitor ...$visitors)` | Traverse AST with custom visitors | `void` |
| `raw()` | Access underlying protobuf ParseResult | `ParseResult` |

## Deparsing (AST to SQL)

Convert a parsed query back to SQL, optionally with pretty-printing:

```php
<?php

use Flow\PgQuery\{DeparseOptions, Parser};

$parser = new Parser();
$query = $parser->parse('SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.active = true');

// Simple deparse (compact output)
$sql = $query->deparse();
// Returns: SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.active = true

// Pretty-printed output
$sql = $query->deparse(DeparseOptions::new());
// Returns:
// SELECT u.id, u.name
// FROM
// users u
// JOIN orders o ON u.id = o.user_id
// WHERE u.active = true

// Custom formatting options
$sql = $query->deparse(
DeparseOptions::new()
->indentSize(2) // 2 spaces per indent level
->maxLineLength(60) // Wrap at 60 characters
->trailingNewline() // Add newline at end
->commasStartOfLine() // Place commas at line start
);
```

### DeparseOptions

| Method | Description | Default |
|--------|-------------|---------|
| `prettyPrint(bool)` | Enable/disable pretty printing | `true` |
| `indentSize(int)` | Spaces per indentation level | `4` |
| `maxLineLength(int)` | Maximum line length before wrapping | `80` |
| `trailingNewline(bool)` | Add trailing newline at end | `false` |
| `commasStartOfLine(bool)` | Place commas at start of lines | `false` |

## Custom AST Traversal

For advanced use cases, you can traverse the AST with custom visitors:
Expand Down
2 changes: 1 addition & 1 deletion src/extension/pg-query-ext/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Flow PHP pg_query Extension Makefile
LIBPG_QUERY_VERSION := 17-6.1.0
LIBPG_QUERY_VERSION := 17-latest
LIBPG_QUERY_DIR := ./vendor/libpg_query
LIBPG_QUERY_LIB := $(LIBPG_QUERY_DIR)/libpg_query.a
EXTENSION_DIR := ./ext
Expand Down
2 changes: 1 addition & 1 deletion src/extension/pg-query-ext/ext/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if test "$PHP_PG_QUERY" != "no"; then
LIBPG_QUERY_VERSION="16-5.2.0"
;;
17|yes|"")
LIBPG_QUERY_VERSION="17-6.1.0"
LIBPG_QUERY_VERSION="17-latest"
PHP_PG_VERSION="17"
;;
*)
Expand Down
Loading