mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(desktop): read SQLite state despite metadata name collisions
Resolve exported SQLite metadata through built-in PRAGMAs on the private connection. Preserve application tables/views, source encoding, nullable rows and the existing resource limits. Validated with 93 focused tests, current-main RED controls, and actual CLI exports of the public Chinook dataset with native/shipped-reader parity and unchanged source hashes. No live GUI, container or installed agentenv qualification is claimed. Full technical CI 42247722923 (all eight shards and final aggregate) passed; Greptile review scored 5/5 on the merged head. The original human readiness declarations remain unfilled; the explicitly authorized maintainer readiness exception applies without adding a human-review attestation. Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
This commit is contained in:
1 parent
98c589e420
commit
93ce13e76f
2 files changed
+127
-2
No files matched your search
@@ -184,11 +184,11 @@ def _table(db, name: str, primary_key: list, encoding: str) -> dict:
|
||||
def _sqlite(path: Path) -> dict:
|
||||
"""The database's ordinary tables, in schema order, up to REA's table and row limits."""
|
||||
with contextlib.closing(sqlite3.connect(path.resolve().as_uri() + "?mode=ro", uri=True, timeout=SQLITE_BUSY_S)) as db:
|
||||
kinds = dict(db.execute("select name, type from pragma_table_list where schema = 'main'"))
|
||||
kinds = dict(db.execute("select name, type from temp.pragma_table_list where schema = 'main'"))
|
||||
names = [name for (name,) in db.execute("select name from sqlite_schema where type = 'table'")
|
||||
if kinds.get(name) == "table"]
|
||||
keys = {name: [key for (key,) in db.execute(
|
||||
"select name from pragma_table_info(?, 'main') where pk > 0 order by pk", (name,))]
|
||||
"select name from temp.pragma_table_info(?, 'main') where pk > 0 order by pk", (name,))]
|
||||
for name in names[:MAX_TABLES]}
|
||||
encoding = {"UTF-8": "utf-8", "UTF-16le": "utf-16-le", "UTF-16be": "utf-16-be"}[
|
||||
db.execute("PRAGMA encoding").fetchone()[0]]
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
PYTHON_HELPERS,
|
||||
PYTHON_IMPORTS,
|
||||
} from "../../../src/domain/desktop/appExportPython.js";
|
||||
import { PYTHON_TASK_EVALUATOR } from "../../../src/domain/desktop/appTaskPython.js";
|
||||
import { inspectSqliteDatabaseSnapshot } from "../../../src/sqlite/SqliteDatabaseInspection.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const collisions = [
|
||||
["table", "pragma_table_list"],
|
||||
["table", "pragma_table_info"],
|
||||
["view", "pragma_table_list"],
|
||||
["view", "pragma_table_info"],
|
||||
] as const;
|
||||
|
||||
it.each(
|
||||
(["UTF-8", "UTF-16le", "UTF-16be"] as const).flatMap((encoding) =>
|
||||
collisions.map(([kind, name]) => [encoding, kind, name] as const),
|
||||
),
|
||||
)(
|
||||
"reads exported %s SQLite state when a %s shadows %s",
|
||||
async (encoding, kind, name) => {
|
||||
const root = await createTestTempDirectory("desktop-sqlite-metadata-");
|
||||
const path = join(root, "state.db");
|
||||
const db = new DatabaseSync(path);
|
||||
try {
|
||||
db.exec(`
|
||||
PRAGMA encoding = '${encoding}';
|
||||
CREATE TABLE notes(id INTEGER, part TEXT, "body "" text" TEXT, PRIMARY KEY(part, id));
|
||||
INSERT INTO notes VALUES(7, 'a', 'retained');
|
||||
${
|
||||
kind === "table"
|
||||
? `CREATE TABLE ${name}(name TEXT, type TEXT, schema TEXT);
|
||||
INSERT INTO ${name} VALUES('notes', 'view', 'main');`
|
||||
: `CREATE VIEW ${name} AS SELECT missing_function() AS name, 'table' AS type, 'main' AS schema;`
|
||||
}
|
||||
`);
|
||||
db.prepare("INSERT INTO notes VALUES(NULL, ?, ?)").run(
|
||||
"a",
|
||||
"\ufeffretained 😀\ufeff",
|
||||
);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
const before = await readFile(path);
|
||||
const imports = PYTHON_IMPORTS.split("\n")
|
||||
.filter((line) => !line.includes("agentenv_protocol"))
|
||||
.join("\n");
|
||||
const program = join(root, "read.py");
|
||||
await writeFile(
|
||||
program,
|
||||
`${imports}${PYTHON_HELPERS}${PYTHON_TASK_EVALUATOR}
|
||||
views = _views(Path(sys.argv[1]), ["state.db"])
|
||||
checks = [{"id": "row", "kind": "row_present", "file": "state.db", "table": "notes", "where": {"id": 7}}]
|
||||
missing = [{**checks[0], "where": {"id": 8}}]
|
||||
print(json.dumps({"views": views, "results": evaluate(checks + missing, views)}))
|
||||
`,
|
||||
);
|
||||
const output = JSON.parse(
|
||||
execFileSync(
|
||||
process.platform === "win32" ? "python" : "python3",
|
||||
["-I", "-B", program, root],
|
||||
{ encoding: "utf8" },
|
||||
),
|
||||
) as {
|
||||
views: Record<string, { format: string; view: { tables: unknown } }>;
|
||||
results: { result: boolean }[];
|
||||
};
|
||||
|
||||
expect(output.views["state.db"]).toMatchObject({
|
||||
format: "sqlite",
|
||||
truncated: false,
|
||||
view: {
|
||||
tables_truncated: false,
|
||||
},
|
||||
});
|
||||
expect(output.results.map(({ result }) => result)).toEqual([true, false]);
|
||||
expect(output.views["state.db"]?.view.tables).toEqual({
|
||||
notes: {
|
||||
columns: ["id", "part", 'body " text'],
|
||||
primary_key: ["part", "id"],
|
||||
rows: [
|
||||
[7, "a", "retained"],
|
||||
[null, "a", "\ufeffretained 😀\ufeff"],
|
||||
],
|
||||
truncated: false,
|
||||
},
|
||||
...(kind === "table"
|
||||
? {
|
||||
[name]: {
|
||||
columns: ["name", "type", "schema"],
|
||||
primary_key: [],
|
||||
rows: [["notes", "view", "main"]],
|
||||
truncated: false,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
expect(
|
||||
inspectSqliteDatabaseSnapshot(path, { path, table: "notes" }).rows,
|
||||
).toMatchObject({
|
||||
columns: ["id", "part", 'body " text'],
|
||||
values: [
|
||||
[
|
||||
{ type: "integer", value: "7" },
|
||||
{ type: "text", value: "a" },
|
||||
{ type: "text", value: "retained" },
|
||||
],
|
||||
[
|
||||
{ type: "null" },
|
||||
{ type: "text", value: "a" },
|
||||
{ type: "text", value: "\ufeffretained 😀\ufeff" },
|
||||
],
|
||||
],
|
||||
});
|
||||
expect((await readFile(path)).equals(before)).toBe(true);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user