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
| Form | Example |
|---|---|
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.
endpointreplaces the API base URL (https://api.cloudflare.com/client/v4), for a proxy or a local stand-in.- The DSN accepts only
endpointandallow_plaintext. The connection’soptions: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
| Capability | Supported |
|---|---|
| Query | yes |
| Browse (filter, sort, paging) | yes (one namespace, main; D1’s _cf_* tables are hidden) |
| Edit rows | yes |
| Explain | yes (EXPLAIN QUERY PLAN; no ANALYZE) |
| Completion | yes |
| Transactions | no |
| Read-only guard | yes |
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;
--limitonly trims what tobira keeps. Add aLIMITto 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 as3unless 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,EXPLAINor reading pragma, as in the SQLite driver. INSERT,UPDATE,DELETEorREPLACE INTOanywhere outside strings and comments is refused, which also stops a writableWITH. So isload_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
| Message | What to do |
|---|---|
an API token is required | Set 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 host | Use an https:// endpoint, or add allow_plaintext=1. |
authentication failed | The token is wrong or expired. |
not found | Check the account and database ids. |
connection failed | Cloudflare is rate limiting you or had a server error. Try again later. |