Files

123 lines
3.9 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const { getTableName } = require('drizzle-orm');
const { getTableConfig } = require('drizzle-orm/sqlite-core');
const { migrateLocalSqlitePath } = require('../dist/migration/migration');
const { localSqliteSchema } = require('../dist/storage/schema');
function sorted(values) {
return [...values].sort((left, right) =>
JSON.stringify(left).localeCompare(JSON.stringify(right)),
);
}
function tableSql(client, tableName) {
const row = client
.prepare(
`SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = ?`,
)
.get(tableName);
assert.equal(typeof row?.sql, 'string');
return row.sql;
}
function catalogChecks(sql) {
return [...sql.matchAll(/CONSTRAINT\s+([A-Za-z0-9_]+)\s+CHECK\b/gi)].map(
(match) => match[1],
);
}
function catalogForeignKeys(client, tableName) {
const grouped = new Map();
for (const entry of client
.prepare(`PRAGMA foreign_key_list("${tableName}")`)
.all()) {
const current = grouped.get(entry.id) ?? {
columns: [],
foreignTable: entry.table,
foreignColumns: [],
onDelete: entry.on_delete,
onUpdate: entry.on_update,
};
current.columns[entry.seq] = entry.from;
current.foreignColumns[entry.seq] = entry.to;
grouped.set(entry.id, current);
}
return [...grouped.values()];
}
function drizzleForeignKeys(config) {
return config.foreignKeys.map((foreignKey) => {
const reference = foreignKey.reference();
return {
columns: reference.columns.map(({ name }) => name),
foreignTable: getTableName(reference.foreignTable),
foreignColumns: reference.foreignColumns.map(({ name }) => name),
onDelete: (foreignKey.onDelete ?? 'no action').toUpperCase(),
onUpdate: (foreignKey.onUpdate ?? 'no action').toUpperCase(),
};
});
}
test('typed SQLite schema matches every reviewed table, column, index, check and foreign key', async (t) => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-schema-lockstep-'));
const databasePath = path.join(directory, 'qinglong3.sqlite');
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
const client = new DatabaseSync(databasePath, { readOnly: true });
try {
const actualTables = client
.prepare(
`SELECT name FROM sqlite_schema
WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`,
)
.all()
.map(({ name }) => name);
const drizzleTables = Object.values(localSqliteSchema).map(getTableConfig);
assert.deepEqual(
actualTables,
drizzleTables.map(({ name }) => name).sort(),
);
for (const config of drizzleTables) {
const columns = client
.prepare(`PRAGMA table_info("${config.name}")`)
.all()
.map(({ name }) => name);
assert.deepEqual(
columns,
config.columns.map(({ name }) => name),
`${config.name} columns`,
);
const indexes = client
.prepare(`PRAGMA index_list("${config.name}")`)
.all()
.map(({ name }) => name)
.filter((name) => !name.startsWith('sqlite_autoindex_'));
assert.deepEqual(
indexes.sort(),
config.indexes.map(({ config: value }) => value.name).sort(),
`${config.name} indexes`,
);
assert.deepEqual(
catalogChecks(tableSql(client, config.name)).sort(),
config.checks.map(({ name }) => name).sort(),
`${config.name} checks`,
);
assert.deepEqual(
sorted(catalogForeignKeys(client, config.name)),
sorted(drizzleForeignKeys(config)),
`${config.name} foreign keys`,
);
}
} finally {
client.close();
}
});