@wbce-d9/schema
Version:
Utility for extracting information about existing DB schema
185 lines (184 loc) • 7.95 kB
JavaScript
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();
}
}