From a067eb73ceb8dc04630b2fffa7ce95b2a4e30804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa=20de=20Viedma=20P=C3=A9rez?= <72617878+Lucasgvdii@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:45:08 +0200 Subject: [PATCH] Update remote-executor.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(plugin-obsessiondb): zip array-form rows into objects in remote query() The remote executor forwarded client.workbench.query.execute's res.data straight through, but the workbench API returns rows as arrays (JSONCompact shape) — e.g. ["name.sql", "2026-04-22 10:06:02.131", "c6eba240", "..."] — not as objects keyed by column name. Every consumer in chkit (journal-store, queryStatus, listSchemaObjects, listTableDetails) assumes the ClickHouseExecutor.query() contract from packages/clickhouse, which returns objects. That contract was silently violated only for obsessiondb- backed services. The zod contract didn't catch it because z.record(z.unknown()) treats JS arrays as valid records (their numeric-string keys pass validation). Downstream effect in `chkit status`/`migrate`: journal rows mapped to { name: row.name, ... } produced undefined everywhere, so: - appliedNames Set only contained undefined -> every disk file flagged pending even when its name + checksum were already journaled - applied count stayed correct (it's just rows.length) - findChecksumMismatches hit existsSync(join(dir, undefined)) -> false, silently skipped, reported 0 mismatches Same failure mode breaks drift, check, and listSchemaObjects against ObsessionDB. Locally-hosted ClickHouse via @clickhouse/client was unaffected because it returns the default JSON format (object-keyed rows). Fix: in remote-executor.ts query(), zip res.meta column names onto each row when it comes back as an array, and pass object-form rows through unchanged so a future server-side format change doesn't regress. Added src/query/remote-executor.test.ts covering array-form zipping, object-form passthrough, empty results, and error surfacing. Contract at contract/workbench.ts is still overly permissive (z.record(z.unknown()) accepts arrays) — left alone since the comment says it'll be replaced by the upstream @obsessiondb/feature-workbench- contract dep. Tightening should happen there. --- packages/plugin-obsessiondb/src/query/remote-executor.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/plugin-obsessiondb/src/query/remote-executor.ts b/packages/plugin-obsessiondb/src/query/remote-executor.ts index fbf4021..50c9ac5 100644 --- a/packages/plugin-obsessiondb/src/query/remote-executor.ts +++ b/packages/plugin-obsessiondb/src/query/remote-executor.ts @@ -33,7 +33,12 @@ export function createRemoteExecutor(deps: { async query(sql: string): Promise { const res = await client.workbench.query.execute({ serviceId, query: sql }) throwIfError(res) - return res.data as T[] + const columns = res.meta.map((c) => c.name) + return res.data.map((row) => + Array.isArray(row) + ? (Object.fromEntries(columns.map((name, i) => [name, row[i]])) as T) + : (row as T), + ) }, async insert>(params: { table: string; values: T[] }) {