What is an SQL parser?
An SQL parser converts SQL text into a structured representation by recognizing tokens and applying a selected dialect grammar. The result is commonly a concrete syntax tree or abstract syntax tree containing statements, projections, table references, joins, predicates, expressions, groups, windows, order clauses, and source locations. If input violates the grammar, the parser reports a syntax error or produces a recovered partial tree according to its policy.
Parsing is structural, not execution. A successful parse does not prove that a table exists, a column is unambiguous, types are compatible, a function signature is valid, permissions allow access, a query is read-only, a result is correct, or execution is affordable. Those claims require binding, semantic validation, schema context, policy analysis, planning, or controlled execution.
Trace the pipeline from raw text to a usable tree
“Parse SQL” often hides several stages. An application may normalize line endings, remove a byte-order mark, expand templates, split a script, tokenize characters, apply grammar rules, build a parse tree, transform it into an AST, attach source metadata, recover from errors, and run post-parse normalization. Each stage can change what downstream tools see.
Encoding, bytes, line endings, BOM, length, source identity.
Templates, variables, directives, includes, delimiter commands.
Keywords, identifiers, literals, parameters, punctuation, trivia.
Dialect productions, precedence, associativity, statement forms.
Concrete nodes or semantic AST, spans, comments, recovered nodes.
Statements, diagnostics, coverage, confidence, unsupported constructs.
PostgreSQL’s official parser-stage documentation distinguishes lexical recognition and grammar parsing from a later transformation process. That separation is broadly useful: a raw syntax tree and a semantically augmented query representation answer different questions and should not be labeled interchangeably.
Separate parsing, validation, planning, and execution claims
A parser answers “Can this text be represented by this grammar?” A validator may answer “Do referenced names and types make sense in this schema and policy context?” A planner asks “How could the database execute the validated operation?” An executor performs work. Tools sometimes collapse these stages into one green check, which creates false confidence.
| Stage | Needs | Can establish | Cannot establish alone |
|---|---|---|---|
| Lex and parse | Text, dialect grammar, parser configuration | Token and grammatical structure; syntax diagnostics | Object existence, types, privileges, meaning, result |
| Bind and validate | AST, catalog, functions, types, scopes, policies | Resolved references, compatible operations, selected policy checks | Runtime result, plan cost, data-dependent behavior |
| Plan or compile | Validated representation, catalog, statistics, settings | Candidate execution plan, estimates, unsupported runtime features | Actual rows, time, locks, side effects, business correctness |
| Execute | Database, identity, data, resources, transaction, network | Observed results, errors, resource use, and side effects for one run | Universal correctness or future performance |
A syntax-only parser should display “parsed,” not “valid” or “safe.” If a tool enriches the tree with schema or executes a dry run, state the data source, identity, freshness, permissions, and side-effect boundary. The same-batch SQL query validator guide covers the broader validation contract.
Define the exact text and preprocessing boundary
The parser may never see the text visible to the user. Applications can expand `${variable}` placeholders, replace templated blocks, strip comments, normalize quotes, resolve notebook cells, include other files, convert named parameters, rewrite client commands, or split batches first. Parse diagnostics then refer to transformed text unless the system maintains a source map.
| Input property | Question | Evidence |
|---|---|---|
| Bytes and encoding | Which encoding, BOM, normalization, invalid-byte policy? | Byte length, decoded length, encoding label, error location |
| Line and column convention | Are columns bytes, code units, code points, or display cells? | Unicode and tab fixtures with documented indexing base |
| Template expansion | Which variables, includes, conditionals, and escaping rules? | Original text, expanded text, source map, redacted values |
| Script splitting | Are delimiters understood inside strings, comments, and procedural bodies? | Statement ranges, delimiters, client directives, incomplete tail |
| Source identity | Which file, cell, request, or generated fragment owns each span? | Stable source URI, version, content hash, mapping chain |
Preserve both raw and normalized input where policy permits, with secrets redacted. If a parser accepts pre-tokenized input, document token provenance. Diagnostics and rewrite tools cannot be trusted when source locations silently drift.
Evaluate the lexer before judging the syntax tree
The lexer turns characters into tokens such as keywords, identifiers, quoted identifiers, string and numeric literals, parameter markers, operators, punctuation, comments, whitespace, and dialect-specific constructs. If token boundaries or kinds are wrong, the grammar receives the wrong language. Lexing is especially sensitive to quoting, escape modes, nested comments, dollar-quoted bodies, Unicode, multi-character operators, and keywords that can also act as identifiers.
| Lexer fixture | Expected distinction | Failure impact |
|---|---|---|
| Quoted and unquoted identifiers | Quote style, original spelling, escape, case-folding flag | Wrong object identity or unsafe reserialization |
| Strings and escape modes | Delimiter, prefix, decoded value, raw text, termination | Premature statement end, changed literal, false parameter |
| Numeric forms | Integer, decimal, exponent, sign, separator, suffix | Different AST, overflow before validation, source drift |
| Parameters and variables | Question mark, positional, named, template, client variable | Literal confusion, wrong count, unsafe substitution |
| Comments and hints | Line, block, nested, optimizer hint, directive | Lost policy marker, wrong split, changed execution hint |
| Operators and punctuation | Longest valid operator, cast, JSON path, range, delimiter | Changed precedence or entirely different grammar path |
Expose token kind, raw slice, normalized value, quote metadata, start and end offsets, line and column, and channel or trivia status. Redact literal values only in a separate presentation layer; altering tokens before parsing changes the input.
Treat dialect configuration as part of the parse result
A grammar defines valid statement forms, clause order, expression precedence, associativity, reserved words, extensions, and ambiguity resolution. “ANSI SQL” is rarely sufficient because products support different standard levels and extensions. Some parsers offer strict, lenient, or vendor conformance modes; leniency can increase coverage while hiding unsupported syntax.
| Configuration | Record | Test |
|---|---|---|
| Dialect and version | Parser dialect name, target engine/version, parser build | Version-specific keyword and syntax fixtures |
| Conformance level | Strict, pragmatic, vendor, lenient, custom flags | Construct accepted only under one level |
| Lexical policy | Quote styles, case sensitivity, casing, escapes, comments | Same text tokenizes differently under two policies |
| Extensions | Custom statements, clauses, functions, operators, hints | Round-trip every extension and reject disabled ones |
| Recovery policy | Fail fast, collect errors, insert, delete, skip, partial tree | Malformed fixtures with known recovered regions |
Apache Calcite’s official SQL language reference describes the grammar accepted by its default parser and identifies constructs allowed only at certain conformance levels. A parser result should carry this configuration so another system cannot silently reinterpret the same text under a different grammar.
Define what the abstract syntax tree preserves and discards
An AST represents meaningful constructs rather than every character. Parentheses may be represented through tree shape; keywords and commas may disappear; aliases, quote flags, comments, hints, and source spans may be attached selectively; equivalent syntax may normalize to one node form. This is useful for analysis but risky for exact rewriting unless the fidelity contract is explicit.
| Tree property | Decision | Downstream consequence |
|---|---|---|
| Concrete versus abstract | Preserve grammar punctuation or only semantic nodes | Exact editing versus easier semantic traversal |
| Node identity | Stable IDs, object references, paths, or structural equality | Incremental updates, comments, diffs, and cache validity |
| Source fidelity | Offsets, line/column, full ranges, token references, source map | Precise diagnostics, highlights, safe edits, refactoring |
| Trivia and comments | Discard, token channel, attach leading/trailing, standalone nodes | Formatting, hints, directives, documentation, round trip |
| Normalization | Preserve spelling and syntax or canonicalize nodes and values | Stable analysis versus loss of original author intent |
| Recovered and unknown nodes | Typed placeholder, raw fragment, error node, omitted region | Whether downstream analysis can distinguish certainty |
Version the AST schema independently from the parser package if other systems persist or exchange it. Adding a node kind, changing child order, normalizing aliases, or altering source-span semantics can break policies and lineage even when parsing still succeeds.
Require precise source spans for every material node
Source spans connect the tree back to the text. They power diagnostics, syntax highlighting, hover details, code actions, policy findings, diffs, refactoring, and review. A node may need a full range, a name range, keyword ranges, delimiter ranges, and ranges for child lists. One coarse statement span is not enough for a safe automated edit.
| Span requirement | Hard case | Test |
|---|---|---|
| Half-open offset convention | Empty node, insertion point, final token, EOF | Slice source by start and end and compare expected raw text |
| Unicode-aware line and column | Surrogate pairs, combining marks, wide characters, tabs | Cross-check editor position using documented unit and tab width |
| Parent and child containment | Implicit nodes, normalized operators, detached comments | Define exceptions; assert every ordinary child lies within parent |
| Original versus expanded source | Template variable expands to multiple tokens or lines | Map generated range back to one or more original ranges |
| Recovered and missing token | Parser inserts an expected token that has no source characters | Mark synthetic range and insertion point explicitly |
Store offset units and indexing base in the API contract. Do not infer them from examples. Verify spans after every preprocessing step and AST normalization. A diagnostic that highlights the wrong identifier can cause a developer to “fix” valid text.
Preserve comments, hints, and whitespace according to purpose
Whitespace and comments are often called trivia because they do not change the core grammar. They can still carry optimizer hints, tool directives, lineage annotations, suppression markers, ticket references, ownership notes, and review rationale. Removing them may change execution or governance even when the AST looks equivalent.
| Preservation model | Strength | Risk |
|---|---|---|
| Discard trivia | Small tree and simple semantic analysis | Cannot round-trip comments, formatting, directives, or hints |
| Separate token channel | Exact lexical order and source fidelity | Downstream tool must associate trivia with semantic nodes |
| Leading and trailing attachment | Convenient node-based formatting and editing | Ambiguous ownership between siblings and delimiters |
| Standalone comment nodes | Comments can be queried and transformed explicitly | Tree traversal must distinguish semantic and nonsemantic children |
| Concrete syntax tree | Maximum round-trip and token ownership fidelity | More complex and grammar-coupled analysis |
Classify hints and directives separately from ordinary prose comments. If an unparser relocates a hint, test whether the target engine still interprets it. Never claim semantic equivalence after comment removal without understanding dialect behavior.
Parse scripts with grammar-aware statement boundaries
Splitting on every semicolon fails when semicolons appear inside strings, comments, procedural bodies, dynamic SQL, or vendor constructs. Some clients support custom delimiters, batch separators, meta-commands, includes, or notebook cell boundaries. The script parser must distinguish server SQL from client directives and report incomplete trailing input.
| Boundary case | Required result | Failure mode |
|---|---|---|
| Semicolon in string or comment | Remain inside one statement token stream | False extra statement and misleading error cascade |
| Procedural body | Respect body quoting and nested language grammar | Body fragments parsed as top-level SQL |
| Custom delimiter | Track directive scope and delimiter changes | Old delimiter remains active or directive reaches server tree |
| Batch separator | Represent batch boundaries and repetition semantics | Identifier mistaken for separator inside a statement |
| Incomplete final statement | Report expected continuation at EOF with partial tree | Silently discard tail or attach it to previous statement |
Return statement and batch ranges, delimiter metadata, client-directive nodes, and an explicit completeness flag. A formatter or policy engine must know whether it received one complete statement, a script, or a fragment.
Return structured diagnostics that identify cause and location
A useful syntax diagnostic is more than “parse failed.” It identifies an unexpected token or input region, expected token classes or grammar context, severity, stage, source range, related ranges, recovery action, and whether a tree remains usable. The human message should be derived from structured fields so interfaces can localize, sort, suppress, and test it.
| Diagnostic field | Purpose | Quality check |
|---|---|---|
| Stable code and stage | Automation, suppression, metrics, documentation | Code does not change with localized message text |
| Primary source range | Highlight the smallest responsible region or insertion point | Unicode, tabs, EOF, templates, and recovered tokens |
| Unexpected and expected | Explain grammar mismatch and support completion | Expected set is relevant, bounded, and dialect-aware |
| Context path | Show statement, clause, expression, and nested construct | Does not expose internal parser stack as user guidance |
| Recovery and certainty | Tell downstream tools what was inserted, deleted, skipped, or guessed | Recovered regions are explicit and never reported as clean |
| Related information | Point to opening delimiter, paired clause, previous declaration | Ranges remain valid after source mapping |
Do not promise the one true fix. After an unexpected token, several repairs may be valid. Offer grammar-grounded suggestions and label them as candidates. Preserve the raw parser error for debugging while presenting a concise user message.
Make recovered trees visibly different from clean trees
Editors need partial trees while a user is typing; batch validators may prefer fail-fast behavior. Recovery strategies can insert an expected token, delete an unexpected token, skip to a synchronization point, wrap raw text in an error node, or choose one branch of an ambiguity. Recovery enables continued analysis but introduces parser-created structure not present in the source.
| Recovery action | Benefit | Required marker |
|---|---|---|
| Insert missing token | Continue a common incomplete construct | Synthetic token, insertion offset, expected kind, diagnostic |
| Delete unexpected token | Recover from stray punctuation or duplicated keyword | Skipped source range retained as error trivia or node |
| Skip to synchronization point | Avoid cascades and parse later clauses or statements | Omitted range, synchronization token, incomplete ancestor flags |
| Error or unknown node | Preserve raw fragment and tree position | Distinct node kind, raw text, expected role, uncertainty |
| Best-effort dialect fallback | Analyze unsupported vendor construct approximately | Fallback grammar, unsupported warning, affected subtree |
Downstream security, lineage, and rewrite systems should default to refusing consequential conclusions from recovered regions. A formatter may print them, and an editor may navigate them, but a policy engine must not treat guessed structure as proven syntax.
Measure incremental parsing by correctness before latency
Interactive tools reparse as a user types. Incremental parsers reuse unchanged tokens or subtrees, while simpler systems reparse the whole document. Reuse reduces latency but requires valid invalidation when an edit changes lexical mode, delimiter balance, statement boundary, alias scope, or dialect interpretation far from the cursor.
Insert, delete, replace, paste, undo, and redo at beginning, middle, and end.
Change an opening quote, comment marker, dollar tag, or multi-token operator.
Move delimiter, parenthesis, CTE boundary, alias, or nested query terminator.
Define which unchanged nodes retain stable identity and which must be recreated.
Incremental result must equal a clean full parse after every edit sequence.
Track median and tail latency, allocations, reused nodes, and cancellation.
Build a differential test that applies random and recorded editor operations, compares the incremental tree and diagnostics with a fresh full parse, and checks source spans. Fast stale trees are worse than slower correct trees.
Preserve identifier, parameter, and literal distinctions
Many downstream errors begin when a parser normalizes away distinctions that matter later. An identifier needs original spelling, quote style, quote escapes, case-folding status, and qualification parts. A parameter needs marker style, position or name, and source range. A literal needs raw text, literal kind, prefixes, and decoded value without losing precision or executing conversion logic unsafely.
| Construct | Preserve | Do not assume |
|---|---|---|
| Unquoted identifier | Raw spelling, normalized lookup form, dialect case rule | Displayed case equals catalog identity |
| Quoted identifier | Delimiter, escaped delimiters, exact content, quote flag | Quotes can be removed during formatting |
| Qualified name | Each component, separators, wildcard or omitted component | Number of parts maps universally to server, database, schema, table |
| Parameter marker | Style, ordinal or name, repeated identity, surrounding cast | Every colon or question mark is a parameter |
| String or numeric literal | Raw lexeme, kind, prefix, escape policy, arbitrary precision representation | Host-language number or string preserves target semantics |
Redaction should replace literal presentation after parsing, not mutate the AST used for analysis unless the transformation is explicit and type-aware. A redacted tree is a different artifact and needs its own provenance.
Measure coverage beyond simple SELECT statements
A parser can advertise a dialect while supporting only common queries. Real repositories contain DDL, DML, MERGE, COPY or load commands, grants, comments, transactions, session settings, explain commands, procedural functions, triggers, dynamic SQL, scripting variables, warehouse clauses, hints, semi-structured paths, and vendor extensions. Coverage must be stated by construct and version.
| Coverage level | Meaning | Evidence |
|---|---|---|
| Recognized | Lexer and grammar accept the construct | Fixture parses cleanly in strict target mode |
| Structured | AST exposes meaningful typed nodes and properties | Node assertions for all material clauses |
| Source-faithful | Spans, comments, hints, quotes, and raw fragments are preserved | Token and span checks plus lossless or defined-loss round trip |
| Analyzable | Visitors, name collectors, policies, and rewrites understand it | Downstream semantic tests, not just parser snapshots |
| Round-trippable | Unparser can emit accepted target SQL with defined fidelity | Parse–unparse–parse structural and execution-aware checks |
Publish unsupported constructs and fallback behavior. Returning a generic raw node can be acceptable when clearly marked; silently coercing a vendor clause into a superficially similar standard node is more dangerous because downstream tools assume understanding.
Evaluate AST traversal with scope and context
A tree visitor can collect every table-looking or column-looking node, but the same syntax has different roles in CTE definitions, subqueries, aliases, function arguments, windows, insert targets, update assignments, merge branches, and correlated references. Useful traversal APIs expose parent, child role, statement, clause, scope boundary, and source range.
| Traversal task | Context required | Naive failure |
|---|---|---|
| Collect relation references | CTE names, aliases, subquery boundaries, table functions, targets | CTE reported as physical table or write target missed |
| Collect column references | Qualifier, alias visibility, star, nested fields, scope | Alias definition mistaken for input column |
| Classify statement effects | Top-level and nested statements, procedures, functions, external commands | SELECT wrapper hides mutation or side-effecting call |
| Find predicates | WHERE, JOIN, HAVING, FILTER, QUALIFY, policy, merge branch | Filter role and row stage collapsed together |
| Rewrite identifiers | Definition versus reference, quote semantics, namespace, source map | Renames unrelated alias or breaks quoting |
Provide typed child roles instead of relying only on list position. Visitors should be able to skip recovered or unknown subtrees, preserve traversal order, and report uncertainty. Test nested scopes and shadowed names even if the parser itself does not bind them.
Do not confuse table-shaped nodes with resolved catalog objects
The AST can say that the source contains a relation reference named orders. Without a catalog and scope rules, it cannot prove whether that name refers to a CTE, local temporary table, view, materialized view, synonym, table function, remote object, or physical table; nor which database and schema resolve an unqualified name.
| Question | Parser-only answer | Additional context |
|---|---|---|
| Is this identifier a table? | It occupies a relation-reference grammar position | CTE scope, aliases, catalog object types, table functions |
Which column does id mean? | It is an unqualified column-reference node | Visible relations, projection aliases, lateral and correlation rules |
| Is this function safe? | It is a call with a name and arguments | Function registry, overload resolution, volatility, privileges, extensions |
| What type is the expression? | Its syntactic operator and literal forms are known | Column types, function signatures, coercion, parameter types, settings |
| Is the query read-only? | Top-level statement and nested syntactic forms are known | Function side effects, procedures, external sources, engine semantics |
Downstream APIs should name syntax references differently from resolved symbols. Preserve the original node and attach resolution separately with catalog version, scope, identity, and confidence. This prevents stale binding from being mistaken for parser truth.
Use the AST as lineage input, not lineage proof
Parsing reveals syntactic data flow: sources, projections, aliases, joins, expressions, insert targets, and nested statements. Complete lineage also needs name resolution, star expansion, view and routine definitions, dynamic SQL, macros, temporary objects, external tables, session defaults, policy rewrites, runtime branches, and engine-specific semantics. Recovered or unsupported syntax creates explicit gaps.
Parser can enumerate syntax sources and targets, but must distinguish CTEs, aliases, and physical relations.
Requires resolved columns, star expansion, expression semantics, set alignment, and nested scope.
Dynamic SQL, conditional scripts, stored procedures, generated names, and external systems may only resolve during execution.
Every edge should record source stage, catalog version, unresolved nodes, and whether it is syntactic, resolved, or observed.
Never turn an incomplete parse into a complete-looking lineage graph by omitting unknown regions. Show gaps, affected outputs, and the evidence needed to resolve them.
Test parse–unparse–parse at syntax and semantic levels
An unparser or formatter turns a tree back into SQL. The result may intentionally normalize keyword case, indentation, parentheses, aliases, identifier quotes, or literal spelling. The critical question is which changes are allowed and whether the output reparses under the same dialect into an equivalent structure without losing hints, comments, or unsupported fragments.
| Fidelity level | Required equality | Use case |
|---|---|---|
| Lossless text | Original bytes or characters reproduced exactly | Archival, minimal edits, source-preserving tools |
| Trivia-preserving syntax | Tokens, comments, hints, and structure preserved; whitespace may normalize | Formatter and review-friendly rewrite |
| Structural AST | Reparsed semantic tree equivalent under defined normalization | Canonicalization, diff, cache keys, analysis |
| Validated semantics | Resolved objects, types, effects, and selected invariants equivalent | Refactoring and dialect-aware transformation |
| Observed behavior | Controlled execution gives equivalent typed outcomes and side effects | High-risk rewrite verification in disposable environment |
Structural equality needs a documented normalizer: alias omission, redundant parentheses, literal forms, commutative expressions, and clause ordering cannot be treated casually. A parser round trip proves its own representation consistency, not universal query equivalence.
Treat dialect transpilation as a typed, lossy transformation
A multi-dialect parser can normalize source syntax and emit a target dialect, but syntax similarity does not guarantee semantic equivalence. Functions, types, null behavior, date arithmetic, identifier resolution, collation, regex, arrays, JSON, intervals, merge semantics, transactions, and optimizer hints may lack exact mappings. Some transformations require schema and type information unavailable to the parser.
| Outcome | Meaning | Required disclosure |
|---|---|---|
| Exact supported mapping | Construct has a documented target equivalent under stated context | Source and target dialect/version, rule, tests |
| Context-dependent mapping | Requires types, schema, settings, or business assumption | Required context and behavior when absent |
| Approximation | Output is usable but may change precision, edge behavior, or performance | Loss description, affected node, severity, alternative |
| Unsupported | No trustworthy target representation | Hard error or explicit preserved raw fragment |
| Dropped silently | Source property disappears without evidence | Unacceptable for trustworthy transformation |
The SQLGlot project documentation describes parser errors, AST inspection, custom dialects, and unsupported transpilation behavior, including cases that need schema information. Regardless of library, configure unsupported mappings to fail or surface prominently for consequential use.
Parse untrusted SQL without turning analysis into execution
Parsing should not contact a database or execute functions, but it still processes attacker-controlled input. Deep nesting, huge token counts, pathological ambiguity, long literals, recursive grammar paths, comment nesting, generated diagnostics, AST serialization, extension hooks, and downstream visitors can consume CPU, memory, stack, disk, or logs. Dependencies and native bindings add their own attack surface.
| Control | Protects against | Evidence |
|---|---|---|
| Byte and character limit | Oversized input, decoding and logging amplification | Reject before allocation with structured size diagnostic |
| Token, statement, and nesting limits | Huge trees, stack exhaustion, excessive ambiguity | Boundary tests at, below, and above every limit |
| Time and cancellation budget | Pathological parse, stalled worker, batch starvation | Hard deadline, cooperative or process cancellation, no orphan work |
| Process or worker isolation | Crash, memory leak, native bug, untrusted extension | Resource cap, restart, health check, sanitized IPC |
| No resolution or execution callback | Network access, file access, function execution, secret retrieval | Architecture and tests prove parse-only dependency graph |
| Pinned and reviewed dependencies | Supply-chain changes and vulnerable parser runtimes | Lockfile, integrity, notices, update and rollback process |
Do not log full untrusted SQL by default. Diagnostics can include secrets or personal data in context windows. Use content hashes, bounded redacted excerpts, secure access, retention limits, and deletion procedures.
Benchmark realistic syntax, errors, and tail latency
A benchmark of short valid SELECT statements says little about migration files, deeply nested views, procedural definitions, malformed editor buffers, multi-megabyte generated SQL, or mixed-dialect repositories. Measure throughput and latency by corpus class, input length, token count, node count, nesting, error count, recovery mode, comments, and source-map requirements.
| Metric | Segment by | Why it matters |
|---|---|---|
| Median and p95/p99 latency | Valid, incomplete, invalid, recovered, dialect, size | Interactive experience and batch capacity depend on tails |
| Peak and retained memory | Tokens, AST, trivia, diagnostics, serialization, repeated parse | Leaks and large trees can exhaust services or editors |
| Allocations and node reuse | Full parse versus incremental edit classes | Explains latency and garbage-collection pressure |
| Cancellation latency | Tokenizer, grammar, recovery, post-processing phase | A timeout is ineffective if work ignores cancellation |
| Correctness under load | Parallel parses, parser reuse, dialect switching, cancellation | Shared mutable state can corrupt trees and diagnostics |
Pin runtime and hardware, warm-up policy, garbage collector, parser options, input corpus hash, and output mode. A parser that discards spans and comments should not be compared directly with one that preserves them without explaining the work difference.
Build a layered parser test corpus
Parser quality depends on breadth and adversarial depth. Golden AST snapshots are useful but can approve an incorrect tree if reviewed casually. Combine lexical fixtures, grammar examples, vendor documentation, real sanitized repositories, negative syntax cases, recovery cases, round trips, differential tests, fuzzing, metamorphic properties, and downstream behavior tests.
| Test layer | Oracle | Finds |
|---|---|---|
| Token fixtures | Exact kinds, raw slices, values, quotes, spans, trivia | Lexical boundary and source-location defects |
| Grammar positives and negatives | Target dialect accepts valid and rejects invalid constructs | Coverage gaps and overly lenient grammar |
| AST property assertions | Typed nodes, child roles, spans, quote flags, recovery markers | Semantically wrong but syntactically accepted trees |
| Round-trip and metamorphic | Defined equality after formatting, whitespace, comments, parentheses | Loss, unstable serialization, normalization mistakes |
| Differential parsing | Compare server parser or independent implementation with reconciled models | Dialect disagreement and hidden assumptions |
| Fuzz and resource tests | No crash, hang, unbounded memory, stale state, or unsafe callback | Robustness and security defects |
Keep corpus provenance, dialect and version, expected support level, sanitization method, license, and failure history. Add every production parser defect as a minimal regression plus the original sanitized case.
Use parsing as the first structural gate for generated SQL
AI-generated SQL should be parsed under the intended dialect before any database interaction. The AST can identify statement count and type, nested mutations, relation-shaped references, functions, joins, predicates, limits, comments, unsupported syntax, parameters, and recovered regions. Those findings support policy and review, but they remain syntactic until enriched with a trusted schema and engine semantics.
Require target engine/version and reject silent generic fallback.
Record exact text, parser version, diagnostics, unsupported and recovered regions.
Count statements and identify top-level and nested operation forms.
Reject disallowed statement kinds, unresolved fragments, or risky constructs.
Resolve names, types, functions, permissions, and semantics using approved metadata.
Use deterministic fixtures, assertions, read-only boundaries, plans, and limits.
Inspect an NL2SQL candidate before database use
Use the InfiniSynapse NL2SQL Query Tester with a sanitized schema and explicit target dialect, then keep parsing, schema validation, policy checks, and controlled execution as separate evidence gates.
Open NL2SQL Query Tester Use sanitized SQL and metadata. Never paste credentials, secrets, personal data, or unrestricted production samples.Score an SQL parser with observable evidence
Parser choice should follow use cases. An editor needs recovery, incremental correctness, spans, and low tail latency; a migration analyzer needs script and DDL coverage; a policy engine needs strict dialect behavior and explicit unknown nodes; a formatter needs round-trip fidelity; a transpiler needs typed context and loss reporting. Set nonnegotiable gates before weighting convenience.
| Criterion | Weight | Evidence task | Gate? |
|---|---|---|---|
| Target dialect and construct coverage | 18% | Run versioned positive, negative, procedural, DDL, DML, and script corpus | Yes |
| AST contract and source fidelity | 16% | Assert nodes, roles, spans, quotes, trivia, unknown and recovery markers | Yes |
| Diagnostics and recovery honesty | 14% | Inject malformed constructs and verify location, code, expected, recovery, certainty | Yes |
| Safety under untrusted input | 14% | Exercise size, nesting, token, time, memory, cancellation, fuzz, isolation | Yes |
| Round-trip and transformation behavior | 12% | Test parse–unparse–parse, comments, hints, quotes, unsupported and losses | Depends |
| Incremental and concurrency correctness | 10% | Differential editor sequences, parallel dialect switching, cancellation | Depends |
| Performance and operational fit | 8% | Measure realistic median/tails, memory, allocations, serialization, recovery | No |
| Versioning, API, documentation, maintenance | 8% | Upgrade, AST schema diff, regression history, security notices, rollback | No |
Score 0–4: absent, claimed, demonstrated once, repeatably verified, or enforced and monitored. Weighting cannot rescue a failed gate. Store corpus hashes, parser and runtime versions, options, output schemas, test results, owners, and dates.
Use a twelve-step trustworthy parsing workflow
State whether the tree supports editing, policy, lineage, formatting, migration, or review.
Record source, encoding, content hash, original text, preprocessing, and source maps.
Specify target engine/version, conformance, lexical policy, extensions, build, and options.
Set bytes, tokens, statements, nesting, time, memory, diagnostics, and serialization limits.
Preserve kind, raw text, value, quote, trivia, and precise source spans.
Report statement boundaries, clean or recovered status, errors, unknown and unsupported regions.
Check node schema, child roles, span containment, token coverage, source identity, and recovery markers.
Classify every statement and construct as structured, raw, recovered, unsupported, or omitted.
Attach binding, types, lineage, effects, and policies with catalog versions and confidence.
Run actual visitors, formatters, policies, diffs, and refactors on hard corpus cases.
Compare clean full parse, incremental parse, independent parser, and target engine where meaningful.
Store versions, options, corpus, AST schema, diagnostics, gaps, metrics, and upgrade evidence.
Stop consequential analysis when the dialect is unknown, input transformation cannot be mapped, the tree contains unbounded recovered or unknown regions, source spans fail checks, required constructs are unsupported, resource bounds are absent, or downstream logic treats syntax references as resolved facts.
Avoid common SQL parser failure patterns
| Pattern | Why it fails | Better control |
|---|---|---|
| Parsed means valid and safe | Grammar says nothing about schema, types, permissions, meaning, or cost | Stage-specific status and separate validation gates |
| Generic SQL mode for vendor repositories | Lenient grammar accepts, misclassifies, or drops extensions | Pinned dialect/version and explicit unsupported regions |
| Recovered tree treated as clean | Inserted and skipped tokens become invented syntax facts | Recovery markers, certainty, and downstream refusal policy |
| Regex statement splitting | Strings, comments, procedures, delimiters, and client commands break it | Dialect-aware lexer and script grammar |
| AST snapshot as only test | Review can approve stable but incorrect structure and misses downstream impact | Property, round-trip, differential, fuzz, and consumer tests |
| Silent best-effort transpilation | Unsupported semantics disappear behind valid-looking target SQL | Loss report, hard failure, typed context, controlled equivalence tests |
Frequently asked questions about SQL parsers
What is an SQL parser?
It tokenizes SQL text and applies a selected dialect grammar to produce a structured syntax tree or syntax errors. It should not execute the statement.
Does successful parsing mean a query is valid?
No. It proves grammar only. Object existence, name resolution, types, functions, permissions, policies, meaning, plan, resources, and results require later stages.
Why does the dialect matter?
Dialects differ in keywords, quoting, operators, functions, parameters, types, statements, procedural syntax, precedence, and extensions. The same text may parse differently or fail.
What is an SQL AST?
It is a tree of meaningful constructs such as statements, projections, relations, joins, predicates, expressions, groups, windows, and order. Its contract determines which punctuation, comments, spelling, and source locations remain.
Can a parser determine SQL lineage?
It provides syntax inputs, but complete lineage also needs catalog binding, star expansion, view and routine definitions, dynamic SQL, temporary state, engine semantics, and explicit gaps.
Is parsing untrusted SQL safe?
It avoids database execution but still needs input, nesting, token, time, memory, cancellation, isolation, dependency, and logging controls to resist denial of service and implementation defects.
Final SQL parser readiness checklist
Encoding, original and expanded text, source identity, script boundaries, preprocessing, and source maps are explicit.
Target engine/version, conformance, lexical rules, extensions, parser build, and recovery policy are pinned.
Node schema, child roles, source spans, quote metadata, trivia, unknown, unsupported, and recovered regions are documented.
Parsing, binding, validation, lineage, policy, planning, transformation, and execution results are labeled separately.
Untrusted input limits, timeouts, cancellation, isolation, dependency integrity, redaction, retention, and no-execution boundaries are tested.
Versioned corpora cover tokens, grammar, errors, recovery, source fidelity, round trip, downstream consumers, differential behavior, fuzzing, and performance tails.
A trustworthy SQL parser does not merely produce a tree. It tells downstream systems exactly which language it recognized, which source regions support each node, where it recovered or guessed, what it intentionally discarded, what it cannot understand, and which claims still require schema or runtime evidence.