Skip to content

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:

cargo test

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:

git tag v0.1.0
git push --tags

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:

  1. Create an Environment named pypi (Settings → Environments), ideally restricted to tags matching v*.
  2. Add the PYPI_API_TOKEN secret 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:

./shell/doc.sh

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.