mylogger/lib/show-db.js

250 lines
6.7 KiB
JavaScript

const MultiMap = require("./multi-map");
/**
* Caches and sets user friendly values for instaces and relations.
*/
module.exports = class ShowDb {
init(logger) {
Object.assign(this, {
logger,
conf: logger.conf.showCache,
tables: new MultiMap(),
cache: new MultiMap()
});
}
checkDb() {
const {conf, cache} = this;
const now = Date.now();
const dbOutdated = this.loops % conf.maxLoops == 0
|| this.lastFlush > now + conf.life * 1000
if (dbOutdated) {
cache.clear();
this.loops = 0;
this.lastFlush = now;
}
this.loops++;
}
async loadSchema() {
const {logger, tables} = this;
const {db, schemaMap} = logger;
tables.clear();
// Fetch relations with other tables
for (const [schema, table, tableInfo] of schemaMap) {
const [relations] = await db.query(
`SELECT
COLUMN_NAME \`col\`,
REFERENCED_TABLE_SCHEMA \`schema\`,
REFERENCED_TABLE_NAME \`table\`
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_NAME = ?
AND TABLE_SCHEMA = ?
AND COLUMN_NAME IN (?)
AND REFERENCED_TABLE_NAME IS NOT NULL`,
[
table,
schema,
Array.from(tableInfo.columns.keys())
]
);
tableInfo.relations = new Map();
for (const {col, schema, table} of relations) {
if (col == tableInfo.relation) continue;
tableInfo.relations.set(col, {schema, table});
tables.setIfEmpty(schema, table, {});
}
}
const relatedList = Array.from(tables.keys());
// Fetch primary key of related tables
const [res] = await db.query(
`SELECT
TABLE_SCHEMA \`schema\`,
TABLE_NAME \`table\`,
COLUMN_NAME \`idName\`,
COUNT(*) nPks
FROM information_schema.\`COLUMNS\`
WHERE (TABLE_SCHEMA, TABLE_NAME) IN (?)
AND COLUMN_KEY = 'PRI'
GROUP BY TABLE_NAME, TABLE_SCHEMA
HAVING nPks = 1`,
[relatedList]
);
for (const {schema, table, idName} of res)
tables.get(schema, table).idName = idName;
// Fetch show field of related tables
const showFields = logger.modelLoader.conf.showFields;
const [result] = await db.query(
`SELECT
TABLE_SCHEMA \`schema\`,
TABLE_NAME \`table\`,
COLUMN_NAME \`col\`
FROM information_schema.\`COLUMNS\`
WHERE (TABLE_SCHEMA, TABLE_NAME) IN (?)
AND COLUMN_NAME IN (?)
AND COLUMN_KEY <> 'PRI'`,
[relatedList, showFields]
);
for (const {schema, table, col} of result) {
const tableInfo = tables.get(schema, table);
let save;
if (tableInfo.showField) {
const newIndex = showFields.indexOf(col);
const oldIndex = showFields.indexOf(tableInfo.showField);
save = newIndex < oldIndex;
} else
save = true;
if (save) tableInfo.showField = col;
}
// Remove tables without showable columns and prepare SELECT statements
for (const [schema, table] of relatedList) {
const tableInfo = tables.get(schema, table);
const {idName, showField} = tableInfo;
if (!idName || !showField || idName == showField) {
tables.delete(schema, table);
continue;
}
const sqlShowField = db.escapeId(showField);
const sqlIdName = db.escapeId(idName);
const sqlTable = `${db.escapeId(schema)}.${db.escapeId(table)}`;
tableInfo.selectStmt =
`SELECT ${sqlIdName} \`id\`, ${sqlShowField} \`val\`
FROM ${sqlTable}
WHERE ${sqlIdName} IN (?)`;
}
// Remove relations without showable tables and set show configuration
for (const [schema, table, tableInfo] of schemaMap) {
const {relations} = tableInfo;
for (const [col, relation] of relations) {
if (!tables.has(relation.schema, relation.table))
relations.delete(col);
}
const {showRelation} = tableInfo.conf;
if (showRelation) {
if (!relations.has(showRelation))
throw new Error(`Relation not found: ${schema}.${table}.${showRelation}`);
Object.assign(tableInfo, {
showField: showRelation +'$',
showRelation
});
}
}
}
async getValues(db, ops) {
const {tables, cache} = this;
const fetchMap = new MultiMap();
this.checkDb();
// Fetch relations ids
for (const op of ops) {
const {
relations,
showRelation
} = op.tableInfo;
for (const change of op.changes) {
let rows;
if (op.action == 'update')
rows = [change.newI, change.oldI];
else
rows = [change.instance];
if (showRelation)
rows.push({[showRelation]: change.row[showRelation]});
for (const row of rows)
for (const col in row) {
const relation = relations.get(col);
if (!relation) continue;
const {schema, table} = relation;
const id = row[col];
let ids = cache.get(schema, table);
if (ids && ids.has(id)) continue;
ids = fetchMap.get(schema, table);
if (!ids) fetchMap.set(schema, table, ids = new Set());
ids.add(id);
}
}
}
// Query show values to database
for (const [schema, table, fetchIds] of fetchMap) {
const tableInfo = tables.get(schema, table);
const [res] = await db.query(
tableInfo.selectStmt,
[Array.from(fetchIds.keys())]
);
let ids = cache.get(schema, table);
if (!ids) cache.set(schema, table, ids = new Map());
for (const row of res)
ids.set(row.id, row.val);
}
// Fill rows with show values
for (const op of ops) {
const {
relations,
showRelation,
showField
} = op.tableInfo;
for (const change of op.changes) {
let rows;
if (op.action == 'update')
rows = [change.newI, change.oldI];
else
rows = [change.instance];
for (const row of rows)
for (const col in row) {
const relation = relations.get(col);
if (!relation) continue;
const showValue = getValue(relation, row, col);
if (showValue) row[col +'$'] = showValue;
}
const {row} = change;
if (showRelation) {
const relation = relations.get(showRelation);
change.modelValue = getValue(relation, row, showRelation);
} else if (showField)
change.modelValue = row[showField];
}
}
function getValue(relation, row, col) {
const {schema, table} = relation;
const ids = cache.get(schema, table);
return ids && ids.get(row[col])
}
}
}