Development¶
Building from source¶
The project uses maturin to build the Python wheel from the Rust crate:
pip install maturin
maturin develop --release # install into the active venv
maturin build --release # produce a wheel in target/wheels/
Running the tests¶
Run the full Rust test suite:
Run specific test groups:
cargo test --lib # unit tests
cargo test --test compiler_tests # compiler golden tests (all engines)
cargo test --test parser_tests # parser golden tests (all engines)
cargo test --test verifier_tests # verifier tests (all engines)
cargo test --test search_tests # search feature tests (all engines)
The repository also ships a shell/test.sh orchestrator (run it from anywhere — it cd's to the repo root):
shell/test.sh # run all tests
shell/test.sh unit # unit tests only (fast)
shell/test.sh golden # golden reference tests only (parser + compiler)
shell/test.sh e2e # end-to-end tests against live SQL engines
shell/test.sh quick # unit + parser golden (no SQL compilation)
Python steps run through uv run, so the project virtualenv supplies pytest and the engine drivers. The e2e step builds the extension into the venv and starts the server engines (PostgreSQL, Trino, Presto, and a Spark Thrift Server standing in for Databricks) itself via Docker Compose.
Golden test generation¶
Golden SQL files are generated by the Python Logica compiler to serve as the reference:
cd tests/compiler_tests && python3 generate_expected_sql.py
cd tests/parser_tests && python3 generate_expected_json.py
Requires pip install logica.
Project structure¶
src/
lib.rs # Public API: parser, compiler, verifier, errors
errors.rs # Unified error types with help messages
python.rs # PyO3 bindings (the _synalog extension module)
parser/
parse.rs # Logica syntax -> JSON AST
rewrite.rs # AST rewrites (aggregation, multi-body)
json.rs # Custom JSON implementation
compiler/
universe.rs # LogicaProgram: AST -> SQL compilation
annotations.rs # @OrderBy, @Limit, @Recursive, etc.
dialects.rs # Engine-specific SQL generation
expr_translate.rs # Expression -> SQL translation
rule_translate.rs # Rule -> SQL translation
functors.rs # Functor expansion (@Make)
concertina.rs # Multi-predicate execution orchestration
type_inference/ # Type checking subsystem
verifier/
mod.rs # Validation entry point
safety.rs # Variable binding checks
stratification.rs # Negative cycle detection
arity.rs # Argument count consistency
recursion.rs # Recursion safety checks
reserved.rs # Reserved predicate name check
python/
synalog/__init__.py # Python package wrapper
synalog/cli.py # The synalog command (one-shot + REPL)
synalog/runners.py # Local SQL runners (duckdb, sqlite, psql)
tests/
compiler_tests/ # Golden SQL tests per engine
parser_tests/ # Golden JSON tests per engine
verifier_tests/ # Negative verification tests per engine
cli/ # CLI tests (pytest)
search_tests.rs # Search feature integration tests
e2e/ # End-to-end tests against live engines
Releasing¶
Publishing to PyPI is automated by .github/workflows/release.yml, which triggers on a version tag:
The workflow builds abi3 wheels (cp310-abi3-*, one wheel per platform/arch covers every Python ≥ 3.10) plus free-threaded (python3.14t) wheels for Linux (glibc and musl), Windows and macOS, builds the sdist, attests build provenance, and uploads everything with maturin upload --skip-existing. The regular CI.yml workflow builds the same wheels on every push to validate them, but never publishes.
One-time setup on the GitHub repository:
- Create an Environment named
pypi(Settings → Environments), ideally restricted to tags matchingv*. - Add the
PYPI_API_TOKENsecret on that environment.
The release can also be launched manually from the Actions UI (workflow_dispatch); only tag builds reach the publish step.
Documentation¶
The documentation site is built with Zensical:
pip install zensical
zensical serve # live preview at http://localhost:8000
zensical build # static site in site/
Or use the all-in-one script, which installs the toolchain, rebuilds the crate, regenerates the documentation examples and starts the live preview:
It deploys to GitHub Pages automatically on push via .github/workflows/docs.yml.
Documentation examples¶
Every "Complete example" on the documentation site lives in docs/examples/ as a .l program. The run.py harness validates each program with synalog.check(), compiles every predicate listed in its # run: header, executes the SQL on an in-memory DuckDB, and writes the combined output to the matching .log file. A # load: <file>.csv as <table> header loads a CSV from docs/examples/ into a DuckDB table before the predicates run. The Today/Now built-in concepts need no special handling — the compiler inlines them per dialect:
python3 docs/examples/run.py # all examples
python3 docs/examples/run.py recursion # a single example
The pages include both files via snippets, so every example shown on the site is guaranteed to validate, compile and run. After changing an example (or the compiler), re-run the harness and commit the regenerated .log files.