UNPKG

@wbce-d9/schema

Version:

Utility for extracting information about existing DB schema

185 lines (184 loc) 7.95 kB
import extractMaxLength from '../utils/extract-max-length.js'; import extractType from '../utils/extract-type.js'; import { stripQuotes } from '../utils/strip-quotes.js'; export function parseDefaultValue(value) { if (value === null || value.trim().toLowerCase() === 'null') return null; return stripQuotes(value); } export default class SQLite { knex; constructor(knex) { this.knex = knex; } // Overview // =============================================================================================== async overview() { const tablesWithAutoIncrementPrimaryKeys = (await this.knex.select('name').from('sqlite_master').whereRaw(`sql LIKE "%AUTOINCREMENT%"`)).map(({ name }) => name); const tables = await this.tables(); const overview = {}; for (const table of tables) { const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table); if (table in overview === false) { const primaryKeys = columns.filter((column) => column.pk !== 0); overview[table] = { primary: primaryKeys.length !== 1 ? undefined : primaryKeys[0].name, columns: {}, }; } for (const column of columns) { overview[table].columns[column.name] = { table_name: table, column_name: column.name, default_value: column.pk === 1 && tablesWithAutoIncrementPrimaryKeys.includes(table) ? 'AUTO_INCREMENT' : parseDefaultValue(column.dflt_value), is_nullable: column.notnull == 0, is_generated: column.hidden !== 0, data_type: extractType(column.type), max_length: extractMaxLength(column.type), numeric_precision: null, numeric_scale: null, }; } } return overview; } // Tables // =============================================================================================== /** * List all existing tables in the current schema/database */ async tables() { const records = await this.knex .select('name') .from('sqlite_master') .whereRaw(`type = 'table' AND name NOT LIKE 'sqlite_%'`); return records.map(({ name }) => name); } async tableInfo(table) { const query = this.knex .select('name', 'sql') .from('sqlite_master') .where({ type: 'table' }) .andWhereRaw(`name NOT LIKE 'sqlite_%'`); if (table) { query.andWhere({ name: table }); } let records = await query; records = records.map((table) => ({ name: table.name, sql: table.sql, })); if (table) { return records[0]; } return records; } /** * Check if a table exists in the current schema/database */ async hasTable(table) { const results = await this.knex.select(1).from('sqlite_master').where({ type: 'table', name: table }); return results.length > 0; } // Columns // =============================================================================================== /** * Get all the available columns in the current schema/database. Can be filtered to a specific table */ async columns(table) { if (table) { const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table); return columns.map((column) => ({ table, column: column.name, })); } const tables = await this.tables(); const columnsPerTable = await Promise.all(tables.map(async (table) => await this.columns(table))); return columnsPerTable.flat(); } async columnInfo(table, column) { const getColumnsForTable = async (table) => { const tablesWithAutoIncrementPrimaryKeys = (await this.knex.select('name').from('sqlite_master').whereRaw(`sql LIKE "%AUTOINCREMENT%"`)).map(({ name }) => name); const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table); const foreignKeys = await this.knex.raw(`PRAGMA foreign_key_list(??)`, table); const indexList = await this.knex.raw(`PRAGMA index_list(??)`, table); const indexInfoList = await Promise.all(indexList.map((index) => this.knex.raw(`PRAGMA index_info(??)`, index.name))); return columns.map((raw) => { const foreignKey = foreignKeys.find((fk) => fk.from === raw.name); const indexIndex = indexInfoList.findIndex((list) => list.find((fk) => fk.name === raw.name)); const index = indexList[indexIndex]; const indexInfo = indexInfoList[indexIndex]; return { name: raw.name, table: table, data_type: extractType(raw.type), default_value: parseDefaultValue(raw.dflt_value), max_length: extractMaxLength(raw.type), /** @NOTE SQLite3 doesn't support precision/scale */ numeric_precision: null, numeric_scale: null, is_generated: raw.hidden !== 0, generation_expression: null, is_nullable: raw.notnull === 0, is_unique: !!index?.unique && indexInfo?.length === 1, is_primary_key: raw.pk === 1, has_auto_increment: raw.pk === 1 && tablesWithAutoIncrementPrimaryKeys.includes(table), foreign_key_column: foreignKey?.to || null, foreign_key_table: foreignKey?.table || null, }; }); }; if (!table) { const tables = await this.tables(); const columnsPerTable = await Promise.all(tables.map(async (table) => await getColumnsForTable(table))); return columnsPerTable.flat(); } if (table && !column) { return await getColumnsForTable(table); } const columns = await getColumnsForTable(table); return columns.find((columnInfo) => columnInfo.name === column); } /** * Check if a table exists in the current schema/database */ async hasColumn(table, column) { let isColumn = false; const results = await this.knex.raw(`SELECT COUNT(*) AS ct FROM pragma_table_xinfo('${table}') WHERE name='${column}'`); const resultsVal = results[0]['ct']; if (resultsVal !== 0) { isColumn = true; } return isColumn; } /** * Get the primary key column for the given table */ async primary(table) { const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table); const pkColumn = columns.find((col) => col.pk !== 0); return pkColumn?.name || null; } // Foreign Keys // =============================================================================================== async foreignKeys(table) { if (table) { const keys = await this.knex.raw(`PRAGMA foreign_key_list(??)`, table); return keys.map((key) => ({ table, column: key.from, foreign_key_table: key.table, foreign_key_column: key.to, on_update: key.on_update, on_delete: key.on_delete, constraint_name: null, })); } const tables = await this.tables(); const keysPerTable = await Promise.all(tables.map(async (table) => await this.foreignKeys(table))); return keysPerTable.flat(); } }