Cloudflare D1

The d1 driver connects to Cloudflare D1 databases through the D1 REST API. It is built into the tobira binary and needs no Cloudflare SDK. The SQL is SQLite’s. You can query, browse, edit rows and explain queries. D1’s REST API keeps no session, so there are no transactions.

Not yet tested against Cloudflare. The driver is tested against a local fake of the D1 REST API, built from Cloudflare’s documentation. Treat it as experimental, and please open an issue if something fails against the real service.

Quick start

tobira query "d1://0123456789abcdef0123456789abcdef/6f1c2b3a-0d4e-4f5a-9b8c-7d6e5f4a3b2c" \
  "select * from users limit 5" --read-only
connections:
  - name: edge
    dsn: d1://0123456789abcdef0123456789abcdef/6f1c2b3a-0d4e-4f5a-9b8c-7d6e5f4a3b2c
    password: keyring:cloudflare-d1

Connecting

FormExample
d1://<account_id>/<database_id>d1://0123...cdef/6f1c2b3a-0d4e-4f5a-9b8c-7d6e5f4a3b2c
...?endpoint=<api base url>d1://acct/db?endpoint=https://proxy.example.com/client/v4
  • Copy the account id and the database id from the Cloudflare dashboard. They may hold only letters, digits and dashes.
  • endpoint replaces the API base URL (https://api.cloudflare.com/client/v4), for a proxy or a local stand-in.
  • The DSN accepts only endpoint and allow_plaintext. The connection’s options: are refused.

API token

Create a Cloudflare API token with D1 access: D1 Read for read-only connections, D1 Edit to edit. Put it in the connection’s password: as a secret reference, or in TOBIRA_CONN_<NAME>_PASSWORD. d1://:TOKEN@account/database also works and is masked like any password.

A token in a query parameter is refused, because query parameters are not masked. The token is sent only as Authorization: Bearer ... to the configured endpoint. tobira does not follow redirects, so the token never reaches another host, and it never appears in an error. HTTPS_PROXY and NO_PROXY are honoured.

TLS and allow_plaintext=1

The endpoint must be https://. An http:// endpoint is allowed without more on your machine (localhost or a loopback address). To another machine it also needs allow_plaintext=1, and the token then crosses the network in clear:

d1://acct/db?endpoint=http://d1-proxy.lan:8787/client/v4&allow_plaintext=1

Plain HTTP to Cloudflare’s own API (*.cloudflare.com) is always refused.

Capabilities

CapabilitySupported
Queryyes
Browse (filter, sort, paging)yes (one namespace, main; D1’s _cf_* tables are hidden)
Edit rowsyes
Explainyes (EXPLAIN QUERY PLAN; no ANALYZE)
Completionyes
Transactionsno
Read-only guardyes

Queries

Write SQLite SQL, one statement per query. BEGIN, COMMIT, ROLLBACK and END are refused.

tobira query edge "select id, email from users order by id desc limit 20"
  • Each query is one HTTPS request. D1 sends the whole result at once; --limit only trims what tobira keeps. Add a LIMIT to queries over large tables.
  • Blob parameters are not supported.
  • Numbers travel as JavaScript numbers: integers beyond ±2^53 are rounded by D1, and a whole REAL (3.0) arrives as 3 unless its column is declared REAL.

Types

D1 does not report column types. For a plain SELECT of * or of bare column names from one table (with WHERE, GROUP BY, ORDER BY and LIMIT, but no joins or UNION), tobira reads the table’s declared types with one more request and converts values as the SQLite driver does. Browsing always knows the types. Any other column takes the type of the first row’s value, so a BOOLEAN read through an alias shows as an integer.

Editing

Edit rows in the grid (e, i, d, then :apply). See Editing rows. Without transactions, each change is one statement, and :apply stops at the first error.

  • An insert returns the stored key.
  • An update or delete only changes a row when the key matches exactly one. Otherwise it fails with “not found” or a conflict, and nothing changes.

Read-only guard

D1 has no read-only mode and no transactions over REST, so tobira checks every statement before it is sent on a read-only connection:

  • It must be one SELECT, WITH, VALUES, EXPLAIN or reading pragma, as in the SQLite driver.
  • INSERT, UPDATE, DELETE or REPLACE INTO anywhere outside strings and comments is refused, which also stops a writable WITH. So is load_extension.
  • ATTACH, VACUUM, pragma assignments and DDL are refused.

Anything tobira cannot classify is refused. tobira query exits with code 4.

What it cannot stop. There is no second layer on the server. Use a token with D1 Read only for read-only connections. Whether Cloudflare enforces that permission on the query endpoint has not been verified.

Limits

  • No transactions.
  • A response over 64 MiB, or with more than 4096 columns, fails with size limit exceeded.
  • Describing a table takes four requests plus one per index.
  • Cancelling a query returns at once; D1 may still finish the statement.

Compatibility

Tested against a local stand-in for the D1 REST API. Not yet tested against Cloudflare.

Troubleshooting

MessageWhat to do
an API token is requiredSet the connection’s password: to a Cloudflare API token with D1 access.
a d1 DSN is d1://<account_id>/<database_id>Check the ids in the dashboard. They hold only letters, digits and dashes.
put the API token in the password field ...Move the token from the URL parameter to the connection’s password:.
refusing to send the API token over plain HTTP to a remote hostUse an https:// endpoint, or add allow_plaintext=1.
authentication failedThe token is wrong or expired.
not foundCheck the account and database ids.
connection failedCloudflare is rate limiting you or had a server error. Try again later.