mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(sqlite): honor selected-table reply budgets (#4216)
This commit is contained in:
@@ -255,14 +255,29 @@ const readSchema = (
|
||||
);
|
||||
let selectedTable: SelectedTableFacts | undefined;
|
||||
const metadata = new Map<string, z.output<typeof tableRow>>();
|
||||
// Names bound the minimum eventual schema output while metadata is collected.
|
||||
// The full schema objects below account for them in the actual reply budget.
|
||||
const selectedKey =
|
||||
selectedName === undefined ? undefined : sqliteIdentifierKey(selectedName);
|
||||
const unresolvedVirtualTables: { name: string; prefix: string }[] = [];
|
||||
// Names bound eventual output only when the whole catalog is requested.
|
||||
// Selected scope retains one table and at most one possible shadow parent;
|
||||
// unrelated names are examined without retaining them or charging the reply.
|
||||
const metadataBudget = new OutputBudget();
|
||||
for (const raw of db.prepare("PRAGMA main.table_list").iterate()) {
|
||||
const value = tableRow.parse(raw);
|
||||
if (value.schema === "main") {
|
||||
if (value.schema !== "main") continue;
|
||||
if (selectedName === undefined) {
|
||||
metadataBudget.retain(value.name);
|
||||
metadata.set(value.name, value);
|
||||
} else if (value.name === selectedName) {
|
||||
metadata.set(value.name, value);
|
||||
}
|
||||
if (value.type === "virtual" && value.ncol === 0) {
|
||||
const prefix = sqliteIdentifierKey(`${value.name}_`);
|
||||
if (
|
||||
selectedKey === undefined ||
|
||||
(unresolvedVirtualTables.length === 0 && selectedKey.startsWith(prefix))
|
||||
)
|
||||
unresolvedVirtualTables.push({ name: value.name, prefix });
|
||||
}
|
||||
}
|
||||
// A trigger stores its target as written in CREATE TRIGGER, which SQLite
|
||||
@@ -278,12 +293,6 @@ const readSchema = (
|
||||
);
|
||||
return foldedNames.get(sqliteIdentifierKey(written)) ?? written;
|
||||
};
|
||||
const unresolvedVirtualTables = [...metadata.values()]
|
||||
.filter((value) => value.type === "virtual" && value.ncol === 0)
|
||||
.map((value) => ({
|
||||
name: value.name,
|
||||
prefix: sqliteIdentifierKey(`${value.name}_`),
|
||||
}));
|
||||
const shadowParentOf = (
|
||||
name: string,
|
||||
listed: z.output<typeof tableRow>,
|
||||
|
||||
@@ -235,6 +235,53 @@ it("counts schema names once when enforcing the complete reply budget", async ()
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps unrelated catalog names outside the selected-table reply budget", async () => {
|
||||
const path = await createDatabase(
|
||||
"CREATE TABLE selected(value TEXT); INSERT INTO selected VALUES ('retained');",
|
||||
);
|
||||
const producer = new DatabaseSync(path);
|
||||
try {
|
||||
producer.exec("BEGIN");
|
||||
for (let index = 0; index < 60; index++) {
|
||||
const name = `unrelated_${index}_${"x".repeat(300 * 1024)}`;
|
||||
producer.exec(`CREATE TABLE "${name}" (value TEXT)`);
|
||||
}
|
||||
producer.exec("COMMIT");
|
||||
} finally {
|
||||
producer.close();
|
||||
}
|
||||
const selected = inspectSqliteDatabaseSnapshot(path, {
|
||||
path,
|
||||
table: "selected",
|
||||
});
|
||||
expect(selected.schema).toMatchObject({
|
||||
scope: "selected_table",
|
||||
completeness: "complete",
|
||||
tables: [{ name: "selected" }],
|
||||
indexes: [],
|
||||
views: [],
|
||||
triggers: [],
|
||||
});
|
||||
expect(selected.schema.tables).toHaveLength(1);
|
||||
expect(selected.rows).toMatchObject({
|
||||
returned_rows: 1,
|
||||
values: [[{ type: "text", value: "retained" }]],
|
||||
});
|
||||
expect(() =>
|
||||
inspectSqliteDatabaseSnapshot(path, { path, table: "absent" }),
|
||||
).toThrowError(
|
||||
expect.objectContaining({
|
||||
reason: "selection",
|
||||
message:
|
||||
"Selected ordinary table does not exist with that exact name: absent",
|
||||
}),
|
||||
);
|
||||
for (const schema_scope of ["inventory", "database"] as const)
|
||||
expect(() =>
|
||||
inspectSqliteDatabaseSnapshot(path, { path, schema_scope }),
|
||||
).toThrowError(expect.objectContaining({ reason: "output-limit" }));
|
||||
});
|
||||
|
||||
it("rejects malformed schema bytes instead of returning replacement text", async () => {
|
||||
const path = await createDatabase(
|
||||
"CREATE TABLE records(value TEXT /*schema-marker*/)",
|
||||
|
||||
Reference in New Issue
Block a user