mylogger/lib/model-loader.js

247 lines
6.7 KiB
JavaScript

const path = require('path');
const {loadConfig, toUpperCamelCase} = require('./util');
const MultiMap = require('./multi-map');
/**
* Loads model configuration.
*/
module.exports = class ModelLoader {
init(logger) {
const configDir = path.join(__dirname, '..');
const conf = loadConfig(configDir, 'logs');
const schemaMap = new MultiMap();
const logMap = new Map();
Object.assign(this, {
logger,
conf
});
Object.assign(logger, {
schemaMap,
logMap
});
for (const logName in conf.logs) {
const logConf = conf.logs[logName];
const schema = logConf.schema || logger.conf.dstDb.database;
const logInfo = {
name: logName,
conf: logConf,
schema,
table: parseTable(logConf.logTable, schema),
mainTable: parseTable(logConf.mainTable, schema)
};
logMap.set(logName, logInfo);
const mainTable = addTable(logConf.mainTable, logInfo);
mainTable.isMain = true;
if (logConf.tables)
for (const tableConf of logConf.tables){
const table = addTable(tableConf, logInfo);
if (table !== mainTable) {
Object.assign(table, {
main: mainTable,
isMain: false
});
}
}
}
function addTable(tableConf, logInfo) {
if (typeof tableConf == 'string')
tableConf = {name: tableConf};
const table = parseTable(tableConf.name, logInfo.schema);
let tableInfo = schemaMap.get(table.schema, table.name);
if (!tableInfo) {
tableInfo = {
conf: tableConf,
logInfo
};
schemaMap.set(table.schema, table.name, tableInfo);
}
let modelName = tableConf.modelName;
if (!modelName) {
modelName = conf.upperCaseTable
? toUpperCamelCase(table.name)
: table.name;
}
Object.assign(tableInfo, {
conf: tableConf,
modelName,
relation: tableConf.relation
});
return tableInfo;
}
}
async loadSchema() {
const {db, schemaMap} = this.logger;
const {conf} = this;
const excludeFields = new Set(conf.excludeFields);
const excludeRegex = conf.excludeRegex
? new RegExp(conf.excludeRegex) : null;
const localProps = [
'idName',
'showField',
'logFields'
];
const globalProps = [
'userField',
'rowExcludeField'
];
for (const [schema, table, tableInfo] of schemaMap) {
const tableConf = tableInfo.conf;
for (const prop of localProps)
tableInfo[prop] = tableConf[prop];
for (const prop of globalProps)
tableInfo[prop] = tableConf[prop] !== undefined
? tableConf[prop]
: conf[prop];
// Fetch columns & types
const columns = new Set();
Object.assign (tableInfo, {
castTypes: new Map(),
columns
});
if (tableConf.types)
for (const col in tableConf.types)
tableInfo.castTypes.set(col, tableConf.types[col]);
const [dbCols] = await db.query(
`SELECT
COLUMN_NAME \`col\`,
DATA_TYPE \`type\`
FROM information_schema.\`COLUMNS\`
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?`,
[table, schema]
);
const exclude = new Set(tableConf.exclude);
exclude.add(tableInfo.userField);
for (const {col, type} of dbCols) {
const isExcluded =
excludeFields.has(col)
|| (excludeRegex && excludeRegex.test(col))
|| exclude.has(col);
if (!isExcluded)
columns.add(col);
const castType = conf.castTypes[type];
if (castType && !tableInfo.castTypes.has(col))
tableInfo.castTypes.set(col, castType);
}
// Fetch primary key
const {idName} = tableInfo;
if (!idName) {
const [pks] = await db.query(
`SELECT COLUMN_NAME pk
FROM information_schema.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME = 'PRIMARY'
AND TABLE_NAME = ?
AND TABLE_SCHEMA = ?`,
[table, schema]
);
if (!pks.length)
throw new Error(`Primary key not found: ${schema}.${table}`);
if (pks.length > 1)
throw new Error(`Only one column primary is supported: ${schema}.${table}`);
const [{pk}] = pks;
tableInfo.idName = pk;
} else if (!columns.has(idName))
throw new Error(`Primary column not found: ${schema}.${table}.${idName}`);
// Get show field
const {showField} = tableInfo;
if (showField !== null) {
if (showField === undefined) {
if (!tableInfo.isMain || conf.logMainShowField)
for (const field of conf.showFields) {
if (columns.has(field)) {
tableInfo.showField = field;
break;
}
}
} else if (!columns.has(showField))
throw new Error(`Show column not found: ${schema}.${table}.${showField}`);
}
}
for (const [schema, table, tableInfo] of schemaMap) {
// Fetch relation to main table
if (!tableInfo.conf.relation && !tableInfo.isMain) {
const mainTable = tableInfo.logInfo.mainTable;
const mainInfo = schemaMap.get(mainTable.schema, mainTable.name);
const [mainRelations] = await db.query(
`SELECT COLUMN_NAME relation
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_NAME = ?
AND TABLE_SCHEMA = ?
AND REFERENCED_TABLE_NAME = ?
AND REFERENCED_TABLE_SCHEMA = ?
AND REFERENCED_COLUMN_NAME = ?`,
[
table,
schema,
mainTable.name,
mainTable.schema,
mainInfo.idName
]
);
if (!mainRelations.length)
throw new Error(`No relation to main table found for table: ${schema}.${table}`);
if (mainRelations.length > 1)
throw new Error(`Found more multiple relations to main table: ${schema}.${table}`);
for (const {relation} of mainRelations)
tableInfo.relation = relation;
}
// Set instance columns
const {columns} = tableInfo;
const cols = new Set(columns);
if (!conf.logId)
cols.delete(tableInfo.idName);
if (!conf.logRelation)
cols.delete(tableInfo.relation);
tableInfo.instanceColumns = [...cols.keys()];
}
}
}
function parseTable(tableString, defaultSchema) {
let name, schema;
const split = tableString.split('.');
if (split.length == 1) {
name = split[0];
schema = defaultSchema;
} else {
[schema, name] = split;
}
return {name, schema};
}