UNPKG

@egi/smart-db

Version:

Unified Smart DB Access

1 lines 14.1 kB
import fs from"fs";import _ from"lodash";import{BehaviorSubject}from"rxjs";import{AbstractModel}from"./models/abstract-model";import{SmartDbDictionary}from"./models/smart-db-dictionary";import{SmartDbLogModel}from"./models/smart-db-log-model";import{IN,smartDbToDate,toSmartDbDate,toSmartDbTimestamp}from"./smart-db-globals";import{FieldNamingStyle,SqlFieldOperationType,SqlOperationType}from"./smart-db-interfaces";import{SmartDbLog}from"./smart-db-log";import{SmartDbSqlBuildData}from"./smart-db-sql-build-data";import{SmartDbUpgradeManager}from"./smart-db-upgrade-manager";import{SmartError,SmartErrorLocation}from"./smart-error";export class SmartDb{constructor(t,e){this.dictionaries=[],this.dbConnector=t,this.smartDbLog=new SmartDbLog(e?null:this),this.dbLogging=!e,this.db=null,this._isReady=null,this._onReady=new BehaviorSubject(this._isReady)}buildSelectStatement(t,e){e||(e={});const r=this.buildSelectSectionStatement(t,e);if(_.forEach(["union","minus","intersect"],(t=>{let s=_.get(e,t);s&&(_.isArray(s)||(s=[s]),_.forEach(s,(e=>{const s=this.buildSelectSectionStatement(e.model,e);r.sql+=" "+t.toUpperCase()+" ",r.append(s)})))})),e.orderBy){r.sql+=" ORDER BY ";const s=_.isString(e.orderBy)?e.orderBy.split(/ *, */):e.orderBy;r.sql+=s.map((e=>{let r="";const s=e.match(/^(\w*) (asc|desc)$/i);return s&&(e=s[1],r=s[2].toUpperCase()),e=this.translateFieldName(t,e),r&&(e+=" "+r),e})).join(", ")}return e.limit&&(e.limit.limit&&(r.sql+=" LIMIT "+e.limit.limit.toString()),e.limit.offset&&(r.sql+=" OFFSET "+e.limit.offset.toString())),this.lastBuildData=r,r}close(){return new Promise(((t,e)=>{try{this.isReady=!1,t(this.closeSync())}catch(t){e(t)}}))}closeSync(){throw new Error("Method not implemented (close)")}commit(){return new Promise(((t,e)=>{try{this.commitSync(),t()}catch(t){e(t)}}))}commitSync(){throw new Error("Method not implemented (commit)")}delete(t,e){return new Promise(((r,s)=>{const a=this.buildDeleteStatement(t,e);this.statementRun(a).then((t=>{t?r(t.changes):s(this.getLastError())})).catch((t=>{this.lastError=t,s(t)}))}))}deleteSync(t,e){if(this.supportSyncCalls)return this.saveExecute((()=>{const r=this.buildDeleteStatement(t,e);return this.statementRunSync(r).changes}));throw"This database driver doesn't support sync calls (deleteSync)"}exec(t){return new Promise(((e,r)=>{try{this.execSync(t)?e():r(this.getLastError())}catch(t){r(t)}}))}execScript(t,e){return new Promise((async(r,s)=>{const a=this.scriptParser(t);let i=!1;for(const t of a)try{await this.exec(t)}catch(r){if(!e||"drop "!=t.trim().substring(0,5).toLowerCase()){i=!0,this.lastBuildData=new SmartDbSqlBuildData(t),this.lastError=r;break}}i?(await this.rollback(),s(this.lastError)):(await this.commit(),r())}))}execScriptSync(t){if(!this.supportSyncCalls)throw"This database driver doesn't support sync calls (execScriptSync)";{const e=this.scriptParser(t);for(const t of e)try{this.execSync(t)}catch(e){if("drop "!=t.substring(0,5).toLowerCase())throw this.lastBuildData=new SmartDbSqlBuildData(t),this.lastError=e,e}this.commitSync()}}execSync(t){throw new Error("Method not implemented (exec)")}exists(t,e,r){return new Promise(((s,a)=>{try{s(this.existsSync(t,e,r))}catch(t){a(t)}}))}existsSync(t,e,r){throw new Error("Method not implemented (exists)")}get(t,e){return new Promise(((r,s)=>{e||(e={});const a=this.buildSelectStatement(t,e);e.firstOnly||e.count?this.statementGet(a).then((s=>{r(this.prepareResultRow(t,s,e))})).catch((t=>{s(t)})):this.statementGetAll(a).then((s=>{r(this.prepareResultRows(t,s,e))})).catch((t=>{s(t)}))}))}getAll(t,e,r,s,a){return this.get(t,{where:e,fields:r,orderBy:s,limit:a})}getAllSync(t,e,r,s,a){return this.getSync(t,{where:e,fields:r,orderBy:s,limit:a})}getDb(){return this.db}getDbConnector(){return this.dbConnector}getDbQuote(){return'"'}getFirst(t,e,r,s){return this.get(t,{firstOnly:!0,where:e,fields:r,orderBy:s})}getFirstSync(t,e,r,s){return this.getSync(t,{firstOnly:!0,where:e,fields:r,orderBy:s})}getLastBuildData(){return this.lastBuildData}getLastError(){return this.lastError}getLogger(){return this.smartDbLog}getSync(t,e){if(this.supportSyncCalls)return this.saveExecute((()=>{e||(e={});const r=this.buildSelectStatement(t,e);let s;if(e.firstOnly||e.count){const a=this.statementGetSync(r);s=this.prepareResultRow(t,a,e)}else{const a=this.statementGetAllSync(r);s=this.prepareResultRows(t,a,e)}return s}));throw"This database driver doesn't support sync calls (getSync)"}hasConcurrentTransactions(){return!1}hasTransaction(){throw new Error("Method not implemented (hasTransaction)")}initDb(t){return new Promise(((e,r)=>{this.dictionaries.push(SmartDbDictionary);const s=new SmartDbUpgradeManager(this);let a;a=import.meta&&import.meta.url?import.meta.url.replace(/file:\/\//,"").replace(/\/[^/]*$/,""):__dirname;const i={module:"smart-db-core",sqlFilesDirectory:a+"/assets/"+this.getDatabaseType()};s.prepareDatabase(i).then((a=>{this.exists(SmartDbLogModel).then((i=>{this.smartDbLog.setDbLogging(this.dbLogging&&i),s.prepareDatabase(t).then((t=>{e([a,t])}),(t=>{r(t)}))}))})).catch((t=>{r(t)}))}))}insert(t,e){return new Promise(((r,s)=>{const a=this.buildInsertStatement(t,e);this.statementRun(a).then((t=>{t?r(t.lastId):s(this.getLastError())})).catch((t=>{s(t)}))}))}insertSync(t,e){if(this.supportSyncCalls)return this.saveExecute((()=>{const r=this.buildInsertStatement(t,e);return this.statementRunSync(r).lastId}));throw"This database driver doesn't support sync calls (insertSync)"}rollback(){return new Promise(((t,e)=>{try{this.rollbackSync(),t()}catch(t){e(t)}}))}rollbackSync(){throw new Error("Method not implemented (rollback)")}toDate(t){return smartDbToDate(t)}toDbDate(t){return toSmartDbDate(t)}toDbTimestamp(t){return toSmartDbTimestamp(t)}update(t,e,r){return new Promise(((s,a)=>{const i=this.buildUpdateStatement(t,e,r);this.statementRun(i).then((t=>{t?s(t.changes):a(this.getLastError())})).catch((t=>{a(t)}))}))}updateSync(t,e,r){if(this.supportSyncCalls)return this.saveExecute((()=>{const s=this.buildUpdateStatement(t,e,r);return this.statementRunSync(s).changes}));throw"This database driver doesn't support sync calls (updateSync)"}statementRun(t){return new Promise(((e,r)=>{try{e(this.statementRun(t))}catch(t){r(t)}}))}statementRunSync(t){throw new Error("Method not implemented (statementRun)")}statementGet(t){return new Promise(((e,r)=>{try{e(this.statementGetSync(t))}catch(t){r(t)}}))}statementGetSync(t){throw new Error("Method not implemented (statementGet)")}statementGetAll(t){return new Promise(((e,r)=>{try{e(this.statementGetAllSync(t))}catch(t){r(t)}}))}statementGetAllSync(t){throw new Error("Method not implemented (statementGetAll)")}buildWhere(t,e,r){const s=new SmartDbSqlBuildData;return e&&_.keys(e).length>0&&(r||(r="AND",s.sql+=" WHERE "),s.sql+=_.map(e,((e,a)=>{const i=a.toUpperCase();if("AND"==i||"OR"==i){let r="";return(_.isArray(e)?e:[e]).forEach(((e,a)=>{const n=this.buildWhere(t,e,i);a>0&&(r+=" "+i+" "),r+=n.sql,s.values=_.concat(s.values,n.values)})),r="("+r+")",r}if("EXPRESSION"==i){let a=[];return _.forEach(e,(e=>{const r=this.prepareField(t,e.compare,s.values),i=this.prepareField(t,e.with,s.values);let n;n=_.isString(e.operation)&&SqlOperationType[e.operation]?SqlOperationType[e.operation]:e.operation||SqlOperationType.EQ,a.push(`${r} ${n} ${i}`)})),a.join(" "+r+" ")}{let r,i,n=!0;if(_.isArray(e)&&(e=IN(e)),_.isObject(e)){const t=e;switch(_.isString(t.operation)&&SqlOperationType[t.operation]&&(t.operation=SqlOperationType[t.operation]),t.operation){case SqlOperationType.IN:case SqlOperationType.NOT_IN:const e=new Array(t.value.length);e.fill("?"),r=t.operation+" ("+e.join(", ")+")",s.values=_.concat(s.values,t.value);break;case SqlOperationType.IS_NULL:case SqlOperationType.IS_NOT_NULL:r=t.operation;break;case SqlOperationType.LITERAL:a=t.key;const i=t.literalOperation||SqlOperationType.EQ;if(i==SqlOperationType.IN||i==SqlOperationType.NOT_IN){const e=new Array(t.value.length);e.fill("?"),r=i+" ("+e.join(", ")+")",s.values=_.concat(s.values,t.value)}else _.isUndefined(t.value)?r=i:(r=i+" ?",s.values.push(this.makeDbValue(t.value)));n=!1;break;default:r=t.operation+" ?",s.values.push(this.makeDbValue(t.value))}}else null===e?r=SqlOperationType.IS_NULL:void 0===e?(a="1",r="= 1",n=!1):(r=_.isString(e)&&e.match(/[%_]/)?SqlOperationType.LIKE+" ?":SqlOperationType.EQ+" ?",s.values.push(this.makeDbValue(e)));return i=n?this.translateFieldName(t,a)+" "+r:a+" "+r,i}})).join(" "+r+" ")),s}buildDeleteStatement(t,e){const r=this.getTableName(t),s=new SmartDbSqlBuildData("DELETE FROM");return s.sql+=" "+r,e&&s.append(this.buildWhere(t,e)),this.lastBuildData=s,s}buildUpdateStatement(t,e,r){const s=this.getTableName(t),a=new SmartDbSqlBuildData("UPDATE");a.sql+=" "+s+" SET ";const i=[],n=e instanceof AbstractModel?e.getPlainObject(FieldNamingStyle.Database):e;return _.forOwn(n,((e,r)=>{i.push(this.translateFieldName(t,r)+" = ?"),a.values.push(this.makeDbValue(e))})),a.sql+=i.join(", "),r&&a.append(this.buildWhere(t,r)),this.lastBuildData=a,a}buildInsertStatement(t,e){const r=this.getTableName(t),s=new SmartDbSqlBuildData("INSERT");s.sql+=" INTO "+r;const a=[];e instanceof AbstractModel&&(e=e.getPlainObject(FieldNamingStyle.Database)),_.forOwn(e,((e,r)=>{r=this.translateFieldName(t,r),a.push(r),s.values.push(this.makeDbValue(e))}));const i=new Array(a.length);return i.fill("?"),s.sql+=" ("+a.join(", ")+") VALUES ("+i.join(", ")+")",this.lastBuildData=s,s}prepareFieldValue(t,e){let r="<undefined>";switch(e.operation){case SqlFieldOperationType.FIELD:r=this.translateFieldName(t,e.value);break;case SqlFieldOperationType.VALUE:null===e.value?r="NULL":e.literal?r=e.value:_.isString(e.value)?!isNaN(parseFloat(e.value))&&isFinite(e.value)||(r="'"+e.value+"'"):_.isNumber(e.value)?r=e.value.toString():_.isBoolean(e.value)?r=e.value?"1":"0":_.isDate(e.value)?r="'"+e.value.toISOString().substring(0,23).replace("T"," ")+"'":console.error("unhandled field data type",typeof e.value,e.value);break;case SqlFieldOperationType.COUNT:r=_.isArray(e.value)?"COUNT("+e.value.join(",")+")":"COUNT("+(e.value||"")+")";break;case SqlFieldOperationType.COALESCE:const s=_.isArray(e.value)?e.value:[e.value];r="COALESCE("+this.buildFieldList(t,s).join(",")+")"}if(e.alias){const t=this.getDbQuote();r+=` as ${t}${e.alias}${t}`}return r}prepareField(t,e,r){let s;if(null===e)s="NULL";else if(_.isString(e)){const a=e.match(/^'(.*)'$/);a?r?(s="?",r.push(a[1])):s=e:s=this.translateFieldName(t,e)}else e.operation?s=this.prepareFieldValue(t,e):r?(s="?",r.push(e)):s=this.makeArgumentDbValue(e).toString();return s}buildFieldList(t,e){const r=[];return _.forEach(e,(e=>{r.push(this.prepareField(t,e))})),r}buildSelectSectionStatement(t,e){const r=this.getTableName(t);let s,a;if(_.isArray(e.fields))s=e.fields;else if(_.isString(e.fields)){const t=e.fields.trim();s=""===t||"*"==t?[]:t.split(/,/)}else s=e.fields&&e.fields.operation?[e.fields]:[];if(s.length>0){a=this.buildFieldList(t,s).join(", ")}else a="*";e.distinct&&(a="DISTINCT "+a),e.count&&(a=`COUNT(${a})`);const i=new SmartDbSqlBuildData(`SELECT ${a} FROM ${r}`);if(e.where&&i.append(this.buildWhere(t,e.where)),e.groupBy){i.sql+=" GROUP BY ";const r=_.isArray(e.groupBy)?e.groupBy:[e.groupBy];i.sql+=r.map((e=>this.translateFieldName(t,e))).join(", ")}return i}translateFieldName(t,e){if(_.isString(t)){const e=t;let r=!1;_.forEach(this.dictionaries,(s=>(s.models&&s.models[e]&&(t=s.models[e].cls,r=!0),!r)))}if(!_.isString(t)){const r=t.attributeMap[e];if(r)r.alias&&(e=r.alias);else{const r=t.getTableName();this.lastError=new Error(`unknown field '${e}' in table '${r}'`),this.smartDbLog.logError(SmartErrorLocation.Database,"translateFieldName",this.lastError)}}return e}makeDbValue(t){return _.isBoolean(t)?t=t?1:0:_.isDate(t)&&(t=toSmartDbDate(t)),t}makeArgumentDbValue(t){return _.isBoolean(t)?t=t?1:0:_.isDate(t)?t=`'${toSmartDbDate(t)}'`:_.isString(t)&&(t=`'${t.replace(/'/,"''").replace(/\\/,"\\\\")}'`),t}saveExecute(t){let e;try{e=t()}catch(t){this.lastError=t instanceof Error?t:new Error(t||"Unknown error"),this.smartDbLog.logFatal(SmartErrorLocation.Database,"saveExecute-catch",this.lastError),e=!1}return e}scriptParser(t){const e=fs.readFileSync(t,"utf8"),r=[];let s=0,a=0,i=!1,n=0,l=!1,o="";for(;a<e.length;){if(l){if("\n"==e[a])for(l=!1,s=a+1;" "==e[s]||"\n"==e[s]||"\n"==e[s];)s+=1}else"'"==e[a]?i=!i:!i&&e.substring(a,a+6).match(/^begin(\s|\n)/)?(n+=1,a+=6):n>0&&!i&&e.substring(a,a+4).match(/^end;/)?(n-=1,a+=4):0!==n||i||"/"!=e[a]?i||"-"!=e[a]||"-"!=e[a+1]?0!==n||i||";"!=e[a]||(r.push((o+e.substring(s,a)).trim()),o="",s=a+2):(o+=e.substring(s,a),l=!0):(r.push((o+e.substring(s,a)).trim()),o="",s=a+2);a+=1}if(s<e.length){const t=e.substring(s,e.length).trim().replace(/;$/,"");console.log(t),""!==t&&r.push(t)}return r}prepareResultRow(t,e,r){let s;if(r.indexedBy&&this.smartDbLog.logWarning(SmartErrorLocation.Database,"AbstractModel.get","option 'indexedBy' not supported without 'all'"),e)if(r.count)s=parseInt(_.values(e)[0],10);else if(r.collapseRow)s=_.values(e).join(",");else{const a=t.from(e);s=r.style==FieldNamingStyle.TypeScript?a.getPlainObject():a}return s}prepareResultRows(t,e,r){let s;if(!e||!r.indexedBy&&!r.collapseRow&&_.isString(t))s=e;else{const a=t;s=r.indexedBy?{}:new Array(e.length),_.forEach(e,((t,e)=>{let i=a.from?a.from(t):null,n=null;if(r.indexedBy&&(n=i?i.getValue(r.indexedBy):t[r.indexedBy]),r.collapseRow){const r=_.values(t).join(",");n?s[n]=r:s[e]=r}else i&&r.style==FieldNamingStyle.TypeScript&&(i=i.getPlainObject()),n?s[n]=i||t:s[e]=i||t}))}return s}getTableName(t){let e;if(_.isString(t)){let r=t;_.forEach(this.dictionaries,(t=>(t.models&&t.models[r]&&(e=t.models[r].cls.getTableName()),!e))),e||(e=r)}else{if(!t||!t.getTableName)throw new Error(`unknown model: ${t}`);e=t.getTableName()}return e}get isReady(){return this._isReady}set isReady(t){this._isReady=t,this._onReady.next(t)}get lastError(){return this._lastError}set lastError(t){this._lastError=new SmartError(t),this._lastError.location=SmartErrorLocation.Database}get onReady(){return this._onReady}get supportSyncCalls(){return!1}}