squonk 1.0.0

Extensible, fast, multi-dialect SQL tokenizer and parser for Rust
Documentation
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Moderately AI Inc.

//! Whole-tree node-identity invariants: every parsed AST node carries
//! a unique, nonzero `NodeId` *and* a real, non-synthetic source `Span`.
//!
//! `make_meta` is the parser's single metadata chokepoint (see [`engine`]); it
//! mints each node's id from a per-parse monotonic counter and its span from the
//! source bytes the node covers, so both invariants hold *by construction* — no
//! parser node builds its `Meta` any other way. This module is the regression
//! guard: it walks representative parsed trees with the generated [`NodeIdWalk`]
//! and asserts no node has a zero or duplicated id, and that none reports the
//! `Span::SYNTHETIC` placeholder reserved for out-of-source rewrite nodes. A
//! future grammar node that builds its `Meta` outside `make_meta` (e.g. pasting a
//! placeholder id, or leaving a synthetic span on a parsed node) surfaces here.
//!
//! Coverage is exhaustive *and* drift-proof without a hand-maintained checklist:
//! [`NodeIdWalk`] is sourcegen-emitted from the same `meta: Meta` shape
//! that defines id-bearing-ness, so it overrides every id-bearing node type — and
//! a newly added node is covered automatically once regenerated, with no edit to
//! this file. The earlier hand-registered collector lived here and silently
//! skipped any node nobody remembered to add (e.g. `CreateIndex`/`CreateView`,
//! and every `Statement` variant past the wildcard); generating the walk removes
//! that last manual step the node-identity ADR deferred.
//!
//! [`engine`]: super::engine
//! [`NodeIdWalk`]: crate::ast::generated::NodeIdWalk

use std::collections::HashSet;

use crate::ast::generated::{NodeIdWalk, Visit};
use crate::ast::*;
use crate::parser::{TestDialect, parse_with};

/// Walk `statements` with the generated [`NodeIdWalk`] and return every id-bearing
/// node's `Meta` (its id and source span), in visitation order. A single `Meta`
/// per node feeds both whole-tree invariants from one traversal.
fn collect(statements: &[Statement<NoExt>]) -> Vec<Meta> {
    let mut walk = NodeIdWalk::default();
    for statement in statements {
        walk.visit_statement(statement);
    }
    walk.metas
}

/// The first id that appears more than once, or `None` when all ids are unique.
fn first_duplicate(ids: &[NodeId]) -> Option<NodeId> {
    let mut seen = HashSet::with_capacity(ids.len());
    ids.iter().copied().find(|id| !seen.insert(*id))
}

/// A `Meta` with a chosen id and a throwaway one-byte span, for hand-built trees.
fn meta_with_id(id: u32) -> Meta {
    Meta::new(Span::new(0, 1), NodeId::new(id).expect("non-zero node id"))
}

/// Representative SQL exercising a broad, varied slice of the grammar — one string
/// per parse. A single `Parser` streams all statements of a parse through the same
/// monotonic counter, so ids stay unique across the semicolon-separated statements
/// that share a parse, not just within one. Shared by both whole-tree invariants.
const REPRESENTATIVE_CORPUS: &[&str] = &[
    // DDL: column defs, generated/identity columns, table constraints, types.
    "CREATE TABLE t (\
        id INT PRIMARY KEY, \
        name TEXT NOT NULL DEFAULT 'x', \
        n INT GENERATED ALWAYS AS (id + 1) STORED, \
        ident BIGINT GENERATED BY DEFAULT AS IDENTITY, \
        CONSTRAINT u UNIQUE (name), \
        CHECK (id > 0)\
    )",
    // CTAS (no typed columns) and table options exercise the remaining
    // CreateTableBody / CreateTableOption node kinds.
    "CREATE TABLE t AS SELECT 1",
    "CREATE TEMPORARY TABLE t (id INT) \
     WITH (fillfactor = 70) ON COMMIT DROP TABLESPACE pg_default",
    // DML.
    "INSERT INTO t (id, name) VALUES (1, DEFAULT), (2, 'b')",
    "UPDATE t AS target SET a = 1, b = DEFAULT FROM u WHERE target.id = u.id",
    "DELETE FROM t AS target USING u WHERE target.id = u.id",
    // Standalone VALUES whose rows carry a `DEFAULT` item (its own node kind).
    "VALUES (1, DEFAULT), (DEFAULT, 2)",
    // Rich query: qualified columns, many Expr variants, CASE/CAST/EXTRACT,
    // a join, IN / BETWEEN / EXISTS, ORDER BY / LIMIT.
    "SELECT t.a + 1 AS x, count(DISTINCT a), \
        CASE a WHEN 1 THEN b ELSE c END, \
        CAST(a AS INTEGER), EXTRACT(year FROM a) \
     FROM s.t AS t LEFT JOIN u ON TRUE \
     WHERE a IN (1, 2) AND b BETWEEN 1 AND 2 AND EXISTS (SELECT 1) \
     ORDER BY a LIMIT 10",
    // Set operation, CTE, derived table.
    "WITH c AS (SELECT 1) SELECT 1 UNION ALL SELECT 2",
    "SELECT * FROM (SELECT 1) AS s",
    // Transaction control (several statements share one parse).
    "BEGIN; SAVEPOINT sp1; ROLLBACK TO SAVEPOINT sp1; RELEASE SAVEPOINT sp1; COMMIT",
    "SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
    "START TRANSACTION READ ONLY NOT DEFERRABLE",
    // Access control and session, including the special-cased SET subforms
    // (their value/sentinel, constraints-target, and characteristics nodes).
    "GRANT SELECT, INSERT ON t TO alice, bob",
    "REVOKE SELECT ON t FROM alice",
    "SET search_path = public",
    "SET TIME ZONE LOCAL; SET TIME ZONE 'UTC'; SET LOCAL TIME ZONE DEFAULT",
    "SET ROLE NONE; SET SESSION AUTHORIZATION admin; SET NAMES utf8 COLLATE utf8_bin",
    "SET CONSTRAINTS ALL DEFERRED; SET CONSTRAINTS a, b IMMEDIATE",
    "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY",
    "RESET ALL",
];

#[test]
fn representative_parsed_trees_have_unique_nonzero_node_ids() {
    let mut total = 0usize;
    for &sql in REPRESENTATIVE_CORPUS {
        let parsed = parse_with(sql, TestDialect)
            .unwrap_or_else(|err| panic!("representative SQL must parse: {sql:?}: {err:?}"));
        let ids: Vec<NodeId> = collect(parsed.statements())
            .iter()
            .map(|meta| meta.node_id)
            .collect();

        assert!(!ids.is_empty(), "{sql:?}: traversal collected no node ids");
        // `NodeId` is a `NonZeroU32`, so a zero id is unrepresentable; the
        // assertion documents the invariant the parser guarantees.
        assert!(
            ids.iter().all(|id| id.as_u32() != 0),
            "{sql:?}: a node carried a zero id",
        );
        if let Some(dup) = first_duplicate(&ids) {
            panic!(
                "{sql:?}: node id {} appears on more than one node — a node was \
                 built outside make_meta or reused a placeholder id",
                dup.as_u32(),
            );
        }
        total += ids.len();
    }

    // Guard against a silent coverage collapse: the corpus must exercise a large,
    // varied tree, not a trivially-unique handful of ids.
    assert!(
        total > 100,
        "corpus exercised only {total} nodes; expected many"
    );
}

#[test]
fn representative_parsed_trees_have_non_synthetic_spans() {
    // Every node parsed from source covers real bytes, so its span must be a true
    // source range. `Span::SYNTHETIC` is reserved for out-of-source rewrite nodes
    // (ADR-0002); a generated `Spanned` impl that folded a child-less node to the
    // synthetic placeholder — the hole sourcegen now rejects at generation time —
    // would surface here as a synthetic span on a parsed node.
    let mut total = 0usize;
    for &sql in REPRESENTATIVE_CORPUS {
        let parsed = parse_with(sql, TestDialect)
            .unwrap_or_else(|err| panic!("representative SQL must parse: {sql:?}: {err:?}"));
        let spans: Vec<Span> = collect(parsed.statements())
            .iter()
            .map(|meta| meta.span)
            .collect();

        assert!(
            !spans.is_empty(),
            "{sql:?}: traversal collected no node spans"
        );
        assert!(
            spans.iter().all(|span| !span.is_synthetic()),
            "{sql:?}: a parsed node reported Span::SYNTHETIC instead of its source range",
        );
        total += spans.len();
    }

    // Same coverage floor as the id invariant: a large, varied tree, not a handful.
    assert!(
        total > 100,
        "corpus exercised only {total} nodes; expected many"
    );
}

/// The `NodeId`s the generated walk records from a hand-built tree, in order.
fn walk_ids(expr: &Expr<NoExt>) -> Vec<NodeId> {
    let mut walk = NodeIdWalk::default();
    walk.visit_expr(expr);
    walk.metas.iter().map(|meta| meta.node_id).collect()
}

#[test]
fn walk_flags_a_node_that_reuses_an_id() {
    // Simulate a node assembled outside make_meta: the wrapping Expr and its inner
    // Literal share id 1, the kind of placeholder a future grammar might paste in.
    // The walk must descend into the child and surface the collision the uniqueness
    // assertion relies on.
    let reused = NodeId::new(1).expect("non-zero node id");
    let ids = walk_ids(&Expr::Literal {
        literal: Literal {
            kind: LiteralKind::Integer,
            meta: meta_with_id(1),
        },
        meta: meta_with_id(1),
    });

    assert_eq!(ids.len(), 2, "both Expr and Literal ids are recorded");
    assert_eq!(first_duplicate(&ids), Some(reused));
}

#[test]
fn walk_accepts_distinct_ids() {
    // The same shape with distinct ids is the well-formed case make_meta produces.
    let ids = walk_ids(&Expr::Literal {
        literal: Literal {
            kind: LiteralKind::Integer,
            meta: meta_with_id(1),
        },
        meta: meta_with_id(2),
    });

    assert_eq!(ids.len(), 2);
    assert_eq!(first_duplicate(&ids), None);
}