ts-db-helper
Version:
Simple ORM based on TypeScript
689 lines • 27.6 kB
JavaScript
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