pg-parser-rs

Crates.iopg-parser-rs
lib.rspg-parser-rs
version0.0.2
created_at2026-01-25 13:14:37.581837+00
updated_at2026-01-25 14:09:24.007836+00
descriptionPostgreSQL SQL parser based on tree-sitter, with a Rust AST layer
homepagehttps://github.com/caicancai/tree-sitter-pg
repositoryhttps://github.com/caicancai/tree-sitter-pg
max_upload_size
id2068703
size64,067,212
Cancai Cai (caicancai)

documentation

https://docs.rs/pg-parser-rs

README

pg-parser-rs

PostgreSQL-flavored SQL parser based on tree-sitter, with a Rust AST layer.

What it does

  • Exposes the SQL Language for tree-sitter
  • Provides a simple parse helper and a PgParser wrapper
  • Builds a PG-flavored AST for SELECT/INSERT/UPDATE/DELETE
  • Includes tests over the examples/ corpus

Usage (Rust)

let mut parser = tree_sitter::Parser::new();
parser.set_language(&pg_parser_rs::language())?;
let tree = parser.parse("SELECT 1;", None).unwrap();

Or use the wrapper for a simpler API:

let mut parser = pg_parser_rs::PgParser::new()?;
let tree = parser.parse("SELECT 1;").unwrap();

AST

let queries = pg_parser_rs::parse_statements("SELECT 1;");

Architecture

  • Parsing: tree-sitter builds a concrete syntax tree (CST) from SQL input.
  • AST: ast_builder walks the CST and produces a typed AST in src/ast.rs.
  • Scope: SELECT/CTE/query expressions plus DML (INSERT/UPDATE/DELETE); unsupported syntax maps to Statement::Unknown.
  • Extensibility: #[non_exhaustive] on key enums and Unknown variants allow incremental support.

Example: traverse and extract AST nodes

use pg_parser_rs::{parse_statements, Expr, SelectItem, Statement};

fn main() {
    let sql = "SELECT a, b + 1 AS c FROM t WHERE b > 10 ORDER BY c DESC";
    let statements = parse_statements(sql);

    for stmt in statements {
        if let Statement::Query(query) = stmt {
            if let pg_parser_rs::SetExpr::Select(select) = query.body {
                for item in select.projection {
                    match item {
                        SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => {
                            if let Expr::Identifier(ident) = expr {
                                println!("select column: {}", ident.value);
                            }
                        }
                        _ => {}
                    }
                }
            }
        }
    }
}

Tests

  • tests/parse_examples.rs: parses every examples/*.sql
  • tests/select_precision.rs: ensures select-related examples do not fall back to unknown_statement
  • tests/simple_select.rs: sanity check for a minimal SELECT
  • tests/ast_builder_select_examples.rs: ensures select examples build AST successfully
  • tests/ast_builder_dml_examples.rs: ensures insert/update/delete examples build AST successfully

Run all tests:

cargo test

Notes

The grammar lives under grammar/ and is compiled via build.rs. Generated sources (grammar/src/parser.c, grammar/src/node-types.json, grammar/src/grammar.json) are not checked into the repo.

Build requirements

This crate runs tree-sitter generate during build. Install the CLI:

cargo install tree-sitter-cli

You can also point TREE_SITTER_CLI to a custom binary.

Development flow

After changing grammar/grammar.js or grammar/src/scanner.cc, regenerate:

tree-sitter generate

Then sync generated sources into generated/ for builds and packaging:

mkdir -p generated/tree_sitter
cp grammar/src/parser.c generated/
cp grammar/src/scanner.cc generated/
cp grammar/src/node-types.json generated/
cp grammar/src/grammar.json generated/
cp grammar/src/tree_sitter/parser.h generated/tree_sitter/
Commit count: 0

cargo fmt