iagate-querykit
Version:
QueryKit: lightweight TypeScript query toolkit with models, views, triggers, events, scheduler and adapters (better-sqlite3).
130 lines (129 loc) • 5.29 kB
JavaScript
import { QueryKitConfig } from './config';
import { scheduler } from './scheduler';
import { table } from './table';
function namesByDialect(dialect) {
switch (dialect) {
case 'sqlite': return [{ sql: "SELECT name FROM sqlite_master WHERE type='view'", map: (r) => r.name }];
case 'mysql': return [{ sql: "SHOW FULL TABLES WHERE TABLE_TYPE = 'VIEW'", map: (r) => Object.values(r)[0] }];
case 'postgres': return [{ sql: "SELECT table_name FROM information_schema.views WHERE table_schema = current_schema()", map: (r) => r.table_name }];
case 'mssql': return [{ sql: "SELECT name FROM sys.views", map: (r) => r.name }];
case 'oracle': return [{ sql: "SELECT VIEW_NAME AS name FROM USER_VIEWS", map: (r) => r.name }];
default:
return [
{ sql: "SELECT name FROM sqlite_master WHERE type='view'", map: (r) => r.name },
{ sql: "SHOW FULL TABLES WHERE TABLE_TYPE = 'VIEW'", map: (r) => Object.values(r)[0] },
{ sql: "SELECT table_name FROM information_schema.views WHERE table_schema = current_schema()", map: (r) => r.table_name },
{ sql: "SELECT name FROM sys.views", map: (r) => r.name },
{ sql: "SELECT VIEW_NAME AS name FROM USER_VIEWS", map: (r) => r.name },
];
}
}
function escapeSqlLiteral(value) {
if (value === null || value === undefined)
return 'NULL';
if (typeof value === 'number' && isFinite(value))
return String(value);
if (typeof value === 'bigint')
return String(value);
if (typeof value === 'boolean')
return value ? '1' : '0';
if (value instanceof Date)
return `'${value.toISOString().replace(/'/g, "''")}'`;
if (Buffer && Buffer.isBuffer && Buffer.isBuffer(value))
return `X'${value.toString('hex')}'`;
if (typeof value === 'object')
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
return `'${String(value).replace(/'/g, "''")}'`;
}
function inlineBindings(sql, bindings) {
if (!bindings || bindings.length === 0)
return sql;
let i = 0;
return sql.replace(/\?/g, () => {
if (i >= bindings.length)
return '?';
const lit = escapeSqlLiteral(bindings[i++]);
return lit;
});
}
export class ViewManager {
async createOrReplaceView(viewName, query) {
const { sql, bindings } = query.toSql();
await this.dropView(viewName);
const inlined = inlineBindings(sql, bindings);
const createViewSql = `CREATE VIEW ${viewName} AS ${inlined}`;
const exec = QueryKitConfig.defaultExecutor;
if (!exec)
throw new Error('No executor configured for QueryKit');
if (exec.runSync)
exec.runSync(createViewSql, []);
else
await exec.executeQuery(createViewSql, []);
}
scheduleViewRefresh(viewName, query, intervalMs) {
const task = () => this.createOrReplaceView(viewName, query);
scheduler.schedule(`refresh-view-${viewName}`, task, intervalMs);
}
unscheduleViewRefresh(viewName) {
scheduler.unschedule(`refresh-view-${viewName}`);
}
async dropView(viewName) {
const dropViewSql = `DROP VIEW IF EXISTS ${viewName}`;
const exec = QueryKitConfig.defaultExecutor;
if (!exec)
throw new Error('No executor configured for QueryKit');
if (exec.runSync)
exec.runSync(dropViewSql, []);
else
await exec.executeQuery(dropViewSql, []);
}
listViews() {
const exec = QueryKitConfig.defaultExecutor;
if (!exec)
throw new Error('No executor configured for QueryKit');
if (!exec.executeQuerySync)
return [];
const candidates = namesByDialect(exec.dialect || QueryKitConfig.defaultDialect);
for (const c of candidates) {
try {
const res = exec.executeQuerySync(c.sql, []);
const rows = res?.data || [];
const names = rows.map(c.map).filter(Boolean);
if (names.length || rows.length >= 0)
return names;
}
catch { }
}
return [];
}
viewExists(viewName) {
const names = this.listViews();
return names.includes(viewName);
}
async listViewsAsync() {
const exec = QueryKitConfig.defaultExecutor;
if (!exec)
throw new Error('No executor configured for QueryKit');
if (exec.executeQuerySync)
return this.listViews();
const candidates = namesByDialect(exec.dialect || QueryKitConfig.defaultDialect);
for (const c of candidates) {
try {
const res = await exec.executeQuery(c.sql, []);
const rows = res?.data || [];
const names = rows.map(c.map).filter(Boolean);
if (names.length || rows.length >= 0)
return names;
}
catch { }
}
return [];
}
async viewExistsAsync(viewName) {
const names = await this.listViewsAsync();
return names.includes(viewName);
}
view(viewName) {
return table(viewName);
}
}