NAME

    SQL::AST::Simple - Parse SQL into a plain Perl data structure and back
    again

VERSION

    version 0.01

SYNOPSIS

     use SQL::AST::Simple qw( parse unparse parse_expr unparse_expr );
    
     my $ast = parse('SELECT a, b FROM t WHERE a > 1', dialect => 'postgresql');
    
     # $ast is an array reference of statements, each a tree of plain
     # hashes, arrays and scalars.  Poke at it however you like:
     $ast->[0]{Query}{body}{Select}{from}[0]{relation}{Table}{name}[0]{Identifier}{value} = 'u';
    
     say unparse($ast);   # SELECT a, b FROM u WHERE a > 1
    
     # Expressions can be handled on their own, without a statement around them:
     $ast->[0]{Query}{body}{Select}{selection} = parse_expr('a > 1 AND b = 2');
     say unparse_expr($ast->[0]{Query}{body}{Select}{selection});   # a > 1 AND b = 2

DESCRIPTION

    This module provides Perl bindings for the Rust sqlparser
    <https://crates.io/crates/sqlparser> crate. It exposes two operations:
    turning SQL text into the parser's abstract syntax tree as an ordinary
    Perl data structure, and turning such a data structure back into SQL
    text. Each comes in a form for whole statements and a form for a lone
    expression. There is no object layer; the tree is what the crate's
    serde serialization produces, decoded from JSON. That keeps the module
    small and makes every node the crate knows about available without any
    wrapping, at the cost of a somewhat verbose structure.

    Nothing is exported by default.

FUNCTIONS

 parse

     my $ast = parse($sql);
     my $ast = parse($sql, dialect => $name);

    Parses $sql, which may contain several semicolon separated statements,
    and returns an array reference with one element per statement. Throws
    an exception with the parser's message, including line and column, if
    the text cannot be parsed.

    Options:

    dialect

      Which SQL dialect to parse with. Defaults to generic, which is the
      most permissive. Recognized names (case insensitive) are generic,
      ansi, postgresql (or postgres), mysql, sqlite, mssql, oracle,
      snowflake, bigquery, redshift, clickhouse, duckdb, databricks, hive,
      spark (or sparksql) and teradata.

 parse_expr

     my $expr = parse_expr($sql);
     my $expr = parse_expr($sql, dialect => $name);

    Parses $sql as a single expression, such as the condition of a WHERE
    clause, and returns it as a hash reference. The whole of $sql must be
    consumed by the expression; a leading WHERE keyword or anything left
    over after the expression is an error. Takes the same dialect option as
    "parse".

    The result is exactly what appears inside a statement wherever the
    crate expects an expression, so it can be spliced into a tree from
    "parse", for instance as the selection of a SELECT.

 unparse

     my $sql = unparse($ast);
     my $sql = unparse($ast, pretty => 1);

    Takes an array reference of statements as returned by "parse", or a
    single statement hash reference, and returns the SQL text. Multiple
    statements are joined with "; ". Throws an exception if the structure
    does not deserialize into a valid AST.

    Options:

    pretty

      If true, statements are formatted with indentation and newlines
      rather than on a single line, and are joined with ";\n".

 unparse_expr

     my $sql = unparse_expr($expr);

    Takes an expression hash reference, as returned by "parse_expr" or
    lifted out of a statement, and returns the SQL text. Throws an
    exception if the structure does not deserialize into a valid
    expression. There is no pretty option; expressions are always rendered
    on one line.

THE DATA STRUCTURE

    The tree mirrors the Rust types of the sqlparser crate one to one, as
    serialized by serde. A few rules of thumb cover most of it:

      * Rust enums are "externally tagged": a hash with a single key naming
      the variant, whose value is the payload. A SELECT statement is {
      Query => {...} }, a column reference in an expression is { Identifier
      => {...} }, a literal is { Value => {...} }. Variants without payload
      are plain strings.

      * Rust structs are hashes keyed by field name; Option fields that are
      absent are undef; Vec fields are array references.

      * Booleans come back as JSON boolean objects. When you set a boolean
      field yourself use \1 or \0 (or the true/false constants from your
      JSON module). A plain Perl 1 would be encoded as a number and
      rejected by "unparse".

      * Numeric literals are kept as strings, exactly as they appeared in
      the source, so that precision is never lost. Any field that holds a
      string must be given a Perl string; if you have computed a number,
      stringify it first.

      * Most nodes carry a span hash recording where they appeared in the
      source. "unparse" ignores the contents but requires the field to be
      present, so the easiest way to build a new node is to parse a small
      snippet (with "parse_expr" for an expression) and lift the piece you
      need out of the result, rather than constructing hashes by hand.

    The easiest way to learn the shape for a given construct is to parse an
    example and dump it. The exact shape depends on the version of the
    crate the bindings are built against, which is pinned in the
    distribution's ffi/Cargo.toml; a release that bumps it may change the
    structure and will say so in the change log.

CAVEATS

    The parser is syntactic only and deliberately permissive. It will
    accept some SQL that a given database would reject, and occasionally
    reject vendor syntax it does not yet know. Round tripping is not byte
    for byte: comments are dropped, keywords are upper cased, and
    whitespace is normalized.

    Building this distribution requires a Rust toolchain (cargo) at install
    time.

SEE ALSO

    https://crates.io/crates/sqlparser

      The parser this module wraps.

AUTHOR

    Graham Ollis <plicease@cpan.org>

COPYRIGHT AND LICENSE

    This software is copyright (c) 2026 by Graham Ollis.

    This is free software; you can redistribute it and/or modify it under
    the same terms as the Perl 5 programming language system itself.

