Files
rea/tests/boundary/sqlite/sqliteColumnIdentity.test.ts
T
morluto ef475fcf8b fix(sqlite): distinguish query capacity and unavailable row sources
Use one bound JSON selection set for inventory metadata, keeping raw-name byte
comparison and filtering inside SQLite while preserving caller projection order
and repetition. Large requests reach native result-column capacity attribution.

Separate unavailable row sources from generated-expression failures. Preserve
SQLite's reason and selected table/path, offer schema-only recovery, and avoid
claiming corruption or recommending unreadable columns.

Validated UTF-8/UTF-16 identity, native inspection/worker tests and CLI/MCP failure
and recovery workflows. The large CLI selection uses the existing framed CLI
fixture to avoid host argv limits. Source bytes and identity remain unchanged.

Fixes #6855
Fixes #6604
2026-10-24 01:24:43 +08:00

116 lines
3.6 KiB
TypeScript

import { DatabaseSync } from "node:sqlite";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { inspectSqliteDatabaseSnapshot } from "../../../src/sqlite/SqliteDatabaseInspection.js";
import { SQLITE_NATIVE_LIMITS_AVAILABLE } from "../../fixtures/sqlite/database.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const sample = async (encoding: string, malformed = false) => {
const root = await createTestTempDirectory("rea-sqlite-column-identity-");
const path = join(root, "Columns.sqlite");
const db = new DatabaseSync(path);
try {
db.exec(`PRAGMA encoding='${encoding}';
CREATE TABLE t("col" TEXT, "�ol" TEXT);
INSERT INTO t VALUES ('first-column','replacement-column');
CREATE TABLE json_each(value TEXT);`);
} finally {
db.close();
}
if (malformed) {
const bytes = await readFile(path);
const marker = Buffer.from('"col"');
const offset = bytes.indexOf(marker);
if (offset < 0 || bytes.indexOf(marker, offset + 1) !== -1)
throw new Error("Expected unique authored column marker");
bytes[offset + 1] = 0xff;
await writeFile(path, bytes);
}
return path;
};
it
.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)
.each(["UTF-8", "UTF-16le", "UTF-16be"])(
"preserves exact column selection in %s with literal U+FFFD",
async (encoding) => {
const path = await sample(encoding);
const result = inspectSqliteDatabaseSnapshot(path, {
path,
table: "t",
schema_scope: "inventory",
columns: ["�ol", "col", "�ol"],
});
expect(result.schema.tables[0]).toMatchObject({
columns: [],
columns_completeness: "not_requested",
});
expect(result.rows?.columns).toEqual(["�ol", "col", "�ol"]);
expect(
result.rows?.values[0]?.map((cell) =>
cell.type === "text" ? cell.value : null,
),
).toEqual(["replacement-column", "first-column", "replacement-column"]);
},
);
it.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"refuses automatic sampling before malformed column bytes can alias a valid name",
async () => {
const path = await sample("UTF-8", true);
expect(() =>
inspectSqliteDatabaseSnapshot(path, {
path,
table: "t",
schema_scope: "inventory",
}),
).toThrow(
expect.objectContaining({
reason: "format",
message: expect.stringContaining(
"column name contains malformed UTF-8",
),
}),
);
},
);
it.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"selects the exact valid subset without decoding an unrelated malformed column",
async () => {
const path = await sample("UTF-8", true);
const result = inspectSqliteDatabaseSnapshot(path, {
path,
table: "t",
schema_scope: "inventory",
columns: ["�ol"],
});
expect(result.rows?.columns).toEqual(["�ol"]);
expect(result.rows?.values[0]?.[0]).toMatchObject({
type: "text",
value: "replacement-column",
});
expect(result.schema.tables[0]).toMatchObject({
columns: [],
columns_completeness: "not_requested",
});
expect(() =>
inspectSqliteDatabaseSnapshot(path, {
path,
table: "t",
schema_scope: "inventory",
columns: ["col"],
}),
).toThrow(expect.objectContaining({ reason: "column-selection" }));
expect(() =>
inspectSqliteDatabaseSnapshot(path, {
path,
table: "t",
schema_scope: "selected_table",
columns: ["�ol"],
}),
).toThrow(expect.objectContaining({ reason: "format" }));
},
);