Skip to content

feat(plugins): add a Cloudflare R2 SQL driver - #2030

Open
datlechin wants to merge 2 commits into
mainfrom
feat/cloudflare-r2-sql
Open

feat(plugins): add a Cloudflare R2 SQL driver#2030
datlechin wants to merge 2 commits into
mainfrom
feat/cloudflare-r2-sql

Conversation

@datlechin

@datlechin datlechin commented Aug 6, 2026

Copy link
Copy Markdown
Member

Adds a registry-only driver for Cloudflare R2 SQL, the read-only SQL engine over Apache Iceberg tables in an R2 bucket.

What you get

Connect with an Account ID, a bucket, and a Cloudflare API token. The sidebar lists Iceberg namespaces as schemas and their tables underneath. SELECT queries run in the editor and render in the grid. Export works; nothing offers a write.

Notes on the request

Two premises in the upstream issue turned out to be wrong, verified against wrangler's own source (packages/wrangler/src/r2/sql.ts) rather than the docs:

  • The request body is {"warehouse": ..., "query": ...}. The docs' curl example omits warehouse; the CLI sends it.
  • The warehouse name is {accountId}_{bucket}, split on the first underscore. Asking for it as a third field would add a class of typos with no information gain, so the form takes Account ID + Bucket and derives it.

The issue also suggested an Iceberg REST catalog client for schema browsing. Not needed: R2 SQL exposes SHOW NAMESPACES, SHOW TABLES IN, and DESCRIBE on the same endpoint, free of charge, under the same auth. That avoids a second client whose auth failures come back as plain text, and Iceberg's %1F multi-level namespace encoding.

Two app-core capabilities this needed

Both are app-side flags on PluginMetadataRegistry.CapabilityFlags, so there is no PluginKit change and no ABI bump.

isEngineReadOnly — TablePro already had the mechanism (SafeModeLevel.readOnly -> blocksAllWrites, enforced in DefaultExecutionGate plus ~22 UI sites), but it was a user setting defaulting to .silent with no way for an engine to pin it. Now such connections seed to Read-Only, the Safe Mode picker is disabled with an explanation, and the execution gate backstops it regardless of persisted state.

Note DriverPlugin.supportsReadOnlyMode is a false friend: it means "TablePro can tell reads from writes in this query language" (it feeds forcesWriteResolver), not "this engine is read-only". Overloading it would have changed behaviour for every other driver.

supportsOffsetPagination — R2 SQL rejects OFFSET outright, even OFFSET 0 (unsupported feature: OFFSET clause is not supported), and caps LIMIT at 10,000. Both PaginationStyle cases emit OFFSET, and the enum is @frozen, so there was no honest way to say "this engine cannot page". Table tabs for such engines now show a single capped page labelled "First N rows" instead of fake page numbers; the nav buttons, the shortcut actions and the restore path are all suppressed so nothing can move the offset.

An earlier design fetched LIMIT offset+pageSize and sliced client-side. It was dropped: each page rescans from row 0 on an engine billed by bytes scanned with a 10 MB floor, and the SQL shown in the tab would not reproduce the grid. QUALIFY ROW_NUMBER() is expressible but Cloudflare documents window functions, including through QUALIFY, as budget-gated and rejected with a 400 when too much data would be scanned. Cloudflare's own documented pagination strategy is keyset (WHERE k > :last ORDER BY k LIMIT n), which is a cross-cutting change to TablePro's pagination model and belongs in its own PR.

Also fixed

  • ColumnTypeClassifier matched ARRAY/MAP/ROW but not STRUCT, so nested-struct columns lost JSON rendering. R2 SQL's Iceberg nested type is literally struct.
  • Sidebar New Table / New View was gated only on Safe Mode, ignoring supportsSchemaEditing. That affects Cloudflare D1, MongoDB, DynamoDB and BigQuery today.

Architecture

Pure logic lives in a new zero-dependency SPM module TableProR2SQLCore (URL and body construction, response decoding, Iceberg/Arrow type mapping, SQL generation, error classification, identifier quoting). The plugin bundle holds only the URLSession transport and the PluginDatabaseDriver conformance. This mirrors TableProTrinoCore and is what makes the tests actually execute: suites inside a plugin bundle silently run zero assertions in the headless test host.

Tests

82 new tests in TableProR2SQLCoreTests, running under swift test. They cover the endpoint URL, both body fields being present, envelope decoding (including success: false under HTTP 200 and 500, and a non-JSON body becoming a typed error rather than a decode crash), Int64/UInt64 precision never routed through Double, schema-ordered row mapping with missing keys as NULL, type normalization, and SQL generation asserting the output never contains OFFSET and that a '; DROP TABLE value stays inside a string literal.

Plus DatabaseTypeTests for the new type, and a ColumnTypeClassifierTests case for STRUCT.

Verification

  • swift test --package-path Packages/TableProCore: all suites pass.
  • xcodebuild -scheme TablePro build: BUILD SUCCEEDED.
  • xcodebuild -scheme CloudflareR2SQLDriverPlugin build: BUILD SUCCEEDED; bundle carries the right principal class, bundle id and PluginKit version 19.
  • xcodebuild test -only-testing:TableProTests/ColumnTypeClassifierTests -only-testing:TableProTests/ExecutionGateTests: TEST SUCCEEDED.
  • swiftlint --strict: clean.

Not verified: no live R2 SQL account was available, so the authenticated round trip is untested. Everything above is pure-logic or compile-level. The exact column headers returned by SHOW/DESCRIBE are read positionally rather than by name specifically because of that. A manual smoke test against a real R2 SQL bucket should happen before the registry release.

https://claude.ai/code/session_01NtGEvGSCkym8Kb24YeXFez

@mintlify

mintlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 6, 2026, 4:40 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant