Polyglot SQL Python API

polyglot-sql exposes the Rust polyglot-sql engine to Python via PyO3.

Install

pip install polyglot-sql

Quick Start

Transpile SQL between dialects

import polyglot_sql

polyglot_sql.transpile(
    "SELECT IFNULL(a, b) FROM t",
    read="mysql",
    write="postgres",
)
# ["SELECT COALESCE(a, b) FROM t"]

Parse, inspect, and generate

ast = polyglot_sql.parse_one("SELECT a AS x, b FROM t WHERE c > 1", dialect="postgres")

# Type dispatch
isinstance(ast, polyglot_sql.Select)  # True

# Property access
ast.expressions          # [Alias(Column(a) AS x), Column(b)]
ast.find(polyglot_sql.Column).name  # "a"

# Generate SQL for a different dialect
ast.sql("mysql")         # "SELECT a AS x, b FROM t WHERE c > 1"

Parse standalone data types

data_type = polyglot_sql.parse_data_type("DECIMAL(10, 2)", dialect="duckdb")
data_type.sql("postgres")  # "DECIMAL(10, 2)"

# SQLGlot-compatible shorthand, limited to DataType.
same = polyglot_sql.parse_one(
    "VARCHAR(255)",
    dialect="duckdb",
    into=polyglot_sql.DataType,
)
same.sql("postgres")  # "VARCHAR(255)"

Analyze query summary facts

analysis = polyglot_sql.analyze_query(
    "WITH base AS (SELECT id, amount FROM orders) SELECT * FROM base",
    {
        "dialect": "generic",
        "schema": {
            "tables": [
                {
                    "name": "orders",
                    "columns": [
                        {"name": "id", "type": "INT", "nullable": False},
                        {"name": "amount", "type": "DECIMAL(10,2)", "nullable": True},
                    ],
                }
            ]
        },
    },
)
analysis["cteFacts"][0]["bodySql"]           # "SELECT id, amount FROM orders"
analysis["starProjections"][0]["expandedColumns"]  # ["id", "amount"]
analysis["projections"][0]["nullability"]    # "non_null"
analysis["baseTables"][0]["name"]            # "orders"
analysis["baseTables"][0]["table"]           # "orders"

relations reports sources visible in the analyzed scope. baseTables reports deduplicated physical table dependencies across nested scopes. For physical relation facts, name remains the qualified display name while catalog, schema, and table expose parsed identifier parts. cteFacts reports top-level CTE definitions, starProjections records original star projections and schema-expanded columns, and each projection has conservative nullability: "non_null", "nullable", or "unknown".

Validation schema dictionaries use this shape:

schema = {
    "strict": True,
    "tables": [
        {
            "name": "orders",
            "schema": "analytics",
            "aliases": ["o"],
            "primaryKey": ["id"],
            "uniqueKeys": [["external_id"]],
            "foreignKeys": [
                {
                    "columns": ["customer_id"],
                    "references": {"table": "customers", "columns": ["id"]},
                }
            ],
            "columns": [
                {"name": "id", "type": "INT", "nullable": False, "primaryKey": True},
                {"name": "amount", "type": "DECIMAL(10,2)", "nullable": True},
            ],
        }
    ],
}

Use the type key for column types. dataType / data_type are not accepted aliases in this payload.

Traverse the AST

ast = polyglot_sql.parse_one("SELECT a + b AS total, c FROM t")

# Find specific node types
columns = ast.find_all(polyglot_sql.Column)
for col in columns:
    print(col.name)  # "a", "b", "c"

# Walk the entire tree
for node in ast.walk():
    print(node.kind, node.name)

# Access parent chain
col = ast.expressions[0].this.this  # Column inside Add inside Alias
col.parent_select.kind  # "select"

Flatten conditions

ast = polyglot_sql.parse_one("SELECT * FROM t WHERE a AND b AND c")
and_node = ast.find(polyglot_sql.And)
conditions = and_node.flatten()
# [Column(a), Column(b), Column(c)]

Format SQL

polyglot_sql.format_sql(
    "SELECT a,b,c FROM t WHERE x>1 AND y<2",
    dialect="postgres",
)
# "SELECT\n  a,\n  b,\n  c\nFROM t\nWHERE\n  x > 1\n  AND y < 2"

Formatting Guard Overrides

format_sql enforces parser/AST limits by default and supports per-call overrides.

sql = polyglot_sql.format_sql(
    "SELECT 1 UNION ALL SELECT 2",
    dialect="generic",
    max_set_op_chain=1024,
    max_input_bytes=32 * 1024 * 1024,
)

Validate SQL

result = polyglot_sql.validate("SELCT 1", dialect="postgres")
result.valid   # False
for err in result.errors:
    print(f"Line {err.line}, Col {err.col}: {err.message}")

Dialects

Use polyglot_sql.dialects() to retrieve supported dialect names at runtime.

polyglot_sql.dialects()
# ["athena", "bigquery", "clickhouse", "databricks", "doris", "drill",
#  "duckdb", "generic", "hive", "materialize", "mysql", "oracle",
#  "postgres", "presto", "redshift", "snowflake", "spark", "sqlite",
#  "starrocks", "tableau", "teradata", "trino", "tsql", ...]