pgx
PGX — Lightweight Postgres for AI agents. Written in Rust. Thousands of isolated databases in one runtime. 🐘
Translation pending
Make APIs, services, files, databases, models, and custom code queryable from DuckDB today—and from more query engines over time.
DataRust · DockerfileGitHub @Query-farmOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Turso databases for every ADBC client: a Grainlift worker for local files and Turso Cloud
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Query your Turso databases from DuckDB, Python, Go, Rust
and any other tool that speaks ADBC .
Built on Grainlift by Query.Farm
grainlift-turso is a small server that sits in front of a Turso database
and lets analytics and data tools work with it directly. Point DuckDB at it and
your Turso tables become tables you can join with local data. Use it from
Python and you get results as Apache Arrow, ready for pandas or Polars. Load
data into Turso from a DuckDB query in one statement.
It works with both kinds of Turso database:Read the full README ↗grainlift-turso is a small server that sits in front of a Turso database and lets analytics and data tools work with it directly. Point DuckDB at it and your Turso tables become tables you can join with local data. Use it from Python and you get results as Apache Arrow, ready for pandas or Polars. Load data into Turso from a DuckDB query in one statement.
It works with both kinds of Turso database:
Your tool loads the Grainlift ADBC driver
(pip install adbc-driver-grainlift),
which talks to grainlift-turso over the network. grainlift-turso runs your SQL
on Turso as written and streams the results back.
This takes about five minutes. You need Python 3.13 or newer (for the driver) and uv (to run Haybarn, a DuckDB distribution).
1. Download grainlift-turso for your platform from the latest release and unpack it. For example, on a Mac with Apple Silicon:
curl -L https://github.com/Query-farm/grainlift-turso/releases/latest/download/grainlift-turso-aarch64-apple-darwin.tar.gz | tar xz
cd grainlift-turso-aarch64-apple-darwin
| Platform | Download |
|---|---|
| Linux, x86_64 | grainlift-turso-x86_64-unknown-linux-gnu.tar.gz |
| Linux, ARM64 | grainlift-turso-aarch64-unknown-linux-gnu.tar.gz |
| macOS, Apple Silicon | grainlift-turso-aarch64-apple-darwin.tar.gz |
| macOS, Intel | grainlift-turso-x86_64-apple-darwin.tar.gz |
| Windows, x86_64 | grainlift-turso-x86_64-pc-windows-msvc.zip |
Each release lists SHA-256 checksums in SHA256SUMS. (Prefer containers?
ghcr.io/query-farm/grainlift-turso is the same program; see
Running it in production.)
2. Install the Grainlift driver from PyPI, and note where its library is:
pip install adbc-driver-grainlift
export GRAINLIFT_DRIVER=$(python -c "import adbc_driver_grainlift; print(adbc_driver_grainlift.driver_path())")
3. Start grainlift-turso on a new local database file:
export GRAINLIFT_TOKEN=choose-a-secret
TURSO_DATABASE_URL=demo.db ./grainlift-turso
It prints Grainlift listening on http://127.0.0.1:8080. Leave it running.
4. Query it from DuckDB. In a second terminal, in the same directory and
with the same two exports, start Haybarn with uvx haybarn-cli and paste the
SQL below. It attaches the Turso database, creates a table in it from a DuckDB
query, adds a row, and joins it with local data. (It is also included as
examples/query.sql: uvx haybarn-cli < examples/query.sql runs it in one go.)
FORCE INSTALL adbc_scanner FROM community;
LOAD adbc_scanner;
-- How to reach grainlift-turso: the driver, the server and a token.
CREATE SECRET turso (
TYPE adbc,
DRIVER getenv('GRAINLIFT_DRIVER'),
URI 'http://127.0.0.1:8080',
SCOPE 'http://127.0.0.1:8080',
EXTRA_OPTIONS MAP {
'grainlift.target': 'turso',
'grainlift.auth.bearer_token': getenv('GRAINLIFT_TOKEN')
}
);
-- Attach the Turso database. READ_WRITE lets you create tables and insert.
ATTACH 'http://127.0.0.1:8080' AS turso (TYPE adbc, SECRET 'turso', READ_WRITE);
-- Create a Turso table from a DuckDB query, then add a row.
CREATE TABLE turso.cities AS
SELECT * FROM (VALUES
('Lima', 'PE', 10092000),
('Pune', 'IN', 7166000),
('Rome', 'IT', 2873000)
) AS v(name, country, population);
INSERT INTO turso.cities VALUES ('Oslo', 'NO', 709000);
-- Query it like any table, joined here with local data.
SELECT c.name, k.country, c.population
FROM turso.cities c
JOIN (VALUES ('PE', 'Peru'), ('IN', 'India'), ('IT', 'Italy'), ('NO', 'Norway')) AS k(code, country)
ON c.country = k.code
ORDER BY c.population DESC;
The last query prints:
┌─────────┬─────────┬────────────┐
│ name │ country │ population │
│ varchar │ varchar │ int64 │
├─────────┼─────────┼────────────┤
│ Lima │ Peru │ 10092000 │
│ Pune │ India │ 7166000 │
│ Rome │ Italy │ 2873000 │
│ Oslo │ Norway │ 709000 │
└─────────┴─────────┴────────────┘
Attach the database with READ_WRITE, and its tables behave much like local
ones: query and join them, create them from queries, and insert into them.
DRIVER is the library path that adbc_driver_grainlift.driver_path() prints:
FORCE INSTALL adbc_scanner FROM community;
LOAD adbc_scanner;
CREATE SECRET turso (
TYPE adbc,
DRIVER '/path/to/libadbc_driver_grainlift.dylib',
URI 'http://127.0.0.1:8080',
SCOPE 'http://127.0.0.1:8080',
EXTRA_OPTIONS MAP {'grainlift.target': 'turso', 'grainlift.auth.bearer_token': 'choose-a-secret'}
);
ATTACH 'http://127.0.0.1:8080' AS turso (TYPE adbc, SECRET 'turso', READ_WRITE);
SELECT * FROM turso.cities WHERE population > 1000000;
CREATE TABLE turso.sales AS SELECT * FROM 'sales.parquet'; -- loads in bulk
INSERT INTO turso.sales SELECT * FROM 'more_sales.parquet';
Leave out READ_WRITE to attach read-only.
DuckDB's attached databases cannot UPDATE or DELETE rows here, or run DDL
such as CREATE INDEX or DROP TABLE: DuckDB changes rows by row ID, which
remote tables do not have. Send those statements to Turso directly with
adbc_execute, which reports the rows changed:
SET VARIABLE conn = (SELECT adbc_connect({'secret': 'turso'}));
CALL adbc_execute(getvariable('conn')::BIGINT, 'UPDATE cities SET population = population + 1000 WHERE country = ''NO''');
CALL adbc_execute(getvariable('conn')::BIGINT, 'CREATE INDEX cities_country ON cities (country)');
A table created with adbc_execute appears in the attached database once you
attach it again.
Use Haybarn 1.5.5 or newer:
the adbc_scanner that stock DuckDB currently downloads is too old to load
data through Grainlift.
from adbc_driver_grainlift import dbapi
with dbapi.connect(db_kwargs={
"grainlift.uri": "http://127.0.0.1:8080",
"grainlift.target": "turso",
"grainlift.auth.bearer_token": "choose-a-secret",
}) as conn, conn.cursor() as cur:
cur.execute("SELECT name, population FROM cities")
table = cur.fetch_arrow_table() # or cur.fetch_df() for pandas
pip install adbc-driver-grainlift pyarrow provides everything this needs.
From Go, Rust, Java or C++, load the same library (driver_path()) through
that language's ADBC driver manager, with the entry point
AdbcDriverGrainliftInit and the same grainlift.* settings.
Point the server at your database's URL and give it a database token:
export TURSO_DATABASE_URL=libsql://my-db-my-org.turso.io
export TURSO_AUTH_TOKEN=$(turso db tokens create my-db)
./grainlift-turso
Prefer to have each person use their own Turso token, so Turso applies their
permissions? Leave TURSO_AUTH_TOKEN unset, and each client adds its token as
turso.auth_token next to its other connection settings.
For a long-running deployment, describe your databases and who may use them in a configuration file, then check and serve it:
[server]
listen = "127.0.0.1:8080"
[auth.static_bearer_tokens]
"replace-with-a-long-random-token" = "analytics"
[targets.app] # clients connect to the target "app"
url = "libsql://app-myorg.turso.io"
auth_token_env = "TURSO_APP_TOKEN" # the Turso token comes from the environment
grainlift-turso check --config turso.toml # validates the file and connects to every database
grainlift-turso serve --config turso.toml
Or run the container image, for Linux on x86_64 or ARM64:
docker run -p 8080:8080 -e TURSO_APP_TOKEN \
-v ./turso.toml:/etc/grainlift-turso/turso.toml:ro \
ghcr.io/query-farm/grainlift-turso:latest
One server can serve many databases, each with its own settings, and supports
single sign-on (JWT), mutual TLS, per-user database permissions, health checks,
JSON logs, tracing and graceful shutdown. The
deployment guide covers all of it, and
turso.example.toml is a complete, annotated example.
?, :name, ...).ATTACH.transaction_mode = "concurrent")."retry it" or SQLSTATE 40001. Another writer got there first: your
transaction was rolled back, and nothing it did was saved. Run it again. This
is normal under heavy concurrent writing.
"exceeded its … deadline". The query ran longer than the server allows
(60 seconds by default). Narrow the query, or raise
operation_timeout_seconds in the configuration.
A write fails as unauthorized. The database is read-only for you: a
read-only Turso token, or a file served with read_only = true.
"Column … has type Int64 but row … holds text". A column mixes kinds of
values, which SQLite-style databases allow. Add a CAST in your query to
choose the type you want.
Loading data hangs in DuckDB. Your adbc_scanner extension is too old.
Run FORCE INSTALL adbc_scanner FROM community; in Haybarn 1.5.5 or newer.
"Can only update base table", or "do not support creating tables through
DDL". DuckDB's attached databases cannot update, delete or run DDL on remote
tables; use adbc_execute (see From DuckDB).
A new table doesn't appear in an attached database. DuckDB caches the table list when you attach. Detach and attach again.
Copyright © 2026 Query Farm LLC. Released under the Apache License 2.0; see LICENSE.
The data in a Turso database belongs to its owner, and Turso Cloud is subject to Turso's terms. See NOTICE. This project is not affiliated with or endorsed by Turso.