Embeddable analytical query engine for MoonBit: run SQL over CSV/Parquet files, native and WebAssembly from one codebase.
Status: all 22 TPC-H queries pass on official texts. The evaluator is columnar — vectorized expression kernels, a columnar hash join, and bind-time projection pruning — but APIs may still change.
uv run --with duckdb python harness/gen_tpch.py # TPC-H SF0.01 data + goldens
bash harness/check_all.sh # 33 goldens: all PASS (22 TPC-H + parquet + encoding variants)
MOONLAKE_DATA=harness/data/sf01 bash harness/check_all.sh # same at SF0.1 (regenerate first: gen_tpch.py 0.1)| query | moonlake native | duckdb |
|---|---|---|
| Pricing Summary Report | 183 ms | 3.3 ms |
| Shipping Priority | 142 ms | 4.1 ms |
| Local Supplier Volume | 151 ms | 4.1 ms |
| Forecasting Revenue Change | 124 ms | 0.8 ms |
| Volume Shipping | 166 ms | 4.7 ms |
| Returned Item Reporting | 143 ms | 7.4 ms |
| Shipping Modes and Order Priority | 147 ms | 3.3 ms |
| Promotion Effect | 122 ms | 1.4 ms |
git clone https://github.com/superbigcup325/moonlake && cd moonlake
moon update
moon run cmd/main -- exec --csv harness/data/sf001/lineitem.csv \
"SELECT l_returnflag, sum(l_extendedprice) FROM lineitem WHERE l_shipdate <= date '1998-09-02' GROUP BY l_returnflag ORDER BY l_returnflag"
# l_returnflag|sum_2
# A|532348211.6499983
# ...moon build cmd/main --target native --release
install -m755 _build/native/release/build/cmd/main/main.exe ~/.local/bin/moonlake
moonlake exec --csv lineitem.csv --file q6.sql # SQL from a file: no shell quotingmoon add superbigcup325/moonlakeThis README and the browser playground track main. moon add gives you the latest published release from mooncakes (0.6.0): the build-side index that is one row-id range instead of a list per key, the chain of joins that composes with or without a budget, and the column-wise group state that makes a million groups of a sum peak at 117 MB instead of 323 MB, with the measurements in verification.md §52, §54 and §55. Everything since 0.5.1 is in it. On main since then, two changes to how a chain of joins holds memory: each step hands its output to the next without copying it into one batch (q21 at SF1 peaks 58 MB lower, verification §65), and a step lets go of the tables it has finished with instead of holding them to the end (another 60 MB, §66). Also on main: the scan reads a field's end a word at a time and a batch is 2048 rows rather than 4096, which is worth about 10% of wall time on the TPC-H queries at SF1 (§67). The next release carries all of it and no version is claimed for it yet.
moon update # refresh the registry index; the graph downloads on the first check
moon checkln -s ../colenc .colenc
printf 'members = [".", "./.colenc"]\n' > moon.work// a @catalog.Catalog with registered table entries — @csv/@parquet
// scan into one; note the argument order: content/name, not name/content.
// registration is lazy: the schema is available at once, and a query decodes
// only the columns it projects, in 4096-row batches
///|
fn main raise {
let cat = @catalog.Catalog::new()
cat.register(@csv.scan_string("region,amount\nnorth,100\n", "events"))
// read results back cell by cell: names() gives the columns,
// row_count() the rows, get(row, col) a @types.Scalar (null via Null)
let result = @moonlake.execute(
"SELECT region, count(*) FROM events GROUP BY region", cat,
)
for r in 0..<result.row_count() {
println(result.get(r, 0).to_string() + "|" + result.get(r, 1).to_string())
}
}///|
fn main raise {
let cat = @catalog.Catalog::new()
cat.register(@csv.scan_string("id,name\n1,ada\n2,grace\n", "people"))
let stream = @moonlake.stream("SELECT id FROM people", cat)
println(stream.names()) // ["id"]
while stream.next() is Some(batch) {
for r in 0..<batch.row_count() {
println(batch.columns()[0].get(r).to_string())
}
}
}| project | what it is | boundary with moonlake |
|---|---|---|
| moonbit-community/sqlparser | SQL lexer/parser (AST) | parsing only, no execution; moonlake runs on its own in-house front-end (lexer, parser, AST in the in-repo sql package) and that is the roadmap, not a placeholder: sqlparser's select-statement AST is not destructurable cross-package, so the relationship is upstream cooperation (issues/PRs), not a planned swap-in |
| apache/parquet-format | Parquet format and encoding specs | moonlake implements the decoding itself (no dependency): the reader follows the specs and is diffed against DuckDB answers, so the format work is in-repo while the specification stays upstream |
| shunge/arrow (MoonArrow) | Arrow IPC format read/write | memory-format interchange; a future to_arrow bridge is cooperation, not competition |
| uiwcvb/moonsql | embedded OLTP database (row storage, CRUD, persistence) | different species — SQLite to moonlake's DuckDB: transactional storage vs external-file analytics |
| f4ah6o/duckdb, mizchi/duckdb | DuckDB C++ bindings | FFI route: the wasm-gc target is a stub upstream and the binding build is unstable; moonlake is pure MoonBit and runs natively in the browser |
moon check # type-check
moon test # run tests on the preferred target (wasm-gc)
moon test --target native # filesystem/native backend tests
moon fmt # format
moon run cmd/main # run the CLI
git config core.hooksPath hooks # once per clone: pre-commit gatelet VERSION : Stringlet s = @moonlake.stream("SELECT * FROM t LIMIT 3", cat)
while s.next() is Some(batch) {
inspect(batch.row_count())
}Install
Download zipEmbeddable analytical query engine for MoonBit: run SQL over CSV/Parquet files, native and WebAssembly from one codebase.