feat(db): add Couchbase provider (SQL++ over Query REST, no native dependency) - #263
Conversation
Implements the Couchbase provider as a directory under providers/document, speaking SQL++ over the documented Query Service and management REST APIs. The provider adds no runtime dependency: package.json is untouched, so every distribution channel keeps its current size. The transport is a seam with one HTTP implementation, so adopting the official SDK later is a new file rather than a rewrite. Its result type is deliberately neutral instead of the REST envelope, and a guard test keeps envelope identifiers from leaking outside http-transport.ts. Behaviours that silently produce wrong output if missed, each covered by a test: - A 200 response can carry a payload status of "errors", so the payload is inspected before the HTTP code. Skipping this reports a syntax error as "0 rows". - SELECT * nests each document under the keyspace name and omits the document key, so statements alias and project META().id. - A wildcard in the signature does not describe the rows: taking those keys verbatim would name a literal "*" column and hide every expanded field, so any wildcard falls back to deriving fields from the rows. - The query context is pinned to the connection's bucket. SQL++ reads a bare two-part name as bucket.collection, so without it the displayed keyspace name resolves to a non-existent bucket. - bucket, scope and using are reserved words in the system catalogs and are backtick-quoted everywhere. - scan_consistency defaults to request_plus so a user always sees their own writes; the server default returned zero rows immediately after an insert. Schema comes from system:keyspaces and system:scopes with columns inferred by INFER at a bounded concurrency, where a collection the user cannot read yields empty columns rather than failing the whole tree. Monitoring degrades to empty on a denial, because the monitoring catalogs require an RBAC role most users lack. EXPLAIN reuses the existing tree render model; analyze returns null rather than degrading to an estimate, matching the behaviour set in #201.
…tors (#262) Adds the union member and every exhaustive map that depends on it. The Record<DatabaseType, ...> in db-ui-config and the PICKER_COVERAGE map in the connection-form test are both exhaustive by design, so the compiler refuses to build until each is updated - they are the checklist for this commit. The connection form labels the database field Bucket, since only the management port is configured and query ports are discovered from the cluster. Connection strings accept the couchbase and couchbases schemes, including Capella SRV endpoints that carry no port or path. The generated statement aliases the keyspace and projects META().id as __id. Without the alias SELECT * would nest each document under the keyspace name, and without the projection the document key would never appear in a result set at all.
Adds docs/providers/couchbase.md following the structure of the sibling provider references, and lists Couchbase everywhere the other providers are enumerated. This repo treats the provider triad as an invariant: code, docs and tests move together. The limitations section is written from live verification against Server 8.0.2 Community rather than from the design assumptions. Two corrections came out of that: an un-indexed collection IS readable from Server 7.6 onward through a sequential scan backed by a KV range scan, so error 4000 is an older-server fallback rather than the headline behaviour, and the capability originally listed as SDK-only for that reason is not a gap on current servers. What remains genuinely SDK-only is non-JSON documents and subdocument operations. The compose service pins the community image and its init sidecar creates the bucket with the couchstore storage backend and zero replicas, because Community Edition rejects the Magma default that couchbase-cli would otherwise pick and a single node cannot satisfy the default replica count.
The default Array.prototype.sort compares UTF-16 code units, not alphabetical order. The observed types here are lowercase ASCII so the rendered union is unchanged today, but the comparator matches what the MongoDB provider already does at mongodb.ts:525 - which is what the function comment claims - and makes the ordering correct for any type name that is not plain lowercase ASCII. Clears the SonarCloud reliability gate on this PR (typescript:S2871).
There was a problem hiding this comment.
Pull request overview
Adds Couchbase as a first-class SQL++ database provider using REST APIs without new runtime dependencies.
Changes:
- Registers Couchbase across provider, UI, seed, URI, and query-generation layers.
- Implements HTTP transport, schema inference, monitoring, maintenance, and visual EXPLAIN.
- Adds extensive tests, documentation, and local Docker Compose support.
Reviewed changes
Copilot reviewed 39 out of 39 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib/types.ts |
Adds the Couchbase database type. |
src/lib/seed/types.ts |
Allows Couchbase seed connections. |
src/lib/query-generators.ts |
Generates quoted Couchbase SQL++. |
src/lib/explain/index.ts |
Registers Couchbase EXPLAIN. |
src/lib/explain/couchbase-json.ts |
Parses Couchbase plan trees. |
src/lib/db/types.ts |
Adds Couchbase explain format. |
src/lib/db/providers/document/couchbase/transport.ts |
Defines the transport contract. |
src/lib/db/providers/document/couchbase/keyspace.ts |
Maps and quotes keyspaces. |
src/lib/db/providers/document/couchbase/introspect.ts |
Implements schema introspection. |
src/lib/db/providers/document/couchbase/index.ts |
Implements the provider. |
src/lib/db/providers/document/couchbase/http-transport.ts |
Implements REST and TLS transport. |
src/lib/db/factory.ts |
Registers provider construction. |
src/lib/db-ui-config.ts |
Adds Couchbase UI metadata. |
src/lib/connection-string-parser.ts |
Parses Couchbase URIs. |
src/hooks/use-connection-form.ts |
Adds Couchbase form behavior. |
src/components/icons/db-icons.tsx |
Adds a Couchbase icon. |
src/components/ConnectionModal.tsx |
Adds bucket labels and URI hints. |
tests/unit/seed/types.test.ts |
Tests seed type acceptance. |
tests/unit/lib/query-generators.test.ts |
Tests SQL++ generation. |
tests/unit/lib/explain/couchbase-json.test.ts |
Tests plan parsing. |
tests/unit/lib/db-ui-config.test.ts |
Tests Couchbase UI configuration. |
tests/unit/lib/db-icons.test.tsx |
Tests icon rendering. |
tests/unit/lib/connection-string-parser.test.ts |
Tests Couchbase URI parsing. |
tests/unit/db/factory.test.ts |
Tests provider creation. |
tests/unit/db/couchbase/seam-guard.test.ts |
Guards the transport boundary. |
tests/unit/db/couchbase/keyspace.test.ts |
Tests keyspace mapping. |
tests/unit/db/couchbase/introspect.test.ts |
Tests schema inference. |
tests/unit/db/couchbase/http-transport.test.ts |
Tests REST, discovery, and TLS. |
tests/integration/db/couchbase-provider.test.ts |
Covers provider behavior end to end. |
tests/hooks/use-connection-form.test.ts |
Tests Couchbase form state. |
tests/components/ConnectionModal.test.tsx |
Tests Couchbase-specific labels. |
README.md |
Adds Couchbase user-facing references. |
docs/providers/README.md |
Adds Couchbase to the provider index. |
docs/FEATURES.md |
Documents Couchbase capabilities. |
docs/DATABASE_PROVIDERS.md |
Documents provider architecture. |
docs/ARCHITECTURE.md |
Updates architecture diagrams and counts. |
docs/API_DOCS.md |
Documents Couchbase API usage. |
database-compose.yml |
Adds local Couchbase services. |
Suppressed comments (2)
src/lib/db/providers/document/couchbase/index.ts:371
- Connection-string mode is not actually usable for Couchbase. The form deliberately clears
host,port,user, andpasswordwhen it stores a URI, but this normalization restores only the hostname. As a result, URI credentials are sent asBasic Og==, a bucket in the URI is not available tovalidate(), andcouchbases://still uses plaintext unless SSL is configured separately. Normalize the full URI (credentials, bucket, secure scheme, and management-port policy) before validation and transport construction.
private transportConfig(): DatabaseConnection {
if (this.config.host) return this.config;
const host = this.hostFromConnectionString();
return host ? { ...this.config, host } : this.config;
src/lib/db/providers/document/couchbase/http-transport.ts:361
- The TLS default is unreachable from the normal host/port form: selecting Couchbase preloads port 8091 and
buildConnection()always serializes that numeric port, so enabling SSL makes this expression keep 8091 and sends HTTPS to the plaintext management port. The tests only coverport: undefined. Track whether the port was explicitly overridden, or update the untouched Couchbase default to 18091 when TLS is enabled.
this.managementPort = config.port ?? (this.tls ? DEFAULT_MANAGEMENT_TLS_PORT : DEFAULT_MANAGEMENT_PORT);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| rows: Array.isArray(record.results) ? (record.results as CouchbaseRow[]) : [], | ||
| fieldNames: fieldNamesFromSignature(record.signature), |
There was a problem hiding this comment.
Reproduced against a live cluster before fixing. Worse than described: SELECT RAW h.name produced fields: ["0","1",...,"13"] (one column per character index of the longest string), and SELECT RAW h.nonexistent returned HTTP 500 "Cannot convert undefined or null to object".
Fixed with your second suggestion - converting at the provider boundary, not in the transport. The transport cast has to stay, because INFER returns its flavour array as rows[0] and introspect.ts:294 reads that raw payload; reshaping in the transport would break schema loading. toQueryResult now maps every row through normalizeRow, wrapping anything that is not a plain object into a single __value column, named after the existing __id convention.
Verified live after the fix: [{"__value":"Grand Plaza"},...] with fields: ["__value"], and the null projection returns rows instead of a 500. Commit d438c57.
| const SeedDatabaseType = z.enum([ | ||
| "postgres", | ||
| "mysql", | ||
| "sqlite", | ||
| "mongodb", | ||
| "redis", | ||
| "oracle", | ||
| "mssql", | ||
| "libredb", | ||
| "couchbase", | ||
| ]); |
There was a problem hiding this comment.
Confirmed - docs/SEED_CONNECTIONS.md:103 had no couchbase, so the seed type this PR adds was undiscoverable from its own reference document. Added. Commit 7a02ccc.
| | `connections[].id` | Yes | Unique slug (`[a-z0-9-]+`, max 64 chars) | | ||
| | `connections[].name` | Yes | Display name in UI | | ||
| | `connections[].type` | Yes | `postgres`, `mysql`, `sqlite`, `mongodb`, `redis`, `oracle`, `mssql` | | ||
| | `connections[].type` | Yes | `postgres`, `mysql`, `sqlite`, `mongodb`, `couchbase`, `redis`, `oracle`, `mssql` | |
There was a problem hiding this comment.
Confirmed. libredb was missing here before this PR, but the line is one this PR edits and it claims to enumerate the union, so it is in scope. Added, and reordered to match the order in src/lib/types.ts so the two stay comparable line by line. Commit 7a02ccc.
| } | ||
|
|
||
| type DatabaseType = 'postgres' | 'mysql' | 'sqlite' | 'mongodb' | 'redis' | 'oracle' | 'mssql'; | ||
| type DatabaseType = 'postgres' | 'mysql' | 'sqlite' | 'mongodb' | 'couchbase' | 'redis' | 'oracle' | 'mssql'; |
There was a problem hiding this comment.
Confirmed. Added libredb and matched the union order from src/lib/types.ts, so the documented type is now character-for-character comparable with the real one. Commit 7a02ccc.
| ## System Overview | ||
|
|
||
| LibreDB Studio is a hybrid, cloud-native database management tool that provides an IDE-like experience in the browser. It supports **7 database backends** via a Strategy Pattern abstraction: PostgreSQL, MySQL, SQLite, Oracle, SQL Server, MongoDB, Redis. | ||
| LibreDB Studio is a hybrid, cloud-native database management tool that provides an IDE-like experience in the browser. It supports **8 database backends** via a Strategy Pattern abstraction: PostgreSQL, MySQL, SQLite, Oracle, SQL Server, MongoDB, Couchbase, Redis. |
There was a problem hiding this comment.
Confirmed - this PR updated the list but left the count at eight and still omitted LibreDB. Now reads nine and includes it. Commit 7a02ccc.
| supportsExplain: true, | ||
| explainFormat: "couchbase-json", |
There was a problem hiding this comment.
Correct, and this was the more damaging of the two. use-query-execution.ts:165 always builds the direct action with mode analyze, so the strategy returning null meant every click hit the refusal path while only the background pre-warm (:231, mode estimate) worked. My own end-to-end pass missed it because I saw the Explain tab populated by that pre-warm and concluded the button worked.
The design decision behind the null was mine and it diverged from the established precedent: sqlite-queryplan.ts ignores the mode entirely and returns EXPLAIN QUERY PLAN for both, for exactly the same reason (SQLite has no EXPLAIN ANALYZE either). Declining the mode does not narrow the feature, it disables it - the #194 dead-button defect.
Both modes now return the estimate; a non-SELECT is still declined in both. Re-verified in the browser on a never-run statement so no pre-warmed plan could mask it: plan tree renders, no refusal toast. Commit d438c57.
…ion (#262) Two defects found in review, both reproduced against a live cluster first. SELECT RAW and SELECT VALUE project bare values, so a row can be a scalar, an array or null rather than the object the grid's row contract assumes. Passed through unchanged, deriveFields called Object.keys on a string and produced one column per character index, and on a null row it threw outright - a RAW projection of a missing field returned HTTP 500 "Cannot convert undefined or null to object". Anything that is not a plain object is now wrapped in a single __value column. The normalization lives at the provider boundary rather than in the transport on purpose: INFER returns its flavour array as rows[0] and introspection reads that raw payload, so reshaping in the transport would break schema loading. The direct Explain action was dead. use-query-execution.ts:165 always builds it with mode "analyze", and the strategy returned null for that mode, so every click was refused with "Only SELECT statements can be explained" while only the background pre-warm worked. SQL++ has no EXPLAIN ANALYZE, but declining the mode disables the feature rather than narrowing it. Both modes now return the estimate, matching sqlite-queryplan.ts, which ignores the mode for exactly the same reason. A non-SELECT is still declined in both modes. Verified live: the RAW projections return one honest column instead of crashing, and clicking Explain on a never-run statement renders the plan tree with no refusal toast.
Four enumerations of DatabaseType had drifted from src/lib/types.ts, three of them on lines this PR already touched: - SEED_CONNECTIONS.md omitted couchbase entirely, so the seed type this PR adds was undiscoverable from its own reference document. - README.md and API_DOCS.md list the union but omit libredb, an omission that predates this PR and that adding couchbase beside it made load-bearing. - ARCHITECTURE.md still claimed eight backends and left LibreDB out of the list; there are nine. All four now match the union's own order, which appends new members rather than inserting them, so the docs and the type stay comparable line by line.
|
Nine review findings, all verified against the code before changing anything. Four were inherited from the guide's previous home and one was a factual error this repo has been shipping since #263. The consequential one: the guide claimed SQLBaseProvider "owns pooled-driver mechanics a stateless HTTP transport does not have". It does not. sql-base.ts is 153 lines of pure SQL text helpers keyed off this.type - escaping, LIMIT clause building, placeholder style, read-only detection - plus a prepareQuery() that applies the shared query limiter. Nothing in it touches a pool, a driver or a connection. As written, the guide would have told the ClickHouse provider (#264) to reimplement all of it for no reason. The real criterion is whether the dialect fits the helpers, which is why Couchbase does not use it: SQL++ needs doubled backticks that escapeIdentifier() emits for no existing type. The same false sentence in providers/couchbase.md is corrected too, along with its claim that transactions and cancelQuery live in that base - they do not. Related: the rubric promised that queryLanguage: "sql" inherits the shared query limiter. It does not. BaseDatabaseProvider.prepareQuery() is a pass-through; Couchbase overrides it explicitly to get limiting back. The rest: - The Strategy Pattern guarantee of "no changes to routes, components or existing providers" is contradicted by the worked example, which touched the union, the exhaustive UI maps, the factory, the connection-string parser, the query generators and the explain registry. It now says what the pattern actually buys: provider logic stays self-contained, registration does not. - The capabilities reference omitted explainFormat, which is required whenever supportsExplain is true. Omitting it leaves a visible, dead Explain control - the exact defect found in review on #263. - The connection-picker example silently dropped sqlite. Copying it as instructed would have removed SQLite from the UI. - Verification listed two checks; the repo contract is six gates plus the coverage gate for executable changes. - "Return [] from monitoring methods that don't apply" does not type-check for getOverview() and getPerformanceMetrics(), which return DTOs. - The transport's neutral result type declared rows as Record<string, unknown>[] while the traps section explains that wire rows can be scalars, arrays or null. It is unknown[] now, with the narrowing at the provider boundary, and the note that the Couchbase transport still declares the narrower type. - The seam's kind discriminator was the literal "http", which would have stopped the future native adapter the seam exists for from naming itself. - The prerequisites intro said the third decision was the consequential one after the driver decision had been moved to first.
* docs: split provider reference from the add-a-provider guide DATABASE_PROVIDERS.md was doing two jobs: describing the architecture and the providers that ship today, and walking through how to add a new one. Those are read by different people at different times, so the how-to moves to its own document and the reference keeps only what exists. The moved guide also gains the material the Couchbase provider produced, which had nowhere to live before: - A rubric for the decision that now comes first - does this database need a driver at all? Seven criteria, each failure being code you hand-write. - The transport seam: one interface, one implementation, a neutral result type rather than the wire envelope, and a guard test so the boundary actually holds. - The failure modes specific to HTTP databases. Each of these silently produces wrong output and each was found against a real server, not a mock: a 200 that carries an error, an envelope that does not describe the rows, rows that are not objects, implicit name resolution, a consistency default that is not read-your-writes, differing pagination models, and the hard edge of having no session. - The requirement to verify against a real server before opening a PR, with the specific checks that caught real defects on #263. - The candidate list, assessed against the rubric, so the next provider starts from evidence rather than from a guess. Also adds the roadmap phases this leaves standing: Couchbase as the first driver-free provider, ClickHouse (#264) and Druid (#265) next, and Trino marked deliberately unscheduled with the product question that blocks it written down. * docs: correct the provider guide after review Nine review findings, all verified against the code before changing anything. Four were inherited from the guide's previous home and one was a factual error this repo has been shipping since #263. The consequential one: the guide claimed SQLBaseProvider "owns pooled-driver mechanics a stateless HTTP transport does not have". It does not. sql-base.ts is 153 lines of pure SQL text helpers keyed off this.type - escaping, LIMIT clause building, placeholder style, read-only detection - plus a prepareQuery() that applies the shared query limiter. Nothing in it touches a pool, a driver or a connection. As written, the guide would have told the ClickHouse provider (#264) to reimplement all of it for no reason. The real criterion is whether the dialect fits the helpers, which is why Couchbase does not use it: SQL++ needs doubled backticks that escapeIdentifier() emits for no existing type. The same false sentence in providers/couchbase.md is corrected too, along with its claim that transactions and cancelQuery live in that base - they do not. Related: the rubric promised that queryLanguage: "sql" inherits the shared query limiter. It does not. BaseDatabaseProvider.prepareQuery() is a pass-through; Couchbase overrides it explicitly to get limiting back. The rest: - The Strategy Pattern guarantee of "no changes to routes, components or existing providers" is contradicted by the worked example, which touched the union, the exhaustive UI maps, the factory, the connection-string parser, the query generators and the explain registry. It now says what the pattern actually buys: provider logic stays self-contained, registration does not. - The capabilities reference omitted explainFormat, which is required whenever supportsExplain is true. Omitting it leaves a visible, dead Explain control - the exact defect found in review on #263. - The connection-picker example silently dropped sqlite. Copying it as instructed would have removed SQLite from the UI. - Verification listed two checks; the repo contract is six gates plus the coverage gate for executable changes. - "Return [] from monitoring methods that don't apply" does not type-check for getOverview() and getPerformanceMetrics(), which return DTOs. - The transport's neutral result type declared rows as Record<string, unknown>[] while the traps section explains that wire rows can be scalars, arrays or null. It is unknown[] now, with the narrowing at the provider boundary, and the note that the Couchbase transport still declares the narrower type. - The seam's kind discriminator was the literal "http", which would have stopped the future native adapter the seam exists for from naming itself. - The prerequisites intro said the third decision was the consequential one after the driver decision had been moved to first.
…two providers actually touched The checklist listed six integration points and asserted "no other files should need changes", while Couchbase (#263) and ClickHouse (#264) each touched 27 files under src/ and tests/. That gap is not cosmetic: the ExplainFormat union in src/lib/db/types.ts was absent from the list, and its registry is exhaustive, so omitting it fails the build in a way the list gave no warning about. Splits the list into always-required and conditional, names the exhaustive-map tests that are the real checklist, and points at `git grep -l <previous-type-id>` as authoritative over a hand-maintained list. Keeps the Strategy Pattern claim but scopes it to provider logic, which is what it actually spares you, rather than to registration, which it does not.
…tive dependency) (#270) * feat(db): add ClickHouse provider (SQL over the HTTP interface, no native dependency) (#264) Implements ClickHouse as a directory under providers/sql, speaking SQL over the documented HTTP interface on port 8123. The provider adds no runtime dependency: package.json is untouched, so every distribution channel keeps its current size. Unlike Couchbase it extends SQLBaseProvider rather than BaseDatabaseProvider, which is the case ADDING_A_PROVIDER.md names ClickHouse for: double-quoted identifiers and LIMIT n OFFSET m are both correct here, so identifier quoting and the shared query limiter are inherited instead of reimplemented. The transport is a seam with one HTTP implementation, its result type is deliberately neutral rather than the JSON envelope, and a guard test keeps envelope identifiers from leaking outside http-transport.ts. Behaviours verified against a live 26.7.1 server, each of which silently produces wrong output if missed, and each covered by a test: - Errors use real HTTP status codes, so failure is detected by status - the opposite of Couchbase's rule. But ACCESS_DENIED arrives as 500, not 403, so failures are CLASSIFIED by the numeric exception code; the 497 message says "Not enough privileges" and contains neither "access denied" nor "permission denied", so sniffing the message text would miss every real denial. - Error bodies are plain text even under an application/json content type, so an error body is never handed to JSON.parse. - 64-bit integers arrive unquoted by default and JSON.parse silently rounds them (18446744073709551615 -> ...552000). The transport always sends output_format_json_quote_64bit_integers=1, matching pg's int8-as-string behaviour. - A statement that fails after output has started answers 200 with a truncated body and the real exception in an __exception__ trailer, keyed on the per-request X-ClickHouse-Exception-Tag. The check runs before the format branch and before parsing, because the fence is format-independent: checking it only for JSON reported 805000 lost rows as a successful short result. The buffered variant is different again - 500, no fence, the partial body ahead of the exception - so the error path trims that prefix off the message. - FORMAT and SETTINGS are trailing clauses and the inherited limiter appends LIMIT at the very end, which is a hard syntax error after either. prepareQuery refuses to rewrite such a statement. - Writes answer 200 with an empty body; counts come from X-ClickHouse-Summary, whose values are strings. For ALTER TABLE ... UPDATE and lightweight DELETE the server reports zero even on success, and that is what the provider reports. - Multi-statement is rejected by the server itself, so no client-side splitting. - supportsCreateTable is false, live-disproving the issue's own guess: ENGINE and ORDER BY turned out to have working defaults, but CreateTableModal's default output emits SERIAL (UNKNOWN_TYPE) and UNIQUE (SYNTAX_ERROR). - A pasted https:// URL carries sslMode so it cannot become a plaintext POST to the TLS port, which is the common ClickHouse Cloud case. Schema comes from system.tables, system.columns and system.data_skipping_indices, each filtered by database and each degrading to empty on its own denial - only data_skipping_indices needs a separate grant. Nullable total_rows/total_bytes are reported as unknown rather than zero, so a view does not claim to have no rows. Column types are the declared strings verbatim. Monitoring reads system.query_log, system.processes, system.metrics and system.parts and degrades to empty or zeroed panels, with the overview split into five reads so a restricted user keeps the panels it can still see. EXPLAIN reuses the existing tree render model and returns the same estimate for both modes, since ClickHouse EXPLAIN never executes. Tri-sync per CLAUDE.md: provider code, docs/providers/clickhouse.md and tests/integration/db/clickhouse-provider.test.ts land together. Two shared features that generate SQL - inline row editing and the schema-diff migration generator - are not dialect-aware and are documented as limitations, tracked in #269 rather than fixed here, since both are pre-existing and neither lives in this provider. * docs: correct the add-a-provider registration checklist against what two providers actually touched The checklist listed six integration points and asserted "no other files should need changes", while Couchbase (#263) and ClickHouse (#264) each touched 27 files under src/ and tests/. That gap is not cosmetic: the ExplainFormat union in src/lib/db/types.ts was absent from the list, and its registry is exhaustive, so omitting it fails the build in a way the list gave no warning about. Splits the list into always-required and conditional, names the exhaustive-map tests that are the real checklist, and points at `git grep -l <previous-type-id>` as authoritative over a hand-maintained list. Keeps the Strategy Pattern claim but scopes it to provider logic, which is what it actually spares you, rather than to registration, which it does not. * test(db): pin backslash escaping in ClickHouse literals, and state the overview's honest zeroes Review of PR #270 noted that literal() escapes both the backslash and the quote but only the quote had a test. The escaping was already correct - live-verified on 26.7.1 that `name = 'back\\slash'` matches a table genuinely named `back\slash` - so this adds the missing tests rather than changing behaviour, including the combined backslash-then-quote case that a quote-only escape would let close the literal. Also documents two overview values that are structurally zero rather than measured: maxConnections where system.server_settings is unavailable, and index scan counts, which ClickHouse exposes nowhere the HTTP interface can reach. * fix(db): bound ClickHouse requests client-side, let a URL scheme override TLS, and strip FORMAT from EXPLAIN Four defects from the PR #270 review, each live-reproduced before fixing. The advertised query timeout did not cover the transport. max_execution_time only starts counting once ClickHouse has accepted a statement, so a stalled DNS lookup, connect or TLS handshake - or a response body that stopped arriving - waited on the runtime's own unrelated timeout instead. The seam gains a neutral timeoutMs (any implementation can honour a deadline) and the HTTP transport arms it as an AbortSignal covering the body read as well, since headers can arrive promptly and the stream stall afterwards. Every call site passes one. A URL scheme could not turn TLS off. Both the pasted path and the hand-typed connection string only ever ADDED TLS for https://, so an explicit http:// URL deferred to whatever ssl setting the form still carried from an earlier edit and sent HTTPS to a plaintext endpoint, failing with a bare "fetch failed". The scheme is the more specific statement of intent, so http:// now disables TLS and https:// requires it, while the scheme-neutral clickhouse:// remains the one form that defers. The explain builder disagreed with prepareQuery about what a trailing FORMAT is. It only matched a format anchored at the very end, so `FORMAT TSV;` and `FORMAT TSV SETTINGS max_threads=1` - both of which prepareQuery already treats as trailing clauses, and both covered by its own tests - kept the clause, ClickHouse formatted the EXPLAIN output as TSV, and no plan could be rendered. The tail is now a lookahead so SETTINGS survives, which is correct: it applies to the inner statement, whereas FORMAT reformats the plan itself. The key-expression splitter treated quoted spans as syntax. A legal ``ORDER BY `region,code` `` was reported as two columns, and an unbalanced parenthesis inside quotes corrupted the depth counter and swallowed every later top-level comma. Both scans now skip quoted spans, honouring backslash and doubled-quote escapes. Also documents what was accurate but under-stated: clickhouse-client speaks the NATIVE protocol, not the HTTP interface this provider uses (the overview claimed otherwise while section 3.1 said the opposite); the maintenance API contract, since analyze deliberately takes no target; and the dotted-name display ambiguity, which is a cross-provider TableSchema limitation rather than a ClickHouse one. The compose comment no longer points at a gitignored spec path.



Closes #262.
Adds Couchbase as the ninth database provider, speaking SQL++ over the documented Query Service and
management REST APIs. The provider adds no runtime dependency of any kind —
package.jsonisuntouched, so the Docker image, Snap, AppImage, Flatpak, deb/rpm and the
@libredb/studionpmpackage that
libredb-platformconsumes all stay exactly the same size.Why no SDK
The official
couchbaseSDK is 64.6 MB unpacked across 3765 files, depends oncmake-jsandnode-addon-api, and runs a postinstall step that downloads a prebuilt binary or compiles fromsource. The capability it would buy turned out to be very small:
USE KEYSreads a document by keywith no index, ACID transactions work over REST, and — verified live on 8.0.2 — the Query Service
performs a sequential scan for un-indexed collections, so even KV range scan is not a gap on Server
7.6+. Only non-JSON documents and subdocument operations remain SDK-only.
HTTP is also the more deployable choice in enterprise environments: nothing to download during an
air-gapped install, REST traverses corporate HTTP proxies while the binary KV protocol on 11210
does not, site firewall policy commonly opens 8091/18093 and not 11210, and container images need
no native module or glibc/musl matching.
The transport is still a seam (
CouchbaseTransport) with one implementation, so adopting the SDKlater is a new file rather than a rewrite. A guard test keeps the REST envelope from leaking out of
http-transport.ts, which is what makes that promise hold.What it does
queryLanguage: "sql"inherits Monaco highlighting, theshared query limiter, the
sqltab type, NL2SQL and saved queries with no extra code.collection in
_defaultshows asairline, anything else asinventory.hotel— the same rule aspostgres.ts:818.from
/pools/default/nodeServices, includingalternateAddressesfor NAT and Docker. Capella SRVendpoints resolve via
dns.promises.resolveSrvwith an A-record fallback.system:*plusINFER, at a bounded concurrency, where a collection the usercannot read yields empty columns instead of failing the whole tree.
couchbase-jsonstrategy reusing the existing tree render model.analyzereturns null rather than silently degrading to an estimate, matching Explain on a non-SELECT statement executes it with the safety check bypassed #201.
Catalog RBAC role, so a denial is the normal case for a restricted user.
UPDATE STATISTICS,BUILD INDEXand killing a request; theEnterprise-only refusal of
UPDATE STATISTICSis surfaced verbatim rather than hidden.Non-obvious behaviours handled
Each of these silently produces wrong output if missed, and each has a test:
status: "errors". The payload is checked before the HTTP code, or asyntax error reads as "0 rows".
SELECT *nests documents under the keyspace name and omits the key, so generated statementsalias and project
META(d).id.{ id, "*" }would name a literal*column and hide every expanded field, so any wildcard falls back to deriving fields from rows.
inventory.hotelasbucket
.collection, so withoutquery_contextthe generated statement dies with "Ambiguousreference to field 'inventory'".
bucket,scopeandusingare reserved words in the system catalogs and must bebacktick-quoted.
scan_consistencydefaults torequest_plusso a user always sees their own writes. Thedefault
not_boundedreturned zero rows immediately after an INSERT in testing.Verification
Six local gates green:
format,lint(0 errors),typecheck,knip,test,build. Coverageis at the required 100%.
Verified end to end against a real Couchbase Server 8.0.2 Community node driving the running
application, not mocks: full INSERT / UPDATE / SELECT / DELETE through both the query API and the
browser UI, read-your-writes, the un-indexed collection, both error paths, schema introspection with
INFER-derived column types, the Explain tree including the
Paralleloperator's nested~child,every monitoring surface, and all three maintenance operations.
That pass found three defects, each fixed with a test first: the wildcard signature, the missing
query context, and a documentation claim that un-indexed collections cannot be listed.
Not in this PR
The SDK and what it gates (non-JSON documents, subdocument operations); Analytics, Full-Text Search
and Eventing; Capella management APIs; multi-bucket browsing from one connection. Chart and operator
bundle versions are untouched — those gates trigger on a
package.jsonversion change, whichbelongs to a separate release step.