@bazilio-san/af-stream
Version:
Data stream from database table
121 lines (99 loc) • 3.97 kB
text/typescript
/* eslint-disable class-methods-use-this */
import { IDbConstructorOptions, IEmPortionOfDataCount, IEmPortionOfDataSql, TDbRecord } from '../interfaces';
import { DEBUG_SQL } from '../constants';
export class DbBase {
public readonly options: IDbConstructorOptions;
public dbInfo: string;
public fieldsList: string;
public schemaAndTable: string;
public fieldsArray: string[];
public tsField: string;
public idFields: string[] | any[];
public sortBy: string;
private ld: string;
private rd: string;
private noLock: string = '';
private readonly recordsetPropName: string;
constructor (options: IDbConstructorOptions) {
this.options = options;
const { streamConfig, dbConfig } = options;
let host;
if (dbConfig.dialect === 'mssql') {
this.ld = '[';
this.rd = ']';
this.recordsetPropName = 'recordset';
this.noLock = ' WITH(NOLOCK) ';
host = dbConfig.server;
} else {
this.ld = '"';
this.rd = '"';
this.recordsetPropName = 'rows';
({ host } = dbConfig);
}
const { ld, rd } = this;
this.dbInfo = `${dbConfig.user}@${ld}${host}:${dbConfig.port}${rd}.${ld}${dbConfig.database}${rd}`;
const { fields, src } = streamConfig;
const { schema, table, tsField, idFields } = src;
if (Array.isArray(fields)) {
this.fieldsArray = [...fields];
} else {
this.fieldsArray = Object.keys(fields);
}
this.fieldsList = this.fieldsArray.map((fName) => `${ld}${fName}${rd}`).join(', ');
this.schemaAndTable = `${ld}${schema}${rd}.${ld}${table}${rd}`;
this.tsField = tsField;
this.idFields = idFields;
this.sortBy = [tsField, ...idFields].map((f) => `${ld}${f}${rd}`).join(',');
}
async getPool (): Promise<any> {
return null;
}
async close (): Promise<boolean> {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
async query (strSQL: string): Promise<any> {
return null;
}
async _getColumnsNames (): Promise<string[]> {
return [];
}
async init (): Promise<void> {
const { schemaAndTable, options: { exitOnError, streamConfig: { streamId } }, fieldsArray } = this;
const columnsNames = await this._getColumnsNames();
const unknownFields = fieldsArray.filter((name) => !columnsNames.includes(name));
if (unknownFields.length) {
exitOnError(`Table ${schemaAndTable} is missing fields specified in the ${streamId} stream configuration:\n\t${unknownFields.join('\n\t')} `);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
limitIt (strSQL: string, limit: number): string {
return strSQL;
}
getPortionSQL ({ startTs, endTs, limit }: { startTs: number, endTs: number, limit: number }): string {
const { tsField, ld, rd, options: { millis2dbFn } } = this;
let sql = `SELECT ${this.fieldsList}
FROM ${this.schemaAndTable}
WHERE ${ld}${tsField}${rd} >= ${millis2dbFn(startTs)}
AND ${ld}${tsField}${rd} <= ${millis2dbFn(endTs)}
ORDER BY ${this.sortBy}`;
if (limit) {
sql = this.limitIt(sql, limit);
}
return sql;
}
async getPortionOfData ({ startTs, endTs, limit }: { startTs: number, endTs: number, limit: number }): Promise<TDbRecord[]> {
const { options: { eventEmitter, streamConfig: { streamId } }, dbInfo } = this;
const sql = this.getPortionSQL({ startTs, endTs, limit });
if (DEBUG_SQL) {
const payload: IEmPortionOfDataSql = { streamId, sql, startTs, endTs, limit, dbInfo };
eventEmitter.emit('get-portion-of-data-sql', payload);
}
const result = await this.query(sql);
if (DEBUG_SQL) {
const payload: IEmPortionOfDataCount = { streamId, sql, count: result?.[this.recordsetPropName]?.length };
eventEmitter.emit('get-portion-of-data-count', payload);
}
return result?.[this.recordsetPropName] || [];
}
}