mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 19:36:12 +00:00
118 lines
2.9 KiB
TypeScript
118 lines
2.9 KiB
TypeScript
// SQLite schema test support reads schema files for shape assertions.
|
|
import { readFileSync } from "node:fs";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
|
|
/**
|
|
* Test helpers for comparing SQLite schema shape.
|
|
*
|
|
* The collected shape intentionally ignores SQLite autoindex suffixes so
|
|
* generated schema tests assert contract-relevant table, column, and index data.
|
|
*/
|
|
type ColumnShape = {
|
|
name: string;
|
|
type: string;
|
|
notnull: number;
|
|
dflt_value: unknown;
|
|
pk: number;
|
|
};
|
|
|
|
type IndexShape = {
|
|
name: string;
|
|
unique: number;
|
|
origin: string;
|
|
partial: number;
|
|
};
|
|
|
|
/** Comparable SQLite schema summary used by generated-schema tests. */
|
|
export type SqliteSchemaShape = Record<
|
|
string,
|
|
{
|
|
columns: ColumnShape[];
|
|
indexes: IndexShape[];
|
|
}
|
|
>;
|
|
|
|
type TableInfoRow = ColumnShape & {
|
|
cid: number;
|
|
};
|
|
|
|
type IndexListRow = IndexShape & {
|
|
seq: number;
|
|
};
|
|
|
|
type SqliteMasterRow = {
|
|
name: string;
|
|
};
|
|
|
|
/** Execute schema SQL in memory and return its comparable shape. */
|
|
export function createSqliteSchemaShapeFromSql(schemaUrl: URL): SqliteSchemaShape {
|
|
const db = new DatabaseSync(":memory:");
|
|
try {
|
|
db.exec(readFileSync(schemaUrl, "utf8"));
|
|
return collectSqliteSchemaShape(db);
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
/** Collect table columns and indexes from an open SQLite database. */
|
|
export function collectSqliteSchemaShape(db: DatabaseSync): SqliteSchemaShape {
|
|
const tableRows = db
|
|
.prepare(
|
|
`
|
|
SELECT name
|
|
FROM sqlite_master
|
|
WHERE type = 'table'
|
|
AND name NOT LIKE 'sqlite_%'
|
|
ORDER BY name ASC
|
|
`,
|
|
)
|
|
.all() as SqliteMasterRow[];
|
|
|
|
return Object.fromEntries(
|
|
tableRows.map((table) => [
|
|
table.name,
|
|
{
|
|
columns: collectColumns(db, table.name),
|
|
indexes: collectIndexes(db, table.name),
|
|
},
|
|
]),
|
|
);
|
|
}
|
|
|
|
function collectColumns(db: DatabaseSync, tableName: string): ColumnShape[] {
|
|
return (
|
|
db.prepare(`PRAGMA table_info(${quoteSqliteIdentifier(tableName)})`).all() as TableInfoRow[]
|
|
)
|
|
.map(({ name, type, notnull, dflt_value, pk }) => ({
|
|
name,
|
|
type,
|
|
notnull,
|
|
dflt_value,
|
|
pk,
|
|
}))
|
|
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
}
|
|
|
|
function collectIndexes(db: DatabaseSync, tableName: string): IndexShape[] {
|
|
return (
|
|
db.prepare(`PRAGMA index_list(${quoteSqliteIdentifier(tableName)})`).all() as IndexListRow[]
|
|
)
|
|
.map(({ name, unique, origin, partial }) => ({
|
|
name: normalizeAutoIndexName(name),
|
|
unique,
|
|
origin,
|
|
partial,
|
|
}))
|
|
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
}
|
|
|
|
function normalizeAutoIndexName(name: string): string {
|
|
// SQLite autoindex names include table-specific suffixes that do not affect schema behavior.
|
|
return name.startsWith("sqlite_autoindex_") ? "sqlite_autoindex" : name;
|
|
}
|
|
|
|
function quoteSqliteIdentifier(identifier: string): string {
|
|
return `"${identifier.replaceAll('"', '""')}"`;
|
|
}
|