Grainlift ADBC gateway
The Grainlift ADBC gateway exposes a database through the standard ADBC client
API. Its PyPI distribution, grainlift-adbc-gateway, contains the Rust server executable and depends on Apache's SQLite
ADBC wheel. Python locates that library and launches the native server; query
execution stays in Rust and the downstream driver.
On Unix the launcher replaces itself with the server. On Windows it waits for
the native child and lets console Ctrl-C initiate the server's graceful
shutdown; service managers that forcibly stop it must terminate the process tree.
This is not the grainlift package on
PyPI, which is the Python toolkit for writing your own Grainlift workers. Clients
of either connect through the adbc-driver-grainlift driver.
Serve a SQLite file
Run it straight from PyPI with uv:
uvx grainlift-adbc-gateway serve sqlite ./database.sqlite
To create a database intentionally:
uvx grainlift-adbc-gateway serve sqlite ./database.sqlite --create
The service listens on 127.0.0.1:8080 and exposes target sqlite. It creates
.grainlift-token in the current directory with a random 256-bit bearer token,
or reuses that file on subsequent starts. Tokens are never printed. On Unix,
new token files use mode 0600 and existing files must have no group/other access.
On Windows, restrict the containing directory's ACL to the service account.
Existing token files must be regular files, not symlinks, at most 4096 bytes,
and contain at least 32 non-whitespace ASCII characters (a final newline is OK).
Keep token files out of source control.
uvx grainlift-adbc-gateway serve sqlite ./database.sqlite \
--listen 127.0.0.1:9400 --token-file ./private/service-token
Database filenames are treated as filesystem paths, not arbitrary SQLite URIs.
Existing files are opened with mode=rw; missing files require --create.
The parent directories for database and token files must already exist.
SQLite's ordinary transaction and writer-locking constraints still apply.
This exposes SQL access under the server account; it is not a SQL or filesystem
sandbox. Only grant the token to callers trusted to use that access.
Clients use the native Grainlift ADBC driver with:
grainlift.uri:grainlift+http://127.0.0.1:8080grainlift.target:sqlitegrainlift.auth.bearer_token: the contents of the token file, stripped of its newline
Clients cannot override the configured database URI. The shorthand only allows loopback HTTP; use the existing server configuration for remote deployments, TLS termination, mTLS, Iroh, multiple targets, JWT authentication, and quotas.
Create an Iroh identity
The Rust CLI creates identities for both servers and clients:
grainlift-adbc-gateway identity create alice.key > alice.id
grainlift-adbc-gateway identity show alice.key
create writes a new 32-byte Ed25519 private key as 64 hexadecimal characters
and a newline. It prints only the derived public endpoint ID to stdout;
redirection above saves that shareable ID in alice.id. show derives the same
ID from an existing key, including keys created by the earlier Python helper.
These commands do not read server configuration, load a database driver, or
start a listener. The native grainlift-server accepts the same commands.
They are also available without installing through
uvx grainlift-adbc-gateway identity create alice.key.
Creation refuses to overwrite an existing file or symlink. It writes and syncs
a private temporary file before publishing the completed key without replacing
the destination, and removes the temporary file if publication fails. The
parent directory must exist. Unix key files have mode 0600; on Windows use a
directory whose ACL is restricted to the identity owner. Keep the key file
private and reuse it to retain a stable endpoint identity. Only share the
public ID. show accepts regular files up to 256 bytes and rejects symlinks
and group/other permissions on Unix. Neither command prints private keys.
For a server, use the key path as iroh.secret_key_file. For a client, use
grainlift.iroh.secret_key_file in the ADBC driver options and authorize its
public ID in the server's iroh.principals map. The file is read on the client
machine when opening an ADBC connection; neither its path nor contents are
forwarded as downstream database options. Prefer absolute paths. The file must
be regular, not a symlink, at most 256 bytes, and private on Unix (0600 or more
restrictive). An inline grainlift.iroh.secret_key remains supported, but
specifying both forms is an error. Existing connections retain their loaded
identity; a changed file takes effect on newly opened ADBC connections.
For a shared target that should accept any verified Iroh key, use
public_targets = ["sqlite"] under [iroh] instead of registering each client.
The server derives a distinct principal from each unlisted key and limits it
to those targets. Named mappings remain optional for additional privileges.
Keep the server's existing HTTP authentication configuration. Public targets
grant downstream database access to any verified Iroh peer, including writes
when the target allows them. See security for the full policy.
Connect from DuckDB
DuckDB can load the native Grainlift ADBC client through the published
adbc_scanner community extension.
The separate Python client wheel can supply the native
library path through adbc_driver_grainlift.driver_path() after installation.
The connection path is DuckDB → ADBC scanner → Grainlift client driver →
Grainlift server → SQLite ADBC driver. The server wheel contains the server;
the client also needs the separate Grainlift ADBC shared library.
Start the service as above, or from a locally built platform wheel:
uvx --from /path/to/grainlift_adbc_gateway-0.4.0-py3-none-<platform>.whl grainlift-adbc-gateway \
serve sqlite ./database.sqlite --create
In DuckDB, use an absolute client-library path for your platform (.dylib on
macOS, .so on Linux, or .dll on Windows) and the server's token-file path:
INSTALL adbc_scanner FROM community;
LOAD adbc_scanner;
-- Temporary workaround for side-effect folding in extension 7a21dda.
PRAGMA disable_optimizer;
SET VARIABLE gl = (
SELECT adbc_connect({
'driver': '/absolute/path/to/libadbc_driver_grainlift.dylib',
'entrypoint': 'AdbcDriverGrainliftInit',
'grainlift.uri': 'grainlift+http://127.0.0.1:8080',
'grainlift.target': 'sqlite',
'grainlift.auth.bearer_token': trim(content, chr(10) || chr(13))
}) FROM read_text('/absolute/path/to/.grainlift-token')
);
SELECT * FROM adbc_scan(getvariable('gl')::BIGINT, 'SELECT 42 AS answer');
SELECT * FROM adbc_tables(getvariable('gl')::BIGINT);
SELECT adbc_disconnect(getvariable('gl')::BIGINT);
Pass driver-specific options directly in the adbc_connect struct.
extra_options is a parameter of CREATE SECRET (... TYPE adbc, ...), not
an option container for adbc_connect.
The token is read from the file instead of embedded in SQL history. The SQL
string given to adbc_scan runs on the server; its result can be joined to local
DuckDB tables or files. Use adbc_execute for server-side DDL and DML.
Validated on EC2 Linux AArch64 with DuckDB 1.5.5, community extension 7a21dda,
and the CLI wheel from commit 4b3ac43: a 10,000-row scan, local join, table
discovery, insert, persisted-write check after shutdown, and the token-file SQL
example above all passed. This check used the published extension without
rebuilding DuckDB or the extension.
Further lock-contention tests found a bug in published extension 7a21dda:
EXPLAIN SELECT adbc_execute(...) executed a write, and a forced SQLite lock
timeout took approximately three times the configured busy timeout. Disabling
the DuckDB optimizer prevented the EXPLAIN write and restored the expected
timeout. The recipe includes that temporary session-wide workaround pending an
extension fix; it disables query optimizations in that DuckDB session.
Configuration and persistent installation
uv tool install grainlift-adbc-gateway
grainlift-adbc-gateway serve --config grainlift.toml
grainlift-adbc-gateway check --config grainlift.toml
check validates configuration and policy; it does not load drivers, connect to
databases, or start listeners. GRAINLIFT_CONFIG and GRAINLIFT_SERVER_ID work
as before. --config/GRAINLIFT_CONFIG cannot be combined with serve sqlite.
Malformed configuration diagnostics report a byte offset when available and
omit source text and key names because these can contain credentials.
For configuration-based targets, use installed driver names or explicit native
library paths as documented in the main README.
The standalone grainlift-server executable accepts the same commands. Its
existing grainlift-server --config grainlift.toml invocation is unchanged.
For SQLite shorthand outside the Python package, install the driver with
dbc install sqlite --level user, or supply --driver /path/to/library.
GRAINLIFT_SQLITE_DRIVER is the equivalent environment option. An explicit
--driver takes precedence over the environment and packaged driver.
Build and validate a wheel
Building from source requires Rust 1.97 or newer. End users of a supported platform wheel do not need a Rust toolchain.
uv build --wheel --out-dir dist
uvx --from ./dist/grainlift_adbc_gateway-0.4.0-py3-none-<platform>.whl grainlift-adbc-gateway --help
Use the actual wheel filename. Without --from, uvx grainlift-adbc-gateway
resolves the published PyPI package.
CI builds platform wheels, installs each wheel in an isolated environment,
and tests the CLI. Publishing requires a configured PyPI trusted publisher and
the explicit release workflow; building wheels does not publish them.
Metadata
Release files for grainlift-adbc-gateway 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| grainlift_adbc_gateway-0.4.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64 | Details |
| grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.28+ ARM64 | Details |
| grainlift_adbc_gateway-0.4.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 49.2 MB
Release files / grainlift_adbc_gateway-0.4.0-py3-none-win_amd64.whl
| Download URL | grainlift_adbc_gateway-0.4.0-py3-none-win_amd64.whl |
|---|---|
| Size | 11.5 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
2cc75f75342e13b1d3c823c566b459f270b35891dfb918efcbf4a4b7ed5e46c6
|
|
BLAKE2b-256 checksum How to use checksums |
43ed2baced6b96232c478300685bdd293c62a76b29559cbbf8541e653c29fa7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 1, 2026.
Transparency logRelease files / grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_x86_64.whl
| Download URL | grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 13.4 MB |
| Tags | Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
180b1259f9952d908ed9d95245fccd4d6c4a2a72d8554ef106589b15ae049745
|
|
BLAKE2b-256 checksum How to use checksums |
56ebad00cdb66814b8b03579411d5a08f6c84a9b5a969f5f3288db15c7467998
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 1, 2026.
Transparency logRelease files / grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_aarch64.whl
| Download URL | grainlift_adbc_gateway-0.4.0-py3-none-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 13.2 MB |
| Tags | Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
9f09ed1702cf9beb2a7646830d11973b7fc81c5436a59ed9777f1e673e1abf8c
|
|
BLAKE2b-256 checksum How to use checksums |
0d700d5336c1f5858dedb9aa3b57484411287b928bd08ff6659c5f5780c816f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 1, 2026.
Transparency logRelease files / grainlift_adbc_gateway-0.4.0-py3-none-macosx_11_0_arm64.whl
| Download URL | grainlift_adbc_gateway-0.4.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.1 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
144e1b9cce3cd22745acb3cc0a75dccce41239a162680c153f5ad3916dd79df2
|
|
BLAKE2b-256 checksum How to use checksums |
5be3b3c37890927f0d2f3d5942e5b2fe688bddebc240469d9ba6cbb6be7d1a7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 1, 2026.
Transparency log