sqlite3orm
Version:
ORM for sqlite3 and TypeScript/JavaScript
201 lines • 9.11 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.DbCatalogDAO = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const core_1 = require("../core");
const metadata_1 = require("../metadata");
const utils_1 = require("../utils");
class DbCatalogDAO {
sqldb;
constructor(sqldb) {
this.sqldb = sqldb;
}
readSchemas() {
const schemas = [];
return this.sqldb.all(`PRAGMA database_list`).then((res) => {
res.forEach((db) => schemas.push(db.name));
return schemas;
});
}
readTables(schemaName) {
const tables = [];
const quotedSchemaName = (0, utils_1.quoteSimpleIdentifier)(schemaName);
return this.sqldb.all(`select * from ${quotedSchemaName}.sqlite_master where type='table'`).then((res) => {
res.forEach((tab) => tables.push(tab.name));
return tables;
});
}
async readTableInfo(tableName, schemaName) {
try {
const { identName, identSchema } = (0, utils_1.splitSchemaIdentifier)(tableName);
tableName = identName;
schemaName = identSchema || schemaName || core_1.SQL_DEFAULT_SCHEMA;
const quotedName = (0, utils_1.quoteSimpleIdentifier)(tableName);
const quotedSchema = (0, utils_1.quoteSimpleIdentifier)(schemaName);
// TODO: sqlite3 issue regarding schema queries from multiple connections
// The result of table_info seems to be somehow cached, so subsequent calls to table_info may return wrong results
// The scenario where this problem was detected:
// connection 1: PRAGMA table_info('FOO_TABLE') => ok (no data)
// connection 2: PRAGMA table_info('FOO_TABLE') => ok (no data)
// connection 2: CREATE TABLE FOO_TABLE (...)
// connection 3: PRAGMA table_info('FOO_TABLE') => ok (data)
// connection 2: PRAGMA table_info('FOO_TABLE') => ok (data)
// connection 1: PRAGMA table_info('FOO_TABLE') => NOT OK (NO DATA)
// known workarounds:
// 1) perform all schema discovery and schema modifications from the same connection
// 2) if using a connection pool, do not recycle a connection after performing schema queries
// 3) not verified yet: using shared cache
// workaround for issue described above (required by e.g 'loopback-connector-sqlite3x')
this.sqldb.dirty = true;
const tableInfo = await this.callSchemaQueryPragma('table_info', quotedName, quotedSchema);
if (tableInfo.length === 0) {
return undefined;
}
const idxList = await this.callSchemaQueryPragma('index_list', quotedName, quotedSchema);
const fkList = await this.callSchemaQueryPragma('foreign_key_list', quotedName, quotedSchema);
const info = {
name: `${schemaName}.${tableName}`,
tableName,
schemaName,
columns: {},
primaryKey: [],
indexes: {},
foreignKeys: {},
};
tableInfo
.sort((colA, colB) => colA.pk - colB.pk)
.forEach((col) => {
const colInfo = {
name: col.name,
type: col.type,
typeAffinity: DbCatalogDAO.getTypeAffinity(col.type),
notNull: !!col.notnull,
defaultValue: col.dflt_value,
};
info.columns[col.name] = colInfo;
if (col.pk) {
info.primaryKey.push(col.name);
}
});
if (info.primaryKey.length === 1 && info.columns[info.primaryKey[0]].typeAffinity === 'INTEGER') {
// dirty hack to check if this column is autoincrementable
// not checked: if autoincrement is part of column/index/foreign key name
// not checked: if autoincrement is part of default literal text
// however, test is sufficient for autoupgrade
const schema = quotedSchema || '"main"';
const res = await this.sqldb.all(`select * from ${schema}.sqlite_master where type='table' and name=:tableName and UPPER(sql) like '%AUTOINCREMENT%'`, {
':tableName': tableName,
});
if (res && res.length === 1) {
info.autoIncrement = true;
}
}
const promises = [];
idxList.forEach((idx) => {
if (idx.origin !== 'pk') {
promises.push(new Promise((resolve, reject) => {
const idxInfo = {
name: idx.name,
unique: !!idx.unique,
partial: !!idx.partial,
columns: [],
};
this.callSchemaQueryPragma('index_xinfo', (0, utils_1.quoteSimpleIdentifier)(idx.name), quotedSchema)
.then((xinfo) => {
xinfo
.sort((idxColA, idxColB) => idxColA.seqno - idxColB.seqno)
.forEach((idxCol) => {
if (idxCol.cid >= 0) {
const idxColInfo = {
name: idxCol.name,
desc: !!idxCol.desc,
coll: idxCol.coll,
key: !!idxCol.key,
};
idxInfo.columns.push(idxColInfo);
}
});
return idxInfo;
})
.then((val) => resolve(val))
.catch(/* istanbul ignore next */ (err) => reject(err));
}));
}
});
const indexInfos = await Promise.all(promises);
indexInfos.forEach((idxInfo) => {
info.indexes[idxInfo.name] = idxInfo;
});
// NOTE: because we are currently not able to discover the FK constraint name
// (not reported by 'foreign_key_list' pragma)
// we are currently using a 'genericForeignKeyId' here, which is readable, but does not look like an identifier
let lastId;
let lastFk;
let fromCols = [];
let toCols = [];
fkList
.sort((fkA, fkB) => fkA.id * 1000 + fkA.seq - (fkB.id * 1000 + fkB.seq))
.forEach((fk) => {
if (lastId === fk.id) {
// continue
fromCols.push(fk.from);
toCols.push(fk.to);
}
else {
// old fk
if (lastFk) {
const fkInfo = {
refTable: lastFk.table,
columns: fromCols,
refColumns: toCols,
};
info.foreignKeys[metadata_1.FKDefinition.genericForeignKeyId(fromCols, lastFk.table, toCols)] = fkInfo;
}
// new fk
lastId = fk.id;
lastFk = fk;
fromCols = [];
toCols = [];
fromCols.push(fk.from);
toCols.push(fk.to);
}
});
if (lastFk) {
const fkInfo = {
refTable: lastFk.table,
columns: fromCols,
refColumns: toCols,
};
info.foreignKeys[metadata_1.FKDefinition.genericForeignKeyId(fromCols, lastFk.table, toCols)] = fkInfo;
}
return info;
}
catch (err) {
/* istanbul ignore next */
return Promise.reject(err);
}
}
callSchemaQueryPragma(pragmaName, identifierName, identifierSchema) {
return this.sqldb.all(`PRAGMA ${identifierSchema}.${pragmaName}(${identifierName})`);
}
static getTypeAffinity(typeDef) {
const type = typeDef.toUpperCase();
if (type.indexOf('INT') !== -1) {
return 'INTEGER';
}
const textMatches = /(CHAR|CLOB|TEXT)/.exec(type);
if (textMatches) {
return 'TEXT';
}
if (type.indexOf('BLOB') !== -1) {
return 'BLOB';
}
const realMatches = /(REAL|FLOA|DOUB)/.exec(type);
if (realMatches) {
return 'REAL';
}
return 'NUMERIC';
}
}
exports.DbCatalogDAO = DbCatalogDAO;
//# sourceMappingURL=DbCatalogDAO.js.map
;