Skip to main content

semql-engine

In-process executor for semql FederatedPlan results. Runs each per-backend fragment via a caller-supplied Adapter, materialises the rows into in-memory DuckDB, then runs the plan's merge SQL against the assembled tables.

semql core stays sans-io. semql-engine is the opt-in package that turns a FederatedPlan into result rows when you want the cross-source execution done for you.

Install

pip install semql-engine

Quickstart

import duckdb
from semql import Catalog, Dialect, compile_federated_query
from semql_engine import DuckDBAdapter, Engine

catalog = Catalog([...])  # cubes spanning multiple backends
plan = compile_federated_query(query, catalog.as_dict())

engine = Engine()
engine.register(Dialect.POSTGRES, my_pg_adapter)
engine.register(Dialect.BIGQUERY, my_bq_adapter)
result = engine.run(plan)
result.columns  # ['region', 'revenue', ...]
result.rows     # [(...), ...]   — or stream with engine.iter_rows(plan)

What it does

For every fragment in the plan, the engine calls the adapter registered for that backend with (sql, params). It loads the resulting rows into a DuckDB table named frag_<i> (matching FederatedPlan.fragments indices) and finally applies plan.merge_spec — rendered to DuckDB SQL by semql_engine.merge — to produce the merged shape.

Single-fragment plans (single-backend queries that went through compile_federated_query anyway) work transparently — the merge is a pass-through.

Execution integrity and analysis

Both synchronous and asynchronous execution validate executable versions and every fragment's final binding manifest before any source adapter is called. The synchronous cache path performs that preflight before lookup as well. Merge bindings are validated before merge execution. Missing/renamed parameters, unsupported versions, and mismatched output columns raise ExecutionContractError, catchable as both EngineError and core ContractError. Driver exceptions are not silently converted into successful results.

result.analysis belongs to the current artifact, including on cache hits. result.validation_evidence describes materialized merge-key checks actually performed; a cache hit does not invent fresh validation evidence. Iterator results expose their own analysis and validation_evidence, not shared per-engine state. The async engine does not provide a result cache.

Declared one-side merge keys are checked together on the exact materialized fragment consumed by the merge, respecting its null-key semantics. Observed duplicates and uncheckable explicit materialized-key obligations reject. Underlying database keys/cardinalities that cannot be inspected remain trusted catalog assumptions; the executor does not run a separate database-probing service or require host waivers for those declarations.

Internal exception/artifact dumps may include private information. Use error.to_public_payload() for routine external diagnostics. Recompile stored plans with unsupported executable versions rather than changing their version number by hand.

Adapters

An Adapter is anything with execute(sql, params) -> AdapterResult. AdapterResult carries columns: list[str], positional rows, and optional column_types: list[str | None], aligned exactly with the columns. Physical type declarations preserve numeric types, including decimal precision/scale, even when a fragment is empty or its values are all NULL. Built-ins:

  • DuckDBAdapter(con) — runs the SQL inside an existing DuckDB connection. Useful for local CSV / Parquet enrichment cubes.
  • DBAPIAdapter(con) — wraps any PEP-249 connection (psycopg, mysql, sqlite, etc).

Bring your own for warehouses that need a vendor SDK.

Built-in adapters retain driver schema metadata where available. Custom adapters should supply supported physical type names such as INTEGER, DOUBLE, or DECIMAL(12,2); common BigQuery/ClickHouse names are normalized for DuckDB. Missing declarations can be inferred only from supported non-NULL values. An empty/all-NULL undeclared column raises physical_schema_missing; unsupported declared types raise physical_schema_unsupported rather than silently materializing numeric data as text.

DB-API decimal precision/scale is retained when supplied. An unbounded NUMERIC declaration without those parameters is unknown, not a fixed BigQuery decimal; inference still requires observed values. Python float type codes map to DOUBLE so their mantissa is not narrowed.

DuckDB materialization accepts decimal precision up to 38 digits. Higher declared precision (including BigQuery's default BIGNUMERIC(76,38)) and over-precision legacy Decimal values are rejected explicitly, not narrowed to an integer or rounded to fit.

The engine owns acquired row iterators and closes closeable iterators after materialization, exhaustion, errors, or explicit stream closure. It does not close caller-owned adapter connections. Async fragment failure or caller cancellation cancels and drains request-owned tasks before returning; an already-running asyncio.to_thread worker cannot be forcibly stopped, so sync drivers still need their own query timeouts/cancellation policy.

Cached rows isolate nested list/dict/tuple cells both on insertion and cache hits. Mutating a miss result or a later hit cannot change the cached value. Uncached execution does not perform these cache-isolation copies.

Semi-joins

A cross-backend semi-join (restrict an outer dimension to the value set of an inner query, shipped as a value list rather than a join) compiles to a SemiJoinPlan via semql.compile_semi_join_query. Run it with run_semi_join(plan, engine) — it executes each inner plan, projects the key column to a value list, and runs the outer query with that list bound as an IN / NOT IN filter:

from semql import compile_semi_join_query
from semql_engine import Engine, run_semi_join

plan = compile_semi_join_query(query, catalog.as_dict())
result = run_semi_join(plan, engine)  # same ExecutionResult shape as run()

Scope

The supported strategy is explicit:

  • Distributive federation supports existing legal recipes, including SUM of partial counts and AVG recomposed from partial sums and non-null counts.
  • Explicit mode="raw_rows" preserves existing raw-row aggregation recipes, including distinct counts, min/max, percentiles, and catalog-declared ratios. The compiler never silently switches to this more expensive strategy.
  • Multi-backend inline-derived measures reject instead of disappearing.
  • Aliases and requested output order survive the final merge.
  • Symmetric multi-fact aggregation uses observed qualifying facts, not an implicit full dimension universe. Keys present in only one fact, including qualifying all-null fact rows, remain; keys absent from all facts do not.
  • Cross-backend joins retain the existing supported equality-key restrictions. Cross-backend comparison/fill and independently planned new operators are not added by the semantic-contract release.

The engine itself is small; most of the federation logic lives in semql.federate.

Backend conformance tests

From the workspace root, with Docker running:

uv sync --python 3.12 --all-extras --frozen
just test-conformance

This enables SEMQL_CONFORMANCE_TESTCONTAINERS=1. The existing result fixtures provision disposable PostgreSQL and ClickHouse containers on dynamically mapped ports, execute emitted SQL without rewriting, and stop the containers afterward. Startup or readiness failures fail the requested run rather than silently skipping. DuckDB runs in-process and does not require Docker.

SEMQL_CONFORMANCE_POSTGRES (a psycopg DSN) and SEMQL_CONFORMANCE_CLICKHOUSE (an HTTP URL) override provisioning per backend, allowing the same fixtures to target externally managed disposable services. Without the Testcontainers opt-in or an explicit endpoint, external-backend cells skip in the ordinary local suite. The full-suite CI job enables Testcontainers.

Status

Pre-v1. Compiler and executor packages release in lockstep. The first-release semantic contract and verification inventory are recorded in compiler-semantic-contract-2026-10-01.md.

Metadata

Release files for semql-engine 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for semql-engine 0.11.0
File Size Uploaded Provenance
semql_engine-0.11.0.tar.gz 33.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for semql-engine 0.11.0
File Interpreter ABI Platform
semql_engine-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 70.5 kB

Release files / semql_engine-0.11.0.tar.gz

Download URL semql_engine-0.11.0.tar.gz
Size 33.1 kB
Tags Source
SHA-256 checksum
How to use checksums
a66c5d0bb8a3b6326c03e49ea6972f5f689136b19b9656c57a63a6a601fe698c
BLAKE2b-256 checksum
How to use checksums
0e3c4387dc44c063e9b07882049ab8264e900fe1361c1119e8ee2c16f419d098
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / semql_engine-0.11.0-py3-none-any.whl

Download URL semql_engine-0.11.0-py3-none-any.whl
Size 37.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45125dd7973083ccb5bd6f2f77b43ed5c7a8afe41d5ca7dc4981b61ac215fdc9
BLAKE2b-256 checksum
How to use checksums
4842c872f888979cb548271a1426d0e04ca1fcca717ce831675189d3216198f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for semql-engine 0.11.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for semql_engine-0.11.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page