A SQL parser, transpiler and optimizer: a MoonBit port of sqlglot
let sql = @sqlglot.transpile(
"SELECT EPOCH_MS(1618088028295)",
read="duckdb",
write="hive",
)[0]
// SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3))
let ast = @sqlglot.parse_one("SELECT a FROM t WHERE b > 1", read="postgres")
let out = @sqlglot.generate(ast, dialect="snowflake", pretty=true)| Component | Status |
|---|---|
| Tokenizer, parser, generator (base dialect) | Complete: identical ASTs and SQL to Python on all base fixtures (5,501 ASTs, 5,962 round trips) |
| Dialects (34) | Complete: all 16,070 Validator cases and 75 error cases (class and message) extracted from tests/dialects match Python; the direct assertions of all 249 test methods that make them are hand-ported (src/dialect_unit_tests, coverage in docs/dialect-test-manifest.md) |
| Optimizer (qualify, annotate_types, simplify, all rules), schema | Complete: all optimizer fixtures, TPC-H and TPC-DS match Python, including error classes and messages |
| Lineage, diff, planner | Complete: lineage 80/80, diff 24/24, planner 27/27 recorded Python results, plus hand-ported identity, callback, copy= and matchings tests |
| Expression API, builders, transforms | Complete: unit tests ported from test_expressions, test_build, test_transforms, test_parser, test_transpile, test_errors, test_tokens, test_jsonpath and others |
| Executor | Complete: generates Python code like sqlglot and evaluates it with a built-in interpreter; test_executor (445 recorded cases) and 57 TPC-DS queries match Python |
| Serde, anonymize, CLI | Complete (src/core/serde.mbt, src/anonymize, src/cli, native binary in src/cmd/sqlglot) |
moon check
moon test -p bobzhang/sqlglot/tests # base parser/generator conformance
moon test -p bobzhang/sqlglot/generator_tests # generator conformance
moon test -p bobzhang/sqlglot/dialect_tests # per-dialect conformance (prints DIALECT <module>: ...)
moon test -p bobzhang/sqlglot/dialect_tests -F "*dialect snowflake*"
moon test # everything (845 tests)moon run src/bench --release [--target native] -- [case] [op] [size]
moon run src/bench --release -- many_ctes rules 512 # per optimizer ruleimport {
"bobzhang/sqlglot",
}///|
test "transpile between dialects" {
inspect(
@sqlglot.transpile(
"SELECT EPOCH_MS(1618088028295)",
read="duckdb",
write="hive",
)[0],
content="SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3))",
)
inspect(
@sqlglot.transpile(
"SELECT STRFTIME(x, '%y-%-m-%S')",
read="duckdb",
write="hive",
)[0],
content="SELECT DATE_FORMAT(x, 'yy-M-ss')",
)
}///|
test "generator options" {
inspect(
@sqlglot.transpile("SELECT a FROM t WHERE b = 1", write="spark", identify=Always)[0],
content="SELECT `a` FROM `t` WHERE `b` = 1",
)
inspect(
@sqlglot.transpile(
"SELECT cardinality(x) FROM t",
read="presto",
write="presto",
normalize_functions=Lower,
)[0],
content="SELECT cardinality(x) FROM t",
)
inspect(
@sqlglot.transpile(
"WITH baz AS (SELECT a, c FROM foo WHERE a = 1) SELECT f.a, b.b, baz.c, CAST(\"b\".\"a\" AS REAL) d FROM foo f JOIN bar b ON f.a = b.a LEFT JOIN baz ON f.a = baz.a",
write="spark",
identify=Always,
pretty=true,
)[0],
content=(
#|WITH `baz` AS (
#| SELECT
#| `a`,
#| `c`
#| FROM `foo`
#| WHERE
#| `a` = 1
#|)
#|SELECT
#| `f`.`a`,
#| `b`.`b`,
#| `baz`.`c`,
#| CAST(`b`.`a` AS FLOAT) AS `d`
#|FROM `foo` AS `f`
#|JOIN `bar` AS `b`
#| ON `f`.`a` = `b`.`a`
#|LEFT JOIN `baz`
#| ON `f`.`a` = `baz`.`a`
),
)
}///|
test "parse, inspect and generate" {
let ast : @sqlglot.Expr = @sqlglot.parse_one(
"SELECT a, b + 1 AS c FROM t WHERE a > 1",
)
inspect(
ast.find_all([Column]).map(c => c.name()).collect().join(", "),
content="a, b, a",
)
inspect(
@sqlglot.generate(ast, dialect="spark"),
content="SELECT a, b + 1 AS c FROM t WHERE a > 1",
)
}
///|
test "parse errors" {
try @sqlglot.parse_one("SELECT foo FROM (SELECT baz FROM t") catch {
@sqlglot.SqlglotError::ParseError(_, errors) => {
let e = errors[0]
inspect("\{e.description} at \{e.line}:\{e.col}", content="Expecting ) at 1:34")
}
_ => fail("expected a parse error")
} noraise {
_ => fail("expected a parse error")
}
}///|
test "build queries" {
let columns : Array[&@sqlglot.IntoPy] = [@sqlglot.column("a"), "b + 1 AS c"]
let base = @sqlglot.select(columns).from_("t")
let q1 = base.where_(["a > 1"])
let conditions : Array[&@sqlglot.IntoPy] = [
@sqlglot.condition("a < 0"),
"c IS NOT NULL",
]
let q2 = base
.where_([@sqlglot.and_(conditions)])
.order_by(["c"])
.limit_(10)
inspect(@sqlglot.generate(base), content="SELECT a, b + 1 AS c FROM t")
inspect(@sqlglot.generate(q1), content="SELECT a, b + 1 AS c FROM t WHERE a > 1")
inspect(
@sqlglot.generate(q2),
content="SELECT a, b + 1 AS c FROM t WHERE a < 0 AND NOT c IS NULL ORDER BY c LIMIT 10",
)
let x = @sqlglot.column("x").eq_(1).or_(["y = 2"])
inspect(
@sqlglot.generate(
@sqlglot.select(["x"]).from_("tbl").where_([x]),
dialect="duckdb",
),
content="SELECT x FROM tbl WHERE x = 1 OR y = 2",
)
}///|
test "optimize" {
let schema : Map[String, @sqlglot.SchemaNode] = {
"x": Dict({
"A": Type("INT"),
"B": Type("INT"),
"C": Type("INT"),
"D": Type("INT"),
"Z": Type("STRING"),
}),
}
let optimized = @sqlglot.optimize(
"SELECT A OR (B OR (C AND D)) FROM x WHERE Z = date '2021-01-01' + INTERVAL '1' month OR 1 = 0",
schema~,
)
inspect(
@sqlglot.generate(optimized, pretty=true),
content=(
#|SELECT
#| (
#| "x"."a" <> 0 OR "x"."b" <> 0 OR "x"."c" <> 0
#| )
#| AND (
#| "x"."a" <> 0 OR "x"."b" <> 0 OR "x"."d" <> 0
#| ) AS "_col_0"
#|FROM "x" AS "x"
#|WHERE
#| CAST("x"."z" AS DATE) = CAST('2021-02-01' AS DATE)
),
)
}///|
test "execute" {
let tables : Map[String, @sqlglot.TableData] = {
"sushi": Records([[("id", Int(1)), ("price", Float(1.0))], [("id", Int(2)), ("price", Float(2.0))]]),
"order_items": Records([
[("sushi_id", Int(1)), ("order_id", Int(1))],
[("sushi_id", Int(1)), ("order_id", Int(1))],
[("sushi_id", Int(2)), ("order_id", Int(1))],
[("sushi_id", Int(2)), ("order_id", Int(2))],
]),
"orders": Records([
[("id", Int(1)), ("user_id", Int(1))],
[("id", Int(2)), ("user_id", Int(2))],
]),
}
let result : @sqlglot.Table = @sqlglot.execute(
(
#|SELECT o.user_id, SUM(s.price) AS price
#|FROM orders o
#|JOIN order_items i ON o.id = i.order_id
#|JOIN sushi s ON i.sushi_id = s.id
#|GROUP BY o.user_id
#|ORDER BY o.user_id
),
tables~,
)
inspect(result.columns.join(", "), content="user_id, price")
inspect(
result,
content=(
#|user_id price
#| 1 4.0
#| 2 2.0
),
)
}fn[T : IntoPy, A : IntoPy] alias_(expression : T, alias : A, table? : Bool, table_columns? : Array[String], quoted? : Bool, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] and_(expressions : Array[T], dialect? : String, copy? : Bool, wrap? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] array(expressions : Array[T], copy? : Bool, dialect? : String) -> Expr raise SqlglotErrorfn[T : IntoPy, D : IntoPy] cast(expression : T, to : D, copy? : Bool, dialect? : String) -> Expr raise SqlglotErrorfn[T : IntoPy] condition(expression : T, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] datatype_build(dtype : T, dialect? : String, udt? : Bool, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] datatype_is_type(dt : Expr, dtypes : Array[T], check_nullable? : Bool) -> Bool raise SqlglotErrorfn[T : IntoPy] delete(table : T, where_? : &IntoPy, returning? : &IntoPy, dialect? : String) -> Expr raise SqlglotErrorfn[T : IntoPy] except_(expressions : Array[T], distinct? : Bool, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] execute(sql : T, schema? : Map[String, SchemaNode], mapping_schema? : MappingSchema, read? : String, dialect? : String, tables? : Map[String, TableData]) -> Table raisefn expand(expression : Expr, sources : Map[String, () -> Expr raise SqlglotError], dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn generate(expression : Expr, dialect? : String, copy? : Bool, pretty? : Bool, identify? : Identify, normalize? : Bool, pad? : Int, indent? : Int, normalize_functions? : NormalizeFunctions, unsupported_level? : ErrorLevel, max_unsupported? : Int, leading_comma? : Bool, max_text_width? : Int, comments? : Bool) -> String raise SqlglotErrorfn[T : IntoPy] intersect(expressions : Array[T], distinct? : Bool, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] is_type(expression : Expr, dtypes : Array[T], check_nullable? : Bool) -> Bool raise SqlglotErrorfn[T : IntoPy] maybe_parse(sql_or_expression : T, into? : Array[Kind], dialect? : String, prefix? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] normalize_table_name(table : T, dialect? : String, copy? : Bool) -> String raise SqlglotErrorfn[T : IntoPy] optimize(expression : T, schema? : Map[String, SchemaNode], mapping_schema? : MappingSchema, db? : String, catalog? : String, dialect? : String, infer_schema? : Bool, identify? : Bool, leave_tables_isolated? : Bool, validate_qualify_columns? : Bool, expand_stars? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] or_(expressions : Array[T], dialect? : String, copy? : Bool, wrap? : Bool) -> Expr raise SqlglotErrorfn parse(sql : String, read? : String, error_level? : ErrorLevel, error_message_context? : Int, max_errors? : Int, max_nodes? : Int) -> Array[Expr?] raise SqlglotErrorfn parse_one(sql : String, read? : String, into? : Kind, into_any? : Array[Kind], error_level? : ErrorLevel, error_message_context? : Int, max_errors? : Int, max_nodes? : Int) -> Expr raise SqlglotErrorfn register_dialects() -> Unitfn[T : IntoPy, O : IntoPy, N : IntoPy] rename_column(table_name : T, old_column_name : O, new_column_name : N, exists? : Bool, dialect? : String) -> Expr raise SqlglotErrorfn[O : IntoPy, N : IntoPy] rename_table(old_name : O, new_name : N, dialect? : String) -> Expr raise SqlglotErrorfn replace_tables(expression : Expr, mapping : Map[String, String], dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] select(expressions : Array[T], dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] subquery(expression : T, alias? : String, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn[T : IntoPy] table_name(table : T, dialect? : String, identify? : Bool) -> String raise SqlglotErrorfn[T : IntoPy] to_column(sql_path : T, quoted? : Bool, dialect? : String, copy? : Bool, kwargs? : Map[String, Value]) -> Expr raise SqlglotErrorfn[T : IntoPy] to_table(sql_path : T, dialect? : String, copy? : Bool, kwargs? : Map[String, Value]) -> Expr raise SqlglotErrorfn transpile(sql : String, read? : String, write? : String, identity? : Bool, error_level? : ErrorLevel, error_message_context? : Int, max_errors? : Int, max_nodes? : Int, pretty? : Bool, identify? : Identify, normalize? : Bool, pad? : Int, indent? : Int, normalize_functions? : NormalizeFunctions, unsupported_level? : ErrorLevel, max_unsupported? : Int, leading_comma? : Bool, max_text_width? : Int, comments? : Bool) -> Array[String] raise SqlglotErrorfn[T : IntoPy] tuple_(expressions : Array[T], copy? : Bool, dialect? : String) -> Expr raise SqlglotErrorfn[T : IntoPy] union(expressions : Array[T], distinct? : Bool, dialect? : String, copy? : Bool) -> Expr raise SqlglotErrorfn values(values : Array[PyObj], alias? : String, columns? : Array[String]) -> Expr raise SqlglotErrorfn[T : IntoPy] xor(expressions : Array[T], dialect? : String, copy? : Bool, wrap? : Bool) -> Expr raise SqlglotErrorInstall
Download zipA SQL parser, transpiler and optimizer: a MoonBit port of sqlglot