UNPKG

ts-db-helper

Version:
689 lines 27.6 kB
import { Count } from './queries/count'; import { ShadowValue } from './shadow-value.model'; import { Subject } from 'rxjs/Subject'; import { ClauseComparators } from './constants/clause-comparators.constant'; import { NotImplementedError } from '../errors/not-implemented.error'; import { QueryError } from '../errors/query.error'; import { RelationType } from './constants/relation-type.constant'; import { Select } from './queries/select'; import { RestoreDataError } from '../errors/restore-data.error'; import { Clause } from './queries/clause.model'; import { CompositeClause } from './queries/composite-clause.model'; import { ClauseGroup } from './queries/clause-group.model'; import { BadColumnDeclarationError } from '../errors/bad-column-declaration.error'; import { ModelManager } from '../managers/model-manager'; import { Insert } from './queries/insert'; import { Delete } from './queries/delete'; import { Update } from './queries/update'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/Observable/from'; /** * @public * @abstract * @class DbHelperModel * * @description * This abstract class is the base of models managed by the orm * it provides base method and fields to do the query magic * * @author Olivier Margarit * @since 0.1 */ var DbHelperModel = /** @class */ (function () { /** * @public * @constructor Create a model instance. Call to super() is mandatory. If super() is not called, magic will fail. * * @since 0.2 */ function DbHelperModel() { /** * @public * @property {number} $$rowid is the standard sqlite rowid, this property is used * to check if the model is already save and is setted on select * queries. */ this.$$rowid = null; /** * @public * @property {boolean} $$inserted is a library helper to manage update or insert operation */ this.$$inserted = false; /** * @public * @property {{[index: string]: ShadowValue}} $$shadow shadow model that map real values corresponding with database values * * @since 0.2 */ this.$$shadow = {}; /** * @public * @property {boolean} $$isModified flag updated on model change, in future version this value sould be observable * * @since 0.2 */ this.$$isModified = true; /** * @public * @property {boolean} $$isDbHelperModel flag to reflexive check if the model is a DbHelperModel * * @since 0.2 */ this.$$isDbHelperModel = true; var table = ModelManager.getInstance().getModel(this.constructor); for (var _i = 0, _a = table.columnList; _i < _a.length; _i++) { var column = _a[_i]; var shadow = new ShadowValue(); shadow.column = column; if (column.defaultValue !== undefined) { this.setFieldValue(column.field, column.defaultValue); } this.$$shadow[column.name] = shadow; } } DbHelperModel.prototype.update = function () { var _this = this; return Update(this).exec().map(function () { _this.$$isModified = false; return null; }); }; DbHelperModel.prototype.insert = function () { var _this = this; return Insert(this).exec().switchMap(function (res) { _this.$$inserted = true; if (!_this.$$rowid && !_this.hasValidPrimaryKey()) { return _this.restoreFromStorage(); } else { _this.$$isModified = false; return Observable.from([null]); } }); }; /** * @public * @method save the model method to save it in database * * @return {Observable<any>} observable to subscribe to save operation */ DbHelperModel.prototype.save = function () { var _this = this; if (this.$$partialWithProjection && !this.hasValidRowid() && !this.hasValidPrimaryKey()) { throw new RestoreDataError('Cannot save or restore data from projection without primary key or rowid,' + 'add rowid or primary kies to the projection or customize Update to do what you expect.'); } if (this.$$inserted) { return this.update(); } else { return this.checkIfShouldUpdateFromDatabase().switchMap(function (shouldUpdate) { if (shouldUpdate) { return _this.update(); } else { return _this.insert(); } }); } }; /** * @public * @method restoreFromStorage restore or reset the model data from database * * @return {Observable} observable to subscribe * * @since 0.2 */ DbHelperModel.prototype.restoreFromStorage = function () { var _this = this; var table = ModelManager.getInstance().getModel(this.constructor); var clauseGroup = new ClauseGroup(); if (this.hasValidRowid()) { clauseGroup.add({ rowid: this.$$rowid }); } else if (this.hasValidPrimaryKey()) { clauseGroup.add(this.getPrimaryClause); } else if (!this.$$partialWithProjection && (table.hasAutoIncrementedPrimaryKey() || table.hasNoPrimaryKey())) { clauseGroup.add(this.toClauseGroup()); } else { this.$$inserted = false; throw new RestoreDataError('Entity "' + this.constructor.name + '" cannot be restored from database. Detailled properties:\n' + JSON.stringify(this)); } return Select(this.constructor).where(clauseGroup).setSize(1) .exec().map(function (qr) { if (qr.rows.length) { var item = qr.rows.item(0); for (var _i = 0, _a = table.columnList; _i < _a.length; _i++) { var column = _a[_i]; _this.setFieldValue(column.field, item.getFieldValue(column.field)); } if (item.hasValidRowid()) { _this.$$rowid = item.$$rowid; } _this.$$isModified = false; } else { _this.$$inserted = false; throw new RestoreDataError('Entity "' + _this.constructor.name + '" cannot be restored from database. Detailled properties:\n' + JSON.stringify(_this)); } return null; }); }; /** * @public * @method hasValidRowid check if model has a valid rowid * * @return {boolean} true if $$rowid is usable * * @since 0.2 */ DbHelperModel.prototype.hasValidRowid = function () { return !!this.$$rowid || this.$$rowid === 0; }; /** * @public * @method toClauseGroup convert model to clause group * * @return {ClauseGroup} the result clause group */ DbHelperModel.prototype.toClauseGroup = function () { var group = new ClauseGroup(); for (var key in this.$$shadow) { if (this.$$shadow.hasOwnProperty(key)) { var shadow = this.$$shadow[key]; if (shadow.column.autoIncrement && !shadow.val && shadow.val !== 0) { continue; } var clause = new Clause(); clause.key = key; clause.value = shadow.val === undefined ? null : shadow.val; if (!shadow.val && shadow.foreign) { shadow.val = shadow.foreign.getColumnValue(shadow.column.foreignKey); } group.add(clause); } } if (this.hasValidRowid()) { group.add({ rowid: this.$$rowid }); } return group; }; /** * @public * @method getPrimaryClause get composite clause of primary key to query model * * @return {IClause} clause object that could be where clause to requery or update stored data */ DbHelperModel.prototype.getPrimaryClause = function () { var clause = new CompositeClause(); clause.comparator = ClauseComparators.IN; var values = []; for (var key in this.$$shadow) { if (this.$$shadow.hasOwnProperty(key)) { var shadow = this.$$shadow[key]; if (shadow.foreign) { shadow.val = shadow.foreign.getColumnValue(shadow.column.foreignKey); } if (shadow.column.primaryKey && (!shadow.column.autoIncrement || (shadow.val !== undefined && shadow.val !== null))) { clause.addKey(shadow.column.name); values.push(shadow.val === undefined ? null : shadow.val); } } } clause.addValue(values); return clause; }; /** * @public * @method hasValidPrimaryKey check if model has usable primary keys * * @return {boolean} return true if primary can be used */ DbHelperModel.prototype.hasValidPrimaryKey = function () { for (var key in this.$$shadow) { if (this.$$shadow.hasOwnProperty(key)) { var shadow = this.$$shadow[key]; if (shadow.column.foreignKey) { if (shadow.foreign) { shadow.val = shadow.foreign.getColumnValue(shadow.column.foreignKey); } } if (shadow.val === undefined || (shadow.column.autoIncrement && shadow.val === null)) { return false; } } } return true; }; /** * @public * @method getColumnValue get a column value by using the column name * * @param {string} columnName the column name * * @return {any} the column value stored in database */ DbHelperModel.prototype.getColumnValue = function (columnName) { if (!this.$$shadow.hasOwnProperty(columnName)) { throw new BadColumnDeclarationError('Value for column "' + columnName + '" can\'t be retrieve because this column is missing on model "' + this.$$dbTable.name + '"'); } var shadow = this.$$shadow[columnName]; if (shadow.column.foreignKey) { if (shadow.foreign) { shadow.val = shadow.foreign.getColumnValue(shadow.column.foreignKey); } } var value; value = shadow.val; return value; }; /** * @public * @method setColumnValue set the column value and bypass the field filter * * @param {string} columnName the column to update * @param {any} value the value to set * * @since 0.2 */ DbHelperModel.prototype.setColumnValue = function (columnName, value) { if (!this.$$shadow.hasOwnProperty(columnName)) { throw new BadColumnDeclarationError('Value for column "' + columnName + '" can\'t be retrieve because this column is missing on model "' + this.$$dbTable.name + '"'); } var shadow = this.$$shadow[columnName]; var oldVal = shadow.val; shadow.val = value; if (oldVal !== value && oldVal !== undefined) { this.$$isModified = true; } }; /** * @public * @method getFieldValue get the field value by its name * * @param {string} fieldName the field name from which retrieve the value * * @return {any} the field value */ DbHelperModel.prototype.getFieldValue = function (fieldName) { return this[fieldName]; }; /** * @public * @method setFieldValue set the field value by its name * * @param {string} fieldName the field to update * @param {any} value the value to set */ DbHelperModel.prototype.setFieldValue = function (fieldName, value) { this[fieldName] = value; }; DbHelperModel.prototype.getCommonFieldType = function (fieldName) { var type = ModelManager.getInstance().getModel(this).fields[fieldName].type; if (type.toLocaleLowerCase().indexOf('varchar') >= 0) { type = 'varchar'; } return type; }; /** * @public * @method delete delete the object from database * * @return {Observable<any>} observable to subscribe to delete operation */ DbHelperModel.prototype.delete = function () { return Delete(this).exec(); }; /** * @public * @method getLinkedModelClauses get linked model clauses * * @param {{new(): T}} model the linked model * @param {string} key the optional relation key * * @return {CompositeClause} the target model clause to retrieve it * * @since 0.2 */ DbHelperModel.prototype.getLinkedModelClauses = function (model, key) { var table = ModelManager.getInstance().getModel(this); var relation = table.getRelation(model, key); if (!relation) { throw new QueryError(this.constructor.name + ' has no link with ' + model.name, '', ''); } switch (relation.type) { case RelationType.ONE_TO_MANY: return this.getOneToManyClause(model, relation); case RelationType.ONE_TO_ONE: return this.getOneToOneClause(model, relation); case RelationType.MANY_TO_ONE: return this.getManyToOneClause(model, relation); case RelationType.MANY_TO_MANY: return this.getManyToManyClause(model); default: throw new QueryError('Invalide relation type', '', ''); } }; /** * @public * @method getLinked get linked model whatever is the relation type * * @param T @extends DbHelperModel generic model managed by this framework * @param {{new(): T}} model the generic model to retrieve * @param {string} key the relation key * * @throws {QueryError} generic error thrown if relation is invalid or query fail * * @return {Observable<QueryResult<>>} Observable to subscribe and manage linked result * * @since 0.2 */ DbHelperModel.prototype.getLinked = function (model, key) { return Select(model).where(this.getLinkedModelClauses(model, key)).exec(); }; /** * @private * @method getOneToManyClause get one to many clause for specific model * * @param T @extends DbHelperModel generic model managed by this framework * @param {{new(): T}} model Themodel clause wanted * @param {DbRelation} relation the model relation * * @return {CompositeClause} the clause to retrieve model * * @since 0.2 */ DbHelperModel.prototype.getOneToManyClause = function (model, relation) { var foreignKeysClause = new CompositeClause(); foreignKeysClause.comparator = ClauseComparators.IN; var values = []; for (var _i = 0, _a = relation.columns; _i < _a.length; _i++) { var column = _a[_i]; foreignKeysClause.addKey(column.name); values.push(this.getColumnValue(column.foreignKey)); } foreignKeysClause.addValue(values); return foreignKeysClause; }; /** * @private * @method getManyToOneClause get many to one clause for specific model * * @param T @extends DbHelperModel generic model managed by this framework * @param {{new(): T}} model Themodel clause wanted * @param {DbRelation} relation the model relation * * @return {CompositeClause} the clause to retrieve model * * @since 0.2 */ DbHelperModel.prototype.getManyToOneClause = function (model, relation) { var foreignKeysClause = new CompositeClause(); foreignKeysClause.comparator = ClauseComparators.IN; var values = []; for (var _i = 0, _a = relation.columns; _i < _a.length; _i++) { var column = _a[_i]; foreignKeysClause.addKey(column.foreignKey); values.push(this.getColumnValue(column.name)); } foreignKeysClause.addValue(values); return foreignKeysClause; }; /** * @private * @method getOneToOneClause get one to one clause for specific model * * @param T @extends DbHelperModel generic model managed by this framework * @param {{new(): T}} model Themodel clause wanted * @param {DbRelation} relation the model relation * * @return {CompositeClause} the clause to retrieve model * * @since 0.2 */ DbHelperModel.prototype.getOneToOneClause = function (model, relation) { var foreignKeysClause = new CompositeClause(); foreignKeysClause.comparator = ClauseComparators.IN; var values = []; if (relation.isReverse) { for (var _i = 0, _a = relation.columns; _i < _a.length; _i++) { var column = _a[_i]; foreignKeysClause.addKey(column.name); values.push(this.getColumnValue(column.foreignKey)); } } else { for (var _b = 0, _c = relation.columns; _b < _c.length; _b++) { var column = _c[_b]; foreignKeysClause.addKey(column.foreignKey); values.push(this.getColumnValue(column.name)); } } foreignKeysClause.addValue(values); return foreignKeysClause; }; /** * @private * @method getManyToManyClause get many to many clause for specific model * * @param T @extends DbHelperModel generic model managed by this framework * @param {{new(): T}} model Themodel clause wanted * @param {DbRelation} relation the model relation * * @return {CompositeClause} the clause to retrieve model * * @throws {NotImplementedError} this method is not implemented yet * * @since 0.2 */ DbHelperModel.prototype.getManyToManyClause = function (model) { throw new NotImplementedError('Many to many linked not implemented yet'); // return Select(model).exec(); }; DbHelperModel.prototype.checkIfShouldUpdateFromDatabase = function () { var _this = this; if (this.hasValidRowid()) { this.$$inserted = true; return Observable.from([true]); } else if (this.hasValidPrimaryKey()) { return Count(Select(this.constructor).where(this.getPrimaryClause())).exec().map(function (count) { _this.$$inserted = count === 1; return _this.$$inserted; }); } else { this.$$inserted = false; return Observable.from([false]); } }; /** * @public * @method linkModel link other model to this instance * * @param T @extends DbHelperModel generic model managed by this framework * @param {Array<T>} model The models to link * @param {string} key the relation key * * @throws {QueryError} generic error thrown if relation is invalid or query fail. * Throw error if model array is empty * * @return {Observable<QueryResult<T>>} the new linked models * * @since 0.2 */ DbHelperModel.prototype.linkModels = function (models, key) { if (!models.length) { throw new QueryError('can\'t link empty array on "' + this.constructor.name + '"', '', ''); } var modelClass = models[0].constructor; var table = ModelManager.getInstance().getModel(this); var relation = table.getRelation(modelClass, key); if (!relation) { throw new QueryError('"' + this.constructor.name + '" has no relation with "' + modelClass.name + '"' + (key ? ' and key "' + key + '"' : ''), '', ''); } if (models.length > 1 && relation.type === RelationType.ONE_TO_ONE) { throw new QueryError('"' + this.constructor.name + '" could be linked to many models for type one to one', '', ''); } if (relation.type === RelationType.MANY_TO_MANY) { return this.linkModelsManyToMany(models, key); } else if (relation.isReverse) { return this.linkModelsReverse(models, key); } else { return this.linkModelsStraight(models, key); } }; /** * @public * @method linkModelsManyToMany link many to many model to this instance * * @param T @extends DbHelperModel generic model managed by this framework * @param {Array<T>} model The models to link * @param {string} key the model relation * @param {boolean} remove flag to unlink model or link it * * @throws {NotImplementedError} This method is not implemented yet * * @return {Observable<QueryResult<T>>} the new linked models * * @since 0.2 */ DbHelperModel.prototype.linkModelsManyToMany = function (models, key, remove) { throw new NotImplementedError('Many to many linked not implemented yet'); }; /** * @public * @method linkModelsStraight link model in the staight relation to this instance * * @param T @extends DbHelperModel generic model managed by this framework * @param {Array<T>} model The models to link * @param {string} key the model relation * @param {boolean} remove flag to unlink model or link it * * @throws {QueryError} generic error thrown if relation is invalid or query fail. * Throw error if model array is empty * * @return {Observable<QueryResult<T>>} the new linked models * * @since 0.2 */ DbHelperModel.prototype.linkModelsStraight = function (models, key, remove) { var _this = this; if (models.length > 1) { throw new QueryError('"' + this.constructor.name + '" could be linked to many models for type one to one or many to one', '', ''); } var modelClass = this.constructor; var model = models[0]; var linkClause = model.getLinkedModelClauses(modelClass, key); var keysToUpdate = linkClause.keys; var valuesToSet = linkClause.values[0]; var set = {}; for (var i = 0; i < keysToUpdate.length; i++) { set[keysToUpdate[i]] = remove ? null : valuesToSet[i]; } var subject = new Subject(); Update(modelClass).set(set).where(this.getPrimaryClause()).exec().subscribe(function () { _this.getLinked(modelClass, key).subscribe(subject); }, function (err) { return subject.error(err); }); return subject; }; /** * @public * @method linkModelsReverse reverse link model relation to this instance * * @param T @extends DbHelperModel generic model managed by this framework * @param {Array<T>} model The models to link * @param {string} key the model relation * @param {boolean} remove flag to unlink model or link it * * @throws {QueryError} generic error thrown if relation is invalid or query fail. * Throw error if model array is empty * * @return {Observable<QueryResult<T>>} the new linked models * * @since 0.2 */ DbHelperModel.prototype.linkModelsReverse = function (models, key, remove) { var _this = this; var keys = null; var modelClass; var values = []; for (var _i = 0, models_1 = models; _i < models_1.length; _i++) { var model = models_1[_i]; var clause = model.getPrimaryClause(); if (!keys) { keys = clause.keys; modelClass = model.constructor; } values.push(clause.values[0]); } var pksClause = new CompositeClause(keys, values); var linkClause = this.getLinkedModelClauses(modelClass, key); var keysToUpdate = linkClause.keys; var valuesToSet = linkClause.values[0]; var set = {}; for (var i = 0; i < keysToUpdate.length; i++) { set[keysToUpdate[i]] = remove ? null : valuesToSet[i]; } var subject = new Subject(); Update(modelClass).set(set).where(pksClause).exec().subscribe(function () { _this.getLinked(modelClass, key).subscribe(subject); }, function (err) { return subject.error(err); }); return subject; }; /** * @public * @method unlinkModels unlink model relation to this instance * * @param T @extends DbHelperModel generic model managed by this framework * @param {Array<T>} model The models to unlink * @param {string} key the model relation * @param {boolean} remove flag to unlink model or link it * * @throws {QueryError} generic error thrown if relation is invalid or query fail. * Throw error if model array is empty * * @return {Observable<QueryResult<T>>} the new linked models * * @since 0.2 */ DbHelperModel.prototype.unlinkModels = function (models, key) { if (!models.length) { throw new QueryError('can\'t link empty array on "' + this.constructor.name + '"', '', ''); } var modelClass = models[0].constructor; var table = ModelManager.getInstance().getModel(this); var relation = table.getRelation(modelClass, key); if (!relation) { throw new QueryError('"' + this.constructor.name + '" has no relation with "' + modelClass.name + '"' + (key ? ' and key "' + key + '"' : ''), '', ''); } if (models.length > 1 && relation.type === RelationType.ONE_TO_ONE) { throw new QueryError('"' + this.constructor.name + '" could be linked to many models for type one to one', '', ''); } if (relation.type === RelationType.MANY_TO_MANY) { return this.linkModelsManyToMany(models, key, true); } else if (relation.isReverse) { return this.linkModelsReverse(models, key, true); } else { return this.linkModelsStraight(models, key, true); } }; return DbHelperModel; }()); export { DbHelperModel }; //# sourceMappingURL=db-helper-model.model.js.map