objection
Advanced tools
Comparing version 0.4.0-rc.2 to 0.4.0-rc.3
'use strict'; | ||
var _class, _temp; | ||
var _desc, _value, _class, _class2, _temp; | ||
@@ -10,2 +10,6 @@ Object.defineProperty(exports, "__esModule", { | ||
var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); | ||
var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
@@ -47,6 +51,2 @@ | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _ModelBase2 = require('./ModelBase'); | ||
@@ -76,2 +76,4 @@ | ||
var _decorators = require('../utils/decorators'); | ||
var _Relation = require('../relations/Relation'); | ||
@@ -95,2 +97,31 @@ | ||
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { | ||
var desc = {}; | ||
Object['ke' + 'ys'](descriptor).forEach(function (key) { | ||
desc[key] = descriptor[key]; | ||
}); | ||
desc.enumerable = !!desc.enumerable; | ||
desc.configurable = !!desc.configurable; | ||
if ('value' in desc || desc.initializer) { | ||
desc.writable = true; | ||
} | ||
desc = decorators.slice().reverse().reduce(function (desc, decorator) { | ||
return decorator(target, property, desc) || desc; | ||
}, desc); | ||
if (context && desc.initializer !== void 0) { | ||
desc.value = desc.initializer ? desc.initializer.call(context) : void 0; | ||
desc.initializer = undefined; | ||
} | ||
if (desc.initializer === void 0) { | ||
Object['define' + 'Property'](target, property, desc); | ||
desc = null; | ||
} | ||
return desc; | ||
} | ||
/** | ||
@@ -181,3 +212,3 @@ * Subclasses of this class represent database tables. | ||
*/ | ||
var Model = (_temp = _class = (function (_ModelBase) { | ||
var Model = (_class = (_temp = _class2 = (function (_ModelBase) { | ||
(0, _inherits3.default)(Model, _ModelBase); | ||
@@ -266,5 +297,7 @@ | ||
* | ||
* Composite id can be specified by giving an array of column names. | ||
* | ||
* Defaults to 'id'. | ||
* | ||
* @type {string} | ||
* @type {string|Array.<string>} | ||
*/ | ||
@@ -291,8 +324,6 @@ | ||
value: function $id() { | ||
var ModelClass = this.constructor; | ||
if (arguments.length > 0) { | ||
this[ModelClass.getIdProperty()] = arguments[0]; | ||
return setId(this, arguments[0]); | ||
} else { | ||
return this[ModelClass.getIdProperty()]; | ||
return getId(this); | ||
} | ||
@@ -449,3 +480,3 @@ } | ||
builder.onBuild(function (builder) { | ||
builder.where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
builder.whereComposite(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
@@ -463,3 +494,3 @@ }).insertImpl(function (insertion, builder) { | ||
builder.onBuild(function (builder) { | ||
builder.$$update(update).where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
builder.$$update(update).whereComposite(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
@@ -472,7 +503,7 @@ }).patchImpl(function (patch, builder) { | ||
builder.onBuild(function (builder) { | ||
builder.$$update(patch).where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
builder.$$update(patch).whereComposite(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
}).deleteImpl(function (builder) { | ||
builder.onBuild(function (builder) { | ||
builder.$$delete().where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
builder.$$delete().whereComposite(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
@@ -1177,3 +1208,3 @@ }).relateImpl(function () { | ||
* @param {knex} knex | ||
* @returns {Model} | ||
* @returns {Constructor.<Model>} | ||
*/ | ||
@@ -1238,3 +1269,3 @@ | ||
* @param trx | ||
* @returns {Model} | ||
* @returns {Constructor.<Model>} | ||
*/ | ||
@@ -1309,2 +1340,17 @@ | ||
/** | ||
* @ignore | ||
* @returns {number} | ||
*/ | ||
}, { | ||
key: 'getIdColumnDimension', | ||
value: function getIdColumnDimension() { | ||
if (_lodash2.default.isArray(this.idColumn)) { | ||
return this.idColumn.length; | ||
} else { | ||
return 1; | ||
} | ||
} | ||
/** | ||
* Returns the name of the identifier property. | ||
@@ -1316,3 +1362,3 @@ * | ||
* | ||
* @returns {string} | ||
* @returns {string|Array.<string>} | ||
*/ | ||
@@ -1323,9 +1369,11 @@ | ||
value: function getIdProperty() { | ||
var idProperty = this.columnNameToPropertyName(this.idColumn); | ||
var ModelClass = this; | ||
if (!idProperty) { | ||
throw new Error(this.name + '.$parseDatabaseJson probably changes the value of the id column `' + this.idColumn + '` which is a no-no.'); | ||
if (_lodash2.default.isArray(ModelClass.idColumn)) { | ||
return _lodash2.default.map(ModelClass.idColumn, function (col) { | ||
return idColumnToIdProperty(ModelClass, col); | ||
}); | ||
} else { | ||
return idColumnToIdProperty(ModelClass, ModelClass.idColumn); | ||
} | ||
return idProperty; | ||
} | ||
@@ -1336,3 +1384,3 @@ | ||
* | ||
* @returns {string} | ||
* @returns {string|Array.<string>} | ||
*/ | ||
@@ -1343,3 +1391,11 @@ | ||
value: function getFullIdColumn() { | ||
return this.tableName + '.' + this.idColumn; | ||
var _this4 = this; | ||
if (_lodash2.default.isArray(this.idColumn)) { | ||
return _lodash2.default.map(this.idColumn, function (col) { | ||
return _this4.tableName + '.' + col; | ||
}); | ||
} else { | ||
return this.tableName + '.' + this.idColumn; | ||
} | ||
} | ||
@@ -1511,3 +1567,3 @@ | ||
value: function $$getJsonAttributes() { | ||
var _this4 = this; | ||
var _this5 = this; | ||
@@ -1532,3 +1588,3 @@ // If the jsonAttributes property is not set, try to create it based | ||
if (_lodash2.default.contains(types, 'object') || _lodash2.default.contains(types, 'array')) { | ||
_this4.jsonAttributes.push(propName); | ||
_this5.jsonAttributes.push(propName); | ||
} | ||
@@ -1546,3 +1602,3 @@ }); | ||
return Model; | ||
})(_ModelBase3.default), _class.QueryBuilder = _QueryBuilder2.default, _class.RelatedQueryBuilder = _QueryBuilder2.default, _class.OneToOneRelation = _OneToOneRelation2.default, _class.OneToManyRelation = _OneToManyRelation2.default, _class.ManyToManyRelation = _ManyToManyRelation2.default, _class.tableName = null, _class.idColumn = 'id', _class.uidProp = '#id', _class.uidRefProp = '#ref', _class.propRefRegex = /#ref{([^\.]+)\.([^}]+)}/g, _class.jsonAttributes = null, _class.relationMappings = null, _class.$$knex = null, _class.$$relations = null, _temp); | ||
})(_ModelBase3.default), _class2.QueryBuilder = _QueryBuilder2.default, _class2.RelatedQueryBuilder = _QueryBuilder2.default, _class2.OneToOneRelation = _OneToOneRelation2.default, _class2.OneToManyRelation = _OneToManyRelation2.default, _class2.ManyToManyRelation = _ManyToManyRelation2.default, _class2.tableName = null, _class2.idColumn = 'id', _class2.uidProp = '#id', _class2.uidRefProp = '#ref', _class2.propRefRegex = /#ref{([^\.]+)\.([^}]+)}/g, _class2.jsonAttributes = null, _class2.relationMappings = null, _class2.$$knex = null, _class2.$$relations = null, _temp), (_applyDecoratedDescriptor(_class, 'getIdProperty', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class, 'getIdProperty'), _class), _applyDecoratedDescriptor(_class, 'getFullIdColumn', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class, 'getFullIdColumn'), _class)), _class); | ||
@@ -1596,2 +1652,64 @@ /** | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function idColumnToIdProperty(ModelClass, idColumn) { | ||
var idProperty = ModelClass.columnNameToPropertyName(idColumn); | ||
if (!idProperty) { | ||
throw new Error(ModelClass.tableName + '.$parseDatabaseJson probably changes the value of the id column `' + idColumn + '` which is a no-no.'); | ||
} | ||
return idProperty; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function setId(model, id) { | ||
var idProp = model.constructor.getIdProperty(); | ||
var isArray = _lodash2.default.isArray(idProp); | ||
if (_lodash2.default.isArray(id)) { | ||
if (isArray) { | ||
if (id.length !== idProp.length) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
for (var i = 0; i < id.length; ++i) { | ||
model[idProp[i]] = id[i]; | ||
} | ||
} else { | ||
if (id.length !== 1) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
model[idProp] = id[0]; | ||
} | ||
} else { | ||
if (isArray) { | ||
if (idProp.length > 1) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
model[idProp[0]] = id; | ||
} else { | ||
model[idProp] = id; | ||
} | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function getId(model) { | ||
var idProp = model.constructor.getIdProperty(); | ||
if (_lodash2.default.isArray(idProp)) { | ||
return model.$values(idProp); | ||
} else { | ||
return model[idProp]; | ||
} | ||
} |
'use strict'; | ||
var _class, _temp; | ||
var _desc, _value, _class, _class2, _temp; | ||
@@ -10,5 +10,5 @@ Object.defineProperty(exports, "__esModule", { | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); | ||
@@ -35,6 +35,2 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _ValidationError = require('../ValidationError'); | ||
@@ -44,4 +40,37 @@ | ||
var _classUtils = require('../utils/classUtils'); | ||
var _decorators = require('../utils/decorators'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { | ||
var desc = {}; | ||
Object['ke' + 'ys'](descriptor).forEach(function (key) { | ||
desc[key] = descriptor[key]; | ||
}); | ||
desc.enumerable = !!desc.enumerable; | ||
desc.configurable = !!desc.configurable; | ||
if ('value' in desc || desc.initializer) { | ||
desc.writable = true; | ||
} | ||
desc = decorators.slice().reverse().reduce(function (desc, decorator) { | ||
return decorator(target, property, desc) || desc; | ||
}, desc); | ||
if (context && desc.initializer !== void 0) { | ||
desc.value = desc.initializer ? desc.initializer.call(context) : void 0; | ||
desc.initializer = undefined; | ||
} | ||
if (desc.initializer === void 0) { | ||
Object['define' + 'Property'](target, property, desc); | ||
desc = null; | ||
} | ||
return desc; | ||
} | ||
/** | ||
@@ -137,3 +166,3 @@ * @typedef {Object} ModelOptions | ||
*/ | ||
var ModelBase = (_temp = _class = (function () { | ||
var ModelBase = (_class = (_temp = _class2 = (function () { | ||
function ModelBase() { | ||
@@ -163,6 +192,2 @@ (0, _classCallCheck3.default)(this, ModelBase); | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
value: function $beforeValidate(jsonSchema, json, options) { | ||
@@ -194,6 +219,2 @@ /* istanbul ignore next */ | ||
/** | ||
* @private | ||
*/ | ||
/** | ||
* The optional schema against which the JSON is validated. | ||
@@ -667,3 +688,3 @@ * | ||
_utils2.default.inherits(subclassConstructor, this); | ||
(0, _classUtils.inherits)(subclassConstructor, this); | ||
return subclassConstructor; | ||
@@ -740,9 +761,12 @@ } | ||
value: function columnNameToPropertyName(columnName) { | ||
this.$$ensurePropNameConversionCache(); | ||
var model = new this(); | ||
var addedProps = _lodash2.default.keys(model.$parseDatabaseJson({})); | ||
if (!this.$$colToProp[columnName]) { | ||
this.$$cachePropNameConversion(_columnNameToPropertyName(this, columnName), columnName); | ||
} | ||
var row = {}; | ||
row[columnName] = null; | ||
return this.$$colToProp[columnName]; | ||
var props = _lodash2.default.keys(_lodash2.default.omit(model.$parseDatabaseJson(row), addedProps)); | ||
var propertyName = _lodash2.default.first(props); | ||
return propertyName || null; | ||
} | ||
@@ -759,40 +783,16 @@ | ||
value: function propertyNameToColumnName(propertyName) { | ||
this.$$ensurePropNameConversionCache(); | ||
var model = new this(); | ||
var addedCols = _lodash2.default.keys(model.$formatDatabaseJson({})); | ||
if (!this.$$propToCol[propertyName]) { | ||
this.$$cachePropNameConversion(propertyName, _propertyNameToColumnName(this, propertyName)); | ||
} | ||
var obj = {}; | ||
obj[propertyName] = null; | ||
return this.$$propToCol[propertyName]; | ||
} | ||
var cols = _lodash2.default.keys(_lodash2.default.omit(model.$formatDatabaseJson(obj), addedCols)); | ||
var columnName = _lodash2.default.first(cols); | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '$$ensurePropNameConversionCache', | ||
value: function $$ensurePropNameConversionCache() { | ||
if (!this.$$propToCol) { | ||
this.$$propToCol = (0, _create2.default)(null); | ||
} | ||
if (!this.$$colToProp) { | ||
this.$$colToProp = (0, _create2.default)(null); | ||
} | ||
return columnName || null; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '$$cachePropNameConversion', | ||
value: function $$cachePropNameConversion(propertyName, columnName) { | ||
this.$$propToCol[propertyName] = columnName; | ||
this.$$colToProp[columnName] = propertyName; | ||
} | ||
}]); | ||
return ModelBase; | ||
})(), _class.jsonSchema = null, _class.$$colToProp = null, _class.$$propToCol = null, _temp); | ||
})(), _class2.jsonSchema = null, _temp), (_applyDecoratedDescriptor(_class, 'columnNameToPropertyName', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class, 'columnNameToPropertyName'), _class), _applyDecoratedDescriptor(_class, 'propertyNameToColumnName', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class, 'propertyNameToColumnName'), _class)), _class); | ||
@@ -1019,36 +1019,4 @@ /** | ||
/** | ||
* @private | ||
*/ | ||
function _propertyNameToColumnName(ModelClass, propertyName) { | ||
var model = new ModelClass(); | ||
var addedCols = _lodash2.default.keys(model.$formatDatabaseJson({})); | ||
var obj = {}; | ||
obj[propertyName] = null; | ||
var cols = _lodash2.default.keys(_lodash2.default.omit(model.$formatDatabaseJson(obj), addedCols)); | ||
var columnName = _lodash2.default.first(cols); | ||
return columnName || null; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function _columnNameToPropertyName(ModelClass, columnName) { | ||
var model = new ModelClass(); | ||
var addedProps = _lodash2.default.keys(model.$parseDatabaseJson({})); | ||
var row = {}; | ||
row[columnName] = null; | ||
var props = _lodash2.default.keys(_lodash2.default.omit(model.$parseDatabaseJson(row), addedProps)); | ||
var propertyName = _lodash2.default.first(props); | ||
return propertyName || null; | ||
} | ||
// Add validation formats, so that for example the following schema validation works: | ||
// createTime: {type: 'string', format: 'date-time'} | ||
_tv2.default.addFormat(_tv4Formats2.default); |
@@ -226,3 +226,2 @@ 'use strict'; | ||
var batch = (0, _create2.default)(null); | ||
var notUnique = (0, _create2.default)(null); | ||
@@ -236,27 +235,27 @@ for (var n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
var sourceVal = node.model[conn.relation.ownerProp]; | ||
var targetVal = conn.node.model[conn.relation.relatedProp]; | ||
var sourceVal = node.model.$values(conn.relation.ownerProp); | ||
var targetVal = conn.node.model.$values(conn.relation.relatedProp); | ||
var uniqueKey = undefined; | ||
var joinModel = {}; | ||
var knex = conn.relation.ownerModelClass.knex(); | ||
var modelClass = conn.relation.joinTableModelClass; | ||
if (conn.relation.joinTableOwnerCol < conn.relation.joinTableRelatedCol) { | ||
uniqueKey = conn.relation.joinTable + '_' + sourceVal + '_' + targetVal; | ||
} else { | ||
uniqueKey = conn.relation.joinTable + '_' + targetVal + '_' + sourceVal; | ||
if (knex) { | ||
// TODO: Because the joinTableModelClass may have been created inside ManyToManyRelation, it may not be bound. We really should not have to know about it here... | ||
modelClass = modelClass.bindKnex(knex); | ||
} | ||
if (notUnique[uniqueKey]) { | ||
continue; | ||
for (var i = 0; i < sourceVal.length; ++i) { | ||
joinModel[conn.relation.joinTableOwnerProp[i]] = sourceVal[i]; | ||
} | ||
notUnique[uniqueKey] = true; | ||
for (var i = 0; i < targetVal.length; ++i) { | ||
joinModel[conn.relation.joinTableRelatedProp[i]] = targetVal[i]; | ||
} | ||
var joinModel = {}; | ||
joinModel[conn.relation.joinTableOwnerProp] = sourceVal; | ||
joinModel[conn.relation.joinTableRelatedProp] = targetVal; | ||
joinModel = conn.relation.joinTableModelClass.fromJson(joinModel); | ||
joinModel = modelClass.fromJson(joinModel); | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(conn.relation.joinTableModelClass, true); | ||
setTableInsertion(batch, conn.relation.joinTable, tableInsertion); | ||
tableInsertion = new TableInsertion(modelClass, true); | ||
setTableInsertion(batch, modelClass.tableName, tableInsertion); | ||
} | ||
@@ -269,2 +268,15 @@ | ||
// Remove duplicates. | ||
_lodash2.default.each(batch, function (tableInsertion) { | ||
if (tableInsertion.models.length) { | ||
(function () { | ||
var keys = _lodash2.default.keys(tableInsertion.models[0]); | ||
tableInsertion.models = _lodash2.default.unique(tableInsertion.models, function (model) { | ||
return model.$values(keys).join(); | ||
}); | ||
tableInsertion.isInputModel = _lodash2.default.times(tableInsertion.models.length, _lodash2.default.constant(false)); | ||
})(); | ||
} | ||
}); | ||
return batch; | ||
@@ -437,7 +449,11 @@ } | ||
node.needs.push(new Dependency(parentNode, function (model) { | ||
model[rel.relatedProp] = this.node.model[rel.ownerProp]; | ||
for (var i = 0; i < rel.relatedProp.length; ++i) { | ||
model[rel.relatedProp[i]] = this.node.model[rel.ownerProp[i]]; | ||
} | ||
})); | ||
parentNode.isNeededBy.push(new Dependency(node, function (model) { | ||
this.node.model[rel.relatedProp] = model[rel.ownerProp]; | ||
for (var i = 0; i < rel.relatedProp.length; ++i) { | ||
this.node.model[rel.relatedProp[i]] = model[rel.ownerProp[i]]; | ||
} | ||
})); | ||
@@ -447,7 +463,11 @@ } else if (rel instanceof _OneToOneRelation2.default) { | ||
node.isNeededBy.push(new Dependency(parentNode, function (model) { | ||
this.node.model[rel.ownerProp] = model[rel.relatedProp]; | ||
for (var i = 0; i < rel.relatedProp.length; ++i) { | ||
this.node.model[rel.ownerProp[i]] = model[rel.relatedProp[i]]; | ||
} | ||
})); | ||
parentNode.needs.push(new Dependency(node, function (model) { | ||
model[rel.ownerProp] = this.node.model[rel.relatedProp]; | ||
for (var i = 0; i < rel.relatedProp.length; ++i) { | ||
model[rel.ownerProp[i]] = this.node.model[rel.relatedProp[i]]; | ||
} | ||
})); | ||
@@ -454,0 +474,0 @@ } else if (rel instanceof _ManyToManyRelation2.default) { |
'use strict'; | ||
var _dec, _dec2, _dec3, _class, _desc, _value, _class2; | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -8,2 +10,6 @@ value: true | ||
var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); | ||
var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
@@ -37,6 +43,2 @@ | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _Relation2 = require('./Relation'); | ||
@@ -50,6 +52,42 @@ | ||
var _dbUtils = require('../utils/dbUtils'); | ||
var _classUtils = require('../utils/classUtils'); | ||
var _decorators = require('../utils/decorators'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var ownerJoinColumnAlias = 'objectiontmpjoin';; | ||
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { | ||
var desc = {}; | ||
Object['ke' + 'ys'](descriptor).forEach(function (key) { | ||
desc[key] = descriptor[key]; | ||
}); | ||
desc.enumerable = !!desc.enumerable; | ||
desc.configurable = !!desc.configurable; | ||
if ('value' in desc || desc.initializer) { | ||
desc.writable = true; | ||
} | ||
desc = decorators.slice().reverse().reduce(function (desc, decorator) { | ||
return decorator(target, property, desc) || desc; | ||
}, desc); | ||
if (context && desc.initializer !== void 0) { | ||
desc.value = desc.initializer ? desc.initializer.call(context) : void 0; | ||
desc.initializer = undefined; | ||
} | ||
if (desc.initializer === void 0) { | ||
Object['define' + 'Property'](target, property, desc); | ||
desc = null; | ||
} | ||
return desc; | ||
} | ||
var ownerJoinColumnAliasPrefix = 'objectiontmpjoin'; | ||
var sqliteBuiltInRowId = '_rowid_'; | ||
/** | ||
@@ -59,4 +97,7 @@ * @ignore | ||
*/ | ||
var ManyToManyRelation = (function (_Relation) { | ||
var ManyToManyRelation = (_dec = (0, _dbUtils.overwriteForDatabase)(), _dec2 = (0, _dbUtils.overwriteForDatabase)({ | ||
sqlite3: 'unrelate_sqlite3' | ||
}), _dec3 = (0, _dbUtils.overwriteForDatabase)({ | ||
sqlite3: '_selectForModify_sqlite3' | ||
}), _dec(_class = (_class2 = (function (_Relation) { | ||
(0, _inherits3.default)(ManyToManyRelation, _Relation); | ||
@@ -86,3 +127,3 @@ | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -94,3 +135,3 @@ _this.joinTableOwnerCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -102,3 +143,3 @@ _this.joinTableOwnerProp = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -110,3 +151,3 @@ _this.joinTableRelatedCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -135,30 +176,29 @@ _this.joinTableRelatedProp = null; | ||
value: function setMapping(mapping) { | ||
var retVal = (0, _get3.default)((0, _getPrototypeOf2.default)(ManyToManyRelation.prototype), 'setMapping', this).call(this, mapping); | ||
// Avoid require loop and import here. | ||
var Model = require(__dirname + '/../model/Model').default; | ||
var retVal = _Relation3.default.prototype.setMapping.call(this, mapping); | ||
var errorPrefix = this.ownerModelClass.name + '.relationMappings.' + this.name; | ||
if (!_lodash2.default.isObject(mapping.join.through)) { | ||
throw new Error(errorPrefix + '.join must have the `through` that describes the join table.'); | ||
this.throwError('join must have the `through` that describes the join table.'); | ||
} | ||
if (!_lodash2.default.isString(mapping.join.through.from) || !_lodash2.default.isString(mapping.join.through.to)) { | ||
throw new Error(errorPrefix + '.join.through must be an object that describes the join table. For example: {from: \'JoinTable.someId\', to: \'JoinTable.someOtherId\'}'); | ||
if (!mapping.join.through.from || !mapping.join.through.to) { | ||
this.throwError('join.through must be an object that describes the join table. For example: {from: "JoinTable.someId", to: "JoinTable.someOtherId"}'); | ||
} | ||
var joinFrom = _Relation3.default.parseColumn(mapping.join.from); | ||
var joinTableFrom = _Relation3.default.parseColumn(mapping.join.through.from); | ||
var joinTableTo = _Relation3.default.parseColumn(mapping.join.through.to); | ||
var joinFrom = this.parseReference(mapping.join.from); | ||
var joinTableFrom = this.parseReference(mapping.join.through.from); | ||
var joinTableTo = this.parseReference(mapping.join.through.to); | ||
if (!joinTableFrom.table || !joinTableFrom.name) { | ||
throw new Error(errorPrefix + '.join.through.from must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
if (!joinTableFrom.table || _lodash2.default.isEmpty(joinTableFrom.columns)) { | ||
this.throwError('join.through.from must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'); | ||
} | ||
if (!joinTableTo.table || !joinTableTo.name) { | ||
throw new Error(errorPrefix + '.join.through.to must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
if (!joinTableTo.table || _lodash2.default.isEmpty(joinTableTo.columns)) { | ||
this.throwError('join.through.to must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'); | ||
} | ||
if (joinTableFrom.table !== joinTableTo.table) { | ||
throw new Error(errorPrefix + '.join.through `from` and `to` must point to the same join table.'); | ||
this.throwError('join.through `from` and `to` must point to the same join table.'); | ||
} | ||
@@ -169,12 +209,12 @@ | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
this.joinTableOwnerCol = joinTableFrom.name; | ||
this.joinTableRelatedCol = joinTableTo.name; | ||
this.joinTableOwnerCol = joinTableFrom.columns; | ||
this.joinTableRelatedCol = joinTableTo.columns; | ||
} else { | ||
this.joinTableRelatedCol = joinTableFrom.name; | ||
this.joinTableOwnerCol = joinTableTo.name; | ||
this.joinTableRelatedCol = joinTableFrom.columns; | ||
this.joinTableOwnerCol = joinTableTo.columns; | ||
} | ||
if (mapping.join.through.modelClass) { | ||
if (!_utils2.default.isSubclassOf(mapping.join.through.modelClass, Model)) { | ||
throw new Error('Join table model class is not a subclass of Model'); | ||
if (!(0, _classUtils.isSubclassOf)(mapping.join.through.modelClass, Model)) { | ||
this.throwError('Join table model class is not a subclass of Model'); | ||
} | ||
@@ -191,4 +231,4 @@ | ||
this.joinTableOwnerProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableOwnerCol); | ||
this.joinTableRelatedProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableRelatedCol); | ||
this.joinTableOwnerProp = this.propertyName(this.joinTableOwnerCol, this.joinTableModelClass); | ||
this.joinTableRelatedProp = this.propertyName(this.joinTableRelatedCol, this.joinTableModelClass); | ||
@@ -201,5 +241,5 @@ return retVal; | ||
* | ||
* For example: `Person_Movie.actorId`. | ||
* For example: [`Person_Movie.actorId`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@@ -210,3 +250,7 @@ | ||
value: function fullJoinTableOwnerCol() { | ||
return this.joinTable + '.' + this.joinTableOwnerCol; | ||
var _this2 = this; | ||
return _lodash2.default.map(this.joinTableOwnerCol, function (col) { | ||
return _this2.joinTable + '.' + col; | ||
}); | ||
} | ||
@@ -217,5 +261,5 @@ | ||
* | ||
* For example: `Person_Movie.movieId`. | ||
* For example: [`Person_Movie.movieId`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@@ -226,3 +270,7 @@ | ||
value: function fullJoinTableRelatedCol() { | ||
return this.joinTable + '.' + this.joinTableRelatedCol; | ||
var _this3 = this; | ||
return _lodash2.default.map(this.joinTableRelatedCol, function (col) { | ||
return _this3.joinTable + '.' + col; | ||
}); | ||
} | ||
@@ -273,5 +321,3 @@ | ||
var bound = (0, _get3.default)((0, _getPrototypeOf2.default)(ManyToManyRelation.prototype), 'bindKnex', this).call(this, knex); | ||
bound.joinTableModelClass = this.joinTableModelClass.bindKnex(knex); | ||
return bound; | ||
@@ -288,12 +334,25 @@ } | ||
key: 'findQuery', | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
builder.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()); | ||
value: function findQuery(builder, ownerIds, isColumnRef) { | ||
var _this4 = this; | ||
var fullRelatedCol = this.fullRelatedCol(); | ||
builder.join(this.joinTable, function (join) { | ||
_lodash2.default.each(_this4.fullJoinTableRelatedCol(), function (joinTableRelatedCol, idx) { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullJoinTableOwnerCol(), ownerCol); | ||
_lodash2.default.each(this.fullJoinTableOwnerCol(), function (joinTableOwnerCol, idx) { | ||
builder.whereRef(joinTableOwnerCol, ownerIds[idx]); | ||
}); | ||
} else { | ||
if (_lodash2.default.isArray(ownerCol)) { | ||
builder.whereIn(this.fullJoinTableOwnerCol(), ownerCol); | ||
if ((0, _lodash2.default)(ownerIds).flatten().all(function (id) { | ||
return _lodash2.default.isNull(id) || _lodash2.default.isUndefined(id); | ||
})) { | ||
// Nothing to fetch. | ||
builder.resolve([]); | ||
} else { | ||
builder.where(this.fullJoinTableOwnerCol(), ownerCol); | ||
builder.whereInComposite(this.fullJoinTableOwnerCol(), ownerIds); | ||
} | ||
@@ -325,9 +384,23 @@ } | ||
var joinTableOwnerCol = joinTableAlias + '.' + this.joinTableOwnerCol; | ||
var joinTableRelatedCol = joinTableAlias + '.' + this.joinTableRelatedCol; | ||
var joinTableOwnerCol = _lodash2.default.map(this.joinTableOwnerCol, function (col) { | ||
return joinTableAlias + '.' + col; | ||
}); | ||
var joinTableRelatedCol = _lodash2.default.map(this.joinTableRelatedCol, function (col) { | ||
return joinTableAlias + '.' + col; | ||
}); | ||
var ownerCol = this.fullOwnerCol(); | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
var relatedCol = _lodash2.default.map(this.relatedCol, function (col) { | ||
return relatedTableAlias + '.' + col; | ||
}); | ||
return builder[joinMethod](joinTableAsAlias, joinTableOwnerCol, ownerCol)[joinMethod](relatedTableAsAlias, joinTableRelatedCol, relatedCol).call(this.filter); | ||
return builder[joinMethod](joinTableAsAlias, function (join) { | ||
_lodash2.default.each(joinTableOwnerCol, function (joinTableOwnerCol, idx) { | ||
join.on(joinTableOwnerCol, ownerCol[idx]); | ||
}); | ||
})[joinMethod](relatedTableAsAlias, function (join) { | ||
_lodash2.default.each(joinTableRelatedCol, function (joinTableRelatedCol, idx) { | ||
join.on(joinTableRelatedCol, relatedCol[idx]); | ||
}); | ||
}).call(this.filter); | ||
} | ||
@@ -343,7 +416,17 @@ | ||
value: function find(builder, owners) { | ||
var _this2 = this; | ||
var _this5 = this; | ||
var ownerJoinColumnAlias = _lodash2.default.times(this.joinTableOwnerCol.length, function (idx) { | ||
return ownerJoinColumnAliasPrefix + idx; | ||
}); | ||
var ownerJoinPropertyAlias = _lodash2.default.map(ownerJoinColumnAlias, function (alias) { | ||
return _this5.relatedModelClass.columnNameToPropertyName(alias); | ||
}); | ||
builder.onBuild(function (builder) { | ||
var ownerIds = _lodash2.default.pluck(owners, _this2.ownerProp); | ||
var ownerJoinColumn = _this2.fullJoinTableOwnerCol(); | ||
var ids = (0, _lodash2.default)(owners).map(function (owner) { | ||
return owner.$values(_this5.ownerProp); | ||
}).unique(function (id) { | ||
return id.join(); | ||
}).value(); | ||
@@ -353,20 +436,28 @@ if (!builder.has(/select/)) { | ||
// If we don't do this we also get the join table's columns. | ||
builder.select(_this2.relatedModelClass.tableName + '.*'); | ||
builder.select(_this5.relatedModelClass.tableName + '.*'); | ||
} | ||
_this2.findQuery(builder, ownerIds).select(ownerJoinColumn + ' as ' + ownerJoinColumnAlias); | ||
_this5.findQuery(builder, ids); | ||
// We must select the owner join columns so that we know for which owner model the related | ||
// models belong to after the requests. | ||
_lodash2.default.each(_this5.fullJoinTableOwnerCol(), function (fullJoinTableOwnerCol, idx) { | ||
builder.select(fullJoinTableOwnerCol + ' as ' + ownerJoinColumnAlias[idx]); | ||
}); | ||
}); | ||
builder.runAfterModelCreate(function (related) { | ||
// The ownerJoinColumnAlias column name may have been changed by the `$parseDatabaseJson` | ||
// method of the related model class. We need to do the same conversion here. | ||
var ownerJoinPropAlias = _this2.relatedModelClass.columnNameToPropertyName(ownerJoinColumnAlias); | ||
var relatedByOwnerId = _lodash2.default.groupBy(related, ownerJoinPropAlias); | ||
var relatedByOwnerId = _lodash2.default.groupBy(related, function (related) { | ||
return related.$values(ownerJoinPropertyAlias); | ||
}); | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedByOwnerId[owner[_this2.ownerProp]] || []; | ||
owner[_this5.name] = relatedByOwnerId[owner.$values(_this5.ownerProp)] || []; | ||
}); | ||
// Delete the temporary join aliases. | ||
_lodash2.default.each(related, function (rel) { | ||
delete rel[ownerJoinPropAlias]; | ||
_lodash2.default.each(ownerJoinPropertyAlias, function (alias) { | ||
delete rel[alias]; | ||
}); | ||
}); | ||
@@ -386,3 +477,3 @@ | ||
value: function insert(builder, owner, insertion) { | ||
var _this3 = this; | ||
var _this6 = this; | ||
@@ -394,10 +485,12 @@ builder.onBuild(function (builder) { | ||
builder.runAfterModelCreate(function (related) { | ||
var ownerId = owner[_this3.ownerProp]; | ||
var relatedIds = _lodash2.default.pluck(related, _this3.relatedProp); | ||
var joinModels = _this3._createJoinModels(ownerId, relatedIds); | ||
var ownerId = owner.$values(_this6.ownerProp); | ||
var relatedIds = _lodash2.default.map(related, function (related) { | ||
return related.$values(_this6.relatedProp); | ||
}); | ||
var joinModels = _this6._createJoinModels(ownerId, relatedIds); | ||
owner[_this3.name] = _this3.mergeModels(owner[_this3.name], related); | ||
owner[_this6.name] = _this6.mergeModels(owner[_this6.name], related); | ||
// Insert the join rows to the join table. | ||
return _this3.joinTableModelClass.bindKnex(builder.modelClass().knex()).query().childQueryOf(builder).insert(joinModels).return(related); | ||
return _this6.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).insert(joinModels).return(related); | ||
}); | ||
@@ -414,8 +507,6 @@ } | ||
value: function update(builder, owner, _update) { | ||
var _this4 = this; | ||
var _this7 = this; | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = _this4._makeFindIdQuery(builder, owner[_this4.ownerProp]); | ||
builder.$$update(_update).whereIn(_this4.relatedModelClass.getFullIdColumn(), idSelectQuery).call(_this4.filter); | ||
_this7._selectForModify(builder, owner).$$update(_update).call(_this7.filter); | ||
}); | ||
@@ -430,21 +521,8 @@ } | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
var _this8 = this; | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = _this5._makeFindIdQuery(builder, owner[_this5.ownerProp]); | ||
builder.$$delete().whereIn(_this5.relatedModelClass.getFullIdColumn(), idSelectQuery).call(_this5.filter); | ||
_this8._selectForModify(builder, owner).$$delete().call(_this8.filter); | ||
}); | ||
@@ -461,8 +539,10 @@ } | ||
value: function relate(builder, owner, ids) { | ||
var _this6 = this; | ||
var _this9 = this; | ||
builder.setQueryExecutor(function () { | ||
var joinModels = _this6._createJoinModels(owner[_this6.ownerProp], ids); | ||
ids = this.normalizeId(ids, this.relatedProp.length); | ||
return _this6.joinTableModelClass.bindKnex(_this6.ownerModelClass.knex()).query().childQueryOf(builder).insert(joinModels).runAfter(_lodash2.default.constant({})); | ||
builder.setQueryExecutor(function (builder) { | ||
var joinModels = _this9._createJoinModels(owner.$values(_this9.ownerProp), ids); | ||
return _this9.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).insert(joinModels).runAfter(_lodash2.default.constant({})); | ||
}); | ||
@@ -479,8 +559,8 @@ } | ||
value: function unrelate(builder, owner) { | ||
var _this7 = this; | ||
var _this10 = this; | ||
builder.setQueryExecutor(function (builder) { | ||
var idSelectQuery = _this7.relatedModelClass.query().childQueryOf(builder).copyFrom(builder, /where/i).select(_this7.fullRelatedCol()).call(_this7.filter); | ||
var selectRelatedColQuery = _this10.relatedModelClass.query().childQueryOf(builder).copyFrom(builder, /where/i).select(_this10.fullRelatedCol()).call(_this10.filter); | ||
return _this7.joinTableModelClass.bindKnex(_this7.ownerModelClass.knex()).query().childQueryOf(builder).delete().where(_this7.fullJoinTableOwnerCol(), owner[_this7.ownerProp]).whereIn(_this7.fullJoinTableRelatedCol(), idSelectQuery).runAfter(_lodash2.default.constant({})); | ||
return _this10.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).delete().whereComposite(_this10.fullJoinTableOwnerCol(), owner.$values(_this10.ownerProp)).whereInComposite(_this10.fullJoinTableRelatedCol(), selectRelatedColQuery).runAfter(_lodash2.default.constant({})); | ||
}); | ||
@@ -490,2 +570,5 @@ } | ||
/** | ||
* Special unrelate implementation for sqlite3. sqlite3 doesn't support multi-value | ||
* where-in clauses. We need to use the built-in _rowid_ instead. | ||
* | ||
* @private | ||
@@ -495,5 +578,23 @@ */ | ||
}, { | ||
key: '_makeFindIdQuery', | ||
value: function _makeFindIdQuery(builder, ownerId) { | ||
return this.joinTableModelClass.bindKnex(this.ownerModelClass.knex()).query().childQueryOf(builder).select(this.fullJoinTableRelatedCol()).where(this.fullJoinTableOwnerCol(), ownerId); | ||
key: 'unrelate_sqlite3', | ||
value: function unrelate_sqlite3(builder, owner) { | ||
var _this11 = this; | ||
builder.setQueryExecutor(function (builder) { | ||
var joinTableAlias = _this11.joinTableAlias(); | ||
var joinTableAsAlias = _this11.joinTable + ' as ' + joinTableAlias; | ||
var joinTableAliasRowId = joinTableAlias + '.' + sqliteBuiltInRowId; | ||
var joinTableRowId = _this11.joinTable + '.' + sqliteBuiltInRowId; | ||
var ownerId = owner.$values(_this11.ownerProp); | ||
var fullRelatedCol = _this11.fullRelatedCol(); | ||
var selectRelatedQuery = _this11.relatedModelClass.query().childQueryOf(builder).copyFrom(builder, /where/i).select(joinTableAliasRowId).call(_this11.filter).whereComposite(_this11.fullJoinTableOwnerCol(), ownerId).join(joinTableAsAlias, function (join) { | ||
_lodash2.default.each(_this11.fullJoinTableRelatedCol(), function (joinTableRelatedCol, idx) { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
return _this11.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).delete().whereIn(joinTableRowId, selectRelatedQuery).runAfter(_lodash2.default.constant({})); | ||
}); | ||
} | ||
@@ -506,5 +607,49 @@ | ||
}, { | ||
key: '_selectForModify', | ||
value: function _selectForModify(builder, owner) { | ||
var ownerId = owner.$values(this.ownerProp); | ||
var idQuery = this.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).select(this.fullJoinTableRelatedCol()).whereComposite(this.fullJoinTableOwnerCol(), ownerId); | ||
return builder.whereInComposite(this.fullRelatedCol(), idQuery); | ||
} | ||
/** | ||
* Special _selectForModify implementation for sqlite3. sqlite3 doesn't support multi-value | ||
* where-in clauses. We need to use the built-in _rowid_ instead. | ||
* | ||
* @private | ||
*/ | ||
}, { | ||
key: '_selectForModify_sqlite3', | ||
value: function _selectForModify_sqlite3(builder, owner) { | ||
var _this12 = this; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedTableAliasRowId = relatedTableAlias + '.' + sqliteBuiltInRowId; | ||
var relatedTableRowId = relatedTable + '.' + sqliteBuiltInRowId; | ||
var fullRelatedCol = this.fullRelatedCol(); | ||
var ownerId = owner.$values(this.ownerProp); | ||
var selectRelatedQuery = this.joinTableModelClass.bindKnex(builder.knex()).query().childQueryOf(builder).select(relatedTableAliasRowId).whereComposite(this.fullJoinTableOwnerCol(), ownerId).join(relatedTableAsAlias, function (join) { | ||
_lodash2.default.each(_this12.fullJoinTableRelatedCol(), function (joinTableRelatedCol, idx) { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
return builder.whereInComposite(relatedTableRowId, selectRelatedQuery); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '_createJoinModels', | ||
value: function _createJoinModels(ownerId, relatedIds) { | ||
var _this8 = this; | ||
var _this13 = this; | ||
@@ -514,5 +659,10 @@ return _lodash2.default.map(relatedIds, function (relatedId) { | ||
joinModel[_this8.joinTableOwnerProp] = ownerId; | ||
joinModel[_this8.joinTableRelatedProp] = relatedId; | ||
_lodash2.default.each(_this13.joinTableOwnerProp, function (joinTableOwnerProp, idx) { | ||
joinModel[joinTableOwnerProp] = ownerId[idx]; | ||
}); | ||
_lodash2.default.each(_this13.joinTableRelatedProp, function (joinTableRelatedProp, idx) { | ||
joinModel[joinTableRelatedProp] = relatedId[idx]; | ||
}); | ||
return joinModel; | ||
@@ -523,4 +673,3 @@ }); | ||
return ManyToManyRelation; | ||
})(_Relation3.default); | ||
})(_Relation3.default), (_applyDecoratedDescriptor(_class2.prototype, 'fullJoinTableOwnerCol', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class2.prototype, 'fullJoinTableOwnerCol'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'fullJoinTableRelatedCol', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class2.prototype, 'fullJoinTableRelatedCol'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'unrelate', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class2.prototype, 'unrelate'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, '_selectForModify', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class2.prototype, '_selectForModify'), _class2.prototype)), _class2)) || _class); | ||
exports.default = ManyToManyRelation; |
@@ -52,43 +52,2 @@ 'use strict'; | ||
(0, _createClass3.default)(OneToManyRelation, [{ | ||
key: 'findQuery', | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_lodash2.default.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
return builder[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()).call(this.filter); | ||
} | ||
}, { | ||
key: 'find', | ||
@@ -103,13 +62,20 @@ | ||
var ownerIds = _lodash2.default.unique(_lodash2.default.pluck(owners, this.ownerProp)); | ||
builder.onBuild(function (builder) { | ||
var ids = (0, _lodash2.default)(owners).map(function (owner) { | ||
return owner.$values(_this2.ownerProp); | ||
}).unique(function (id) { | ||
return id.join(); | ||
}).value(); | ||
builder.onBuild(function (builder) { | ||
_this2.findQuery(builder, ownerIds); | ||
_this2.findQuery(builder, ids); | ||
}); | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedByOwnerId = _lodash2.default.groupBy(related, _this2.relatedProp); | ||
var relatedByOwnerId = _lodash2.default.groupBy(related, function (related) { | ||
return related.$values(_this2.relatedProp); | ||
}); | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedByOwnerId[owner[_this2.ownerProp]] || []; | ||
var ownerId = owner.$values(_this2.ownerProp); | ||
owner[_this2.name] = relatedByOwnerId[ownerId] || []; | ||
}); | ||
@@ -132,3 +98,5 @@ | ||
_lodash2.default.each(insertion.models(), function (insert) { | ||
insert[_this3.relatedProp] = owner[_this3.ownerProp]; | ||
_lodash2.default.each(_this3.relatedProp, function (relatedProp, idx) { | ||
insert[relatedProp] = owner[_this3.ownerProp[idx]]; | ||
}); | ||
}); | ||
@@ -152,36 +120,16 @@ | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
var _this4 = this; | ||
builder.onBuild(function (builder) { | ||
_this4.findQuery(builder, owner[_this4.ownerProp]); | ||
builder.$$update(_update); | ||
}); | ||
} | ||
ids = this.normalizeId(ids, this.relatedModelClass.getIdColumnDimension()); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = {}; | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
_lodash2.default.each(_this4.relatedProp, function (relatedProp, idx) { | ||
patch[relatedProp] = owner[_this4.ownerProp[idx]]; | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
builder.onBuild(function (builder) { | ||
_this5.findQuery(builder, owner[_this5.ownerProp]); | ||
builder.$$delete(); | ||
return _this4.relatedModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).whereInComposite(_this4.relatedModelClass.getFullIdColumn(), ids).call(_this4.filter).runAfter(_lodash2.default.constant({})); | ||
}); | ||
@@ -196,27 +144,14 @@ } | ||
}, { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
var _this6 = this; | ||
builder.setQueryExecutor(function () { | ||
var patch = relatePatch(_this6, owner[_this6.ownerProp]); | ||
return _this6.relatedModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).whereIn(_this6.relatedModelClass.getFullIdColumn(), ids).call(_this6.filter).runAfter(_lodash2.default.constant({})); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'unrelate', | ||
value: function unrelate(builder, owner) { | ||
var _this7 = this; | ||
var _this5 = this; | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = relatePatch(_this7, null); | ||
var patch = {}; | ||
return _this7.relatedModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).where(_this7.fullRelatedCol(), owner[_this7.ownerProp]).call(_this7.filter).runAfter(_lodash2.default.constant({})); | ||
_lodash2.default.each(_this5.relatedProp, function (relatedProp) { | ||
patch[relatedProp] = null; | ||
}); | ||
return _this5.relatedModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).whereComposite(_this5.fullRelatedCol(), owner.$values(_this5.ownerProp)).call(_this5.filter).runAfter(_lodash2.default.constant({})); | ||
}); | ||
@@ -228,11 +163,2 @@ } | ||
/** | ||
* @private | ||
*/ | ||
exports.default = OneToManyRelation; | ||
function relatePatch(relation, value) { | ||
var patch = {}; | ||
patch[relation.relatedProp] = value; | ||
return patch; | ||
} | ||
exports.default = OneToManyRelation; |
@@ -52,3 +52,3 @@ 'use strict'; | ||
(0, _createClass3.default)(OneToOneRelation, [{ | ||
key: 'findQuery', | ||
key: 'find', | ||
@@ -58,45 +58,3 @@ /** | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_lodash2.default.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), _lodash2.default.compact(ownerCol)); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
return builder[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()).call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'find', | ||
value: function find(builder, owners) { | ||
@@ -106,11 +64,19 @@ var _this2 = this; | ||
builder.onBuild(function (builder) { | ||
var relatedIds = _lodash2.default.unique(_lodash2.default.compact(_lodash2.default.pluck(owners, _this2.ownerProp))); | ||
_this2._makeFindQuery(builder, relatedIds); | ||
var ids = (0, _lodash2.default)(owners).map(function (owner) { | ||
return owner.$values(_this2.ownerProp); | ||
}).unique(function (id) { | ||
return id.join(); | ||
}).value(); | ||
_this2.findQuery(builder, ids); | ||
}); | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedById = _lodash2.default.indexBy(related, _this2.relatedProp); | ||
var relatedByOwnerId = _lodash2.default.indexBy(related, function (related) { | ||
return related.$values(_this2.relatedProp); | ||
}); | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedById[owner[_this2.ownerProp]] || null; | ||
var ownerId = owner.$values(_this2.ownerProp); | ||
owner[_this2.name] = relatedByOwnerId[ownerId] || null; | ||
}); | ||
@@ -133,3 +99,3 @@ | ||
if (insertion.models().length > 1) { | ||
throw new Error('can only insert one model to a OneToOneRelation'); | ||
this.throwError('can only insert one model to a OneToOneRelation'); | ||
} | ||
@@ -142,25 +108,12 @@ | ||
builder.runAfterModelCreate(function (inserted) { | ||
owner[_this3.ownerProp] = inserted[0][_this3.relatedProp]; | ||
owner[_this3.name] = inserted[0]; | ||
var patch = {}; | ||
patch[_this3.ownerProp] = inserted[0][_this3.relatedProp]; | ||
return _this3.ownerModelClass.query().childQueryOf(builder).patch(patch).where(_this3.ownerModelClass.getFullIdColumn(), owner.$id()).return(inserted); | ||
}); | ||
} | ||
_lodash2.default.each(_this3.ownerProp, function (ownerProp, idx) { | ||
var relatedValue = inserted[0][_this3.relatedProp[idx]]; | ||
owner[ownerProp] = relatedValue; | ||
patch[ownerProp] = relatedValue; | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
var _this4 = this; | ||
builder.onBuild(function (builder) { | ||
_this4._makeFindQuery(builder, owner[_this4.ownerProp]); | ||
builder.$$update(_update); | ||
return _this3.ownerModelClass.query().childQueryOf(builder).patch(patch).whereComposite(_this3.ownerModelClass.getFullIdColumn(), owner.$id()).return(inserted); | ||
}); | ||
@@ -175,35 +128,10 @@ } | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
builder.onBuild(function (builder) { | ||
_this5._makeFindQuery(builder, owner[_this5.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
var _this6 = this; | ||
var _this4 = this; | ||
ids = this.normalizeId(ids, this.relatedProp.length); | ||
if (ids.length > 1) { | ||
throw new Error('can only relate one model to a OneToOneRelation'); | ||
this.throwError('can only relate one model to a OneToOneRelation'); | ||
} | ||
@@ -214,6 +142,8 @@ | ||
patch[_this6.ownerProp] = ids[0]; | ||
owner[_this6.ownerProp] = ids[0]; | ||
_lodash2.default.each(_this4.ownerProp, function (prop, idx) { | ||
patch[prop] = ids[0][idx]; | ||
owner[prop] = ids[0][idx]; | ||
}); | ||
return _this6.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).where(_this6.ownerModelClass.getFullIdColumn(), owner.$id()).runAfterModelCreate(_lodash2.default.constant({})); | ||
return _this4.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).whereComposite(_this4.ownerModelClass.getFullIdColumn(), owner.$id()).runAfterModelCreate(_lodash2.default.constant({})); | ||
}); | ||
@@ -230,3 +160,3 @@ } | ||
value: function unrelate(builder, owner) { | ||
var _this7 = this; | ||
var _this5 = this; | ||
@@ -236,22 +166,10 @@ builder.setQueryExecutor(function (builder) { | ||
patch[_this7.ownerProp] = null; | ||
owner[_this7.ownerProp] = null; | ||
_lodash2.default.each(_this5.ownerProp, function (prop) { | ||
patch[prop] = null; | ||
owner[prop] = null; | ||
}); | ||
return _this7.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).where(_this7.ownerModelClass.getFullIdColumn(), owner.$id()).runAfterModelCreate(_lodash2.default.constant({})); | ||
return _this5.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).whereComposite(_this5.ownerModelClass.getFullIdColumn(), owner.$id()).runAfterModelCreate(_lodash2.default.constant({})); | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '_makeFindQuery', | ||
value: function _makeFindQuery(builder, relatedIds) { | ||
if (_lodash2.default.isArray(relatedIds) && _lodash2.default.isEmpty(relatedIds) || !relatedIds) { | ||
return builder.resolve([]); | ||
} else { | ||
return this.findQuery(builder, relatedIds); | ||
} | ||
} | ||
}]); | ||
@@ -258,0 +176,0 @@ return OneToOneRelation; |
'use strict'; | ||
var _desc, _value, _class; | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -8,2 +10,6 @@ value: true | ||
var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); | ||
var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
@@ -25,5 +31,5 @@ | ||
var _utils = require('../utils'); | ||
var _classUtils = require('../utils/classUtils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _decorators = require('../utils/decorators'); | ||
@@ -36,2 +42,31 @@ var _QueryBuilder = require('../queryBuilder/QueryBuilder'); | ||
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { | ||
var desc = {}; | ||
Object['ke' + 'ys'](descriptor).forEach(function (key) { | ||
desc[key] = descriptor[key]; | ||
}); | ||
desc.enumerable = !!desc.enumerable; | ||
desc.configurable = !!desc.configurable; | ||
if ('value' in desc || desc.initializer) { | ||
desc.writable = true; | ||
} | ||
desc = decorators.slice().reverse().reduce(function (desc, decorator) { | ||
return decorator(target, property, desc) || desc; | ||
}, desc); | ||
if (context && desc.initializer !== void 0) { | ||
desc.value = desc.initializer ? desc.initializer.call(context) : void 0; | ||
desc.initializer = undefined; | ||
} | ||
if (desc.initializer === void 0) { | ||
Object['define' + 'Property'](target, property, desc); | ||
desc = null; | ||
} | ||
return desc; | ||
} | ||
/** | ||
@@ -62,11 +97,14 @@ * @typedef {Object} RelationJoin | ||
* | ||
* @property {string} from | ||
* @property {string|Array.<string>} from | ||
* The relation column in the owner table. Must be given with the table name. | ||
* For example `Person.id`. Note that neither this nor `to` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* For example `Person.id`. Composite key can be specified using an array of | ||
* columns e.g. `['Person.a', 'Person.b']`. Note that neither this nor `to` | ||
* need to be foreign keys or primary keys. You can join any column to | ||
* any column. | ||
* | ||
* @property {string} to | ||
* @property {string|Array.<string>} to | ||
* The relation column in the related table. Must be given with the table name. | ||
* For example `Movie.id`. Note that neither this nor `from` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* For example `Movie.id`. Composite key can be specified using an array of | ||
* columns e.g. `['Movie.a', 'Movie.b']`. Note that neither this nor `from` | ||
* need to be foreign keys or primary keys. You can join any column to any column. | ||
* | ||
@@ -80,9 +118,11 @@ * @property {Object} through | ||
* | ||
* @property {string} through.from | ||
* @property {string|Array.<string>} through.from | ||
* The column that is joined to `from` property of the `RelationJoin`. For example | ||
* `Person_Movie.actorId` where `Person_Movie` is the join table. | ||
* `Person_Movie.actorId` where `Person_Movie` is the join table. Composite key can | ||
* be specified using an array of columns e.g. `['Person_Movie.a', 'Person_Movie.b']`. | ||
* | ||
* @property {string} through.to | ||
* @property {string|Array.<string>} through.to | ||
* The column that is joined to `to` property of the `RelationJoin`. For example | ||
* `Person_Movie.movieId` where `Person_Movie` is the join table. | ||
* `Person_Movie.movieId` where `Person_Movie` is the join table. Composite key can | ||
* be specified using an array of columns e.g. `['Person_Movie.a', 'Person_Movie.b']`. | ||
*/ | ||
@@ -122,4 +162,3 @@ | ||
*/ | ||
var Relation = (function () { | ||
var Relation = (_class = (function () { | ||
function Relation(relationName, OwnerClass) { | ||
@@ -156,3 +195,3 @@ (0, _classCallCheck3.default)(this, Relation); | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -164,3 +203,3 @@ this.ownerCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -172,3 +211,3 @@ this.ownerProp = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -180,3 +219,3 @@ this.relatedCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -212,10 +251,8 @@ this.relatedProp = null; | ||
if (!_utils2.default.isSubclassOf(this.ownerModelClass, Model)) { | ||
throw new Error('Relation\'s owner is not a subclass of Model'); | ||
if (!(0, _classUtils.isSubclassOf)(this.ownerModelClass, Model)) { | ||
this.throwError('Relation\'s owner is not a subclass of Model'); | ||
} | ||
var errorPrefix = this.ownerModelClass.name + '.relationMappings.' + this.name; | ||
if (!mapping.modelClass) { | ||
throw new Error(errorPrefix + '.modelClass is not defined'); | ||
this.throwError('modelClass is not defined'); | ||
} | ||
@@ -227,9 +264,9 @@ | ||
var relatedModelClassModule = require(mapping.modelClass); | ||
this.relatedModelClass = _utils2.default.isSubclassOf(relatedModelClassModule.default, Model) ? relatedModelClassModule.default : relatedModelClassModule; | ||
this.relatedModelClass = (0, _classUtils.isSubclassOf)(relatedModelClassModule.default, Model) ? relatedModelClassModule.default : relatedModelClassModule; | ||
} catch (err) { | ||
throw new Error(errorPrefix + '.modelClass is an invalid file path to a model class.'); | ||
this.throwError('modelClass is an invalid file path to a model class.'); | ||
} | ||
if (!_utils2.default.isSubclassOf(this.relatedModelClass, Model)) { | ||
throw new Error(errorPrefix + '.modelClass is a valid path to a module, but the module doesn\'t export a Model subclass.'); | ||
if (!(0, _classUtils.isSubclassOf)(this.relatedModelClass, Model)) { | ||
this.throwError('modelClass is a valid path to a module, but the module doesn\'t export a Model subclass.'); | ||
} | ||
@@ -239,4 +276,4 @@ } else { | ||
if (!_utils2.default.isSubclassOf(this.relatedModelClass, Model)) { | ||
throw new Error(errorPrefix + '.modelClass is not a subclass of Model or a file path to a module that exports one.'); | ||
if (!(0, _classUtils.isSubclassOf)(this.relatedModelClass, Model)) { | ||
this.throwError('modelClass is not a subclass of Model or a file path to a module that exports one.'); | ||
} | ||
@@ -246,11 +283,11 @@ } | ||
if (!mapping.relation) { | ||
throw new Error(errorPrefix + '.relation is not defined'); | ||
this.throwError('relation is not defined'); | ||
} | ||
if (!_utils2.default.isSubclassOf(mapping.relation, Relation)) { | ||
throw new Error(errorPrefix + '.relation is not a subclass of Relation'); | ||
if (!(0, _classUtils.isSubclassOf)(mapping.relation, Relation)) { | ||
this.throwError('relation is not a subclass of Relation'); | ||
} | ||
if (!mapping.join || !_lodash2.default.isString(mapping.join.from) || !_lodash2.default.isString(mapping.join.to)) { | ||
throw new Error(errorPrefix + '.join must be an object that maps the columns of the related models together. For example: {from: \'SomeTable.id\', to: \'SomeOtherTable.someModelId\'}'); | ||
if (!mapping.join || !mapping.join.from || !mapping.join.to) { | ||
this.throwError('join must be an object that maps the columns of the related models together. For example: {from: "SomeTable.id", to: "SomeOtherTable.someModelId"}'); | ||
} | ||
@@ -261,11 +298,11 @@ | ||
var joinFrom = Relation.parseColumn(mapping.join.from); | ||
var joinTo = Relation.parseColumn(mapping.join.to); | ||
var joinFrom = this.parseReference(mapping.join.from); | ||
var joinTo = this.parseReference(mapping.join.to); | ||
if (!joinFrom.table || !joinFrom.name) { | ||
throw new Error(errorPrefix + '.join.from must have format TableName.columnName. For example `SomeTable.id`.'); | ||
if (!joinFrom.table || _lodash2.default.isEmpty(joinFrom.columns)) { | ||
this.throwError('join.from must have format TableName.columnName. For example "SomeTable.id" or in case of composite key ["SomeTable.a", "SomeTable.b"].'); | ||
} | ||
if (!joinTo.table || !joinTo.name) { | ||
throw new Error(errorPrefix + '.join.to must have format TableName.columnName. For example `SomeTable.id`.'); | ||
if (!joinTo.table || _lodash2.default.isEmpty(joinTo.columns)) { | ||
this.throwError('join.to must have format TableName.columnName. For example "SomeTable.id" or in case of composite key ["SomeTable.a", "SomeTable.b"].'); | ||
} | ||
@@ -280,22 +317,32 @@ | ||
} else { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the owner model table.'); | ||
this.throwError('join: either `from` or `to` must point to the owner model table.'); | ||
} | ||
if (joinRelated.table !== this.relatedModelClass.tableName) { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the related model table.'); | ||
this.throwError('join: either `from` or `to` must point to the related model table.'); | ||
} | ||
this.ownerProp = this._propertyName(joinOwner, this.ownerModelClass); | ||
this.ownerCol = joinOwner.name; | ||
this.relatedProp = this._propertyName(joinRelated, this.relatedModelClass); | ||
this.relatedCol = joinRelated.name; | ||
this.filter = Relation.parseFilter(mapping); | ||
this.ownerCol = joinOwner.columns; | ||
this.ownerProp = this.propertyName(this.ownerCol, this.ownerModelClass); | ||
this.relatedCol = joinRelated.columns; | ||
this.relatedProp = this.propertyName(this.relatedCol, this.relatedModelClass); | ||
this.filter = this.parseFilter(mapping); | ||
} | ||
/** | ||
* Return the knex connection. | ||
*/ | ||
}, { | ||
key: 'knex', | ||
value: function knex() { | ||
return this.ownerModelClass.knex(); | ||
} | ||
/** | ||
* Reference to the relation column in the owner model's table. | ||
* | ||
* For example: `Person.id`. | ||
* For example: [`Person.id`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@@ -306,3 +353,7 @@ | ||
value: function fullOwnerCol() { | ||
return this.ownerModelClass.tableName + '.' + this.ownerCol; | ||
var _this = this; | ||
return _lodash2.default.map(this.ownerCol, function (col) { | ||
return _this.ownerModelClass.tableName + '.' + col; | ||
}); | ||
} | ||
@@ -313,5 +364,5 @@ | ||
* | ||
* For example: `Movie.id`. | ||
* For example: [`Movie.id`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@@ -322,3 +373,7 @@ | ||
value: function fullRelatedCol() { | ||
return this.relatedModelClass.tableName + '.' + this.relatedCol; | ||
var _this2 = this; | ||
return _lodash2.default.map(this.relatedCol, function (col) { | ||
return _this2.relatedModelClass.tableName + '.' + col; | ||
}); | ||
} | ||
@@ -391,5 +446,6 @@ | ||
value: function mergeModels(models1, models2) { | ||
var modelsById = (0, _create2.default)(null); | ||
models1 = _lodash2.default.compact(models1); | ||
models2 = _lodash2.default.compact(models2); | ||
var modelsById = (0, _create2.default)(null); | ||
@@ -404,13 +460,20 @@ _lodash2.default.forEach(models1, function (model) { | ||
return _lodash2.default.sortBy(_lodash2.default.values(modelsById), function (model) { | ||
return model.$id(); | ||
}); | ||
var models = _lodash2.default.values(modelsById); | ||
if (models.length === 0) { | ||
return []; | ||
} | ||
var modelClass = models[0].constructor; | ||
var idProperty = modelClass.getIdProperty(); | ||
if (!_lodash2.default.isArray(idProperty)) { | ||
idProperty = [idProperty]; | ||
} | ||
return _lodash2.default.sortByAll(models, idProperty); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {number|string} ownerCol | ||
* @param {boolean} isColumnRef | ||
* @param {Array.<string>|Array.<Array.<(string|number)>>} ownerIds | ||
* @param {boolean=} isColumnRef | ||
* @returns {QueryBuilder} | ||
@@ -421,11 +484,26 @@ */ | ||
key: 'findQuery', | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
throw new Error('not implemented'); | ||
value: function findQuery(builder, ownerIds, isColumnRef) { | ||
var fullRelatedCol = this.fullRelatedCol(); | ||
if (isColumnRef) { | ||
_lodash2.default.each(fullRelatedCol, function (col, idx) { | ||
builder.whereRef(col, ownerIds[idx]); | ||
}); | ||
} else { | ||
if ((0, _lodash2.default)(ownerIds).flatten().all(function (id) { | ||
return _lodash2.default.isNull(id) || _lodash2.default.isUndefined(id); | ||
})) { | ||
// Nothing to fetch. | ||
builder.resolve([]); | ||
} else { | ||
builder.whereInComposite(fullRelatedCol, ownerIds); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {string} joinMethod | ||
* @param {string=} joinMethod | ||
* @returns {QueryBuilder} | ||
@@ -437,3 +515,18 @@ */ | ||
value: function join(builder, joinMethod) { | ||
throw new Error('not implemented'); | ||
joinMethod = joinMethod || 'join'; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedCol = _lodash2.default.map(this.relatedCol, function (col) { | ||
return relatedTableAlias + '.' + col; | ||
}); | ||
var ownerCol = this.fullOwnerCol(); | ||
return builder[joinMethod](relatedTableAsAlias, function (join) { | ||
_lodash2.default.each(relatedCol, function (relatedCol, idx) { | ||
join.on(relatedCol, '=', ownerCol[idx]); | ||
}); | ||
}).call(this.filter); | ||
} | ||
@@ -445,3 +538,3 @@ | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} owners | ||
* @param {Array.<Model>} owners | ||
*/ | ||
@@ -452,3 +545,3 @@ | ||
value: function find(builder, owners) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
@@ -458,4 +551,5 @@ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} insertion | ||
@@ -467,10 +561,8 @@ */ | ||
value: function insert(builder, owner, insertion) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} update | ||
@@ -482,10 +574,13 @@ */ | ||
value: function update(builder, owner, _update) { | ||
return builder; | ||
var _this3 = this; | ||
builder.onBuild(function (builder) { | ||
_this3.findQuery(builder, [owner.$values(_this3.ownerProp)]); | ||
builder.$$update(_update); | ||
}); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} patch | ||
@@ -497,10 +592,8 @@ */ | ||
value: function patch(builder, owner, _patch) { | ||
throw new Error('not implemented'); | ||
return this.update(builder, owner, _patch); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
*/ | ||
@@ -511,3 +604,8 @@ | ||
value: function _delete(builder, owner) { | ||
throw new Error('not implemented'); | ||
var _this4 = this; | ||
builder.onBuild(function (builder) { | ||
_this4.findQuery(builder, [owner.$values(_this4.ownerProp)]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
@@ -520,3 +618,3 @@ | ||
* @param {Model|Object} owner | ||
* @param {number|string|Array.<number>|Array.<string>} ids | ||
* @param {number|string|Array.<number|string>|Array.<Array.<number|string>>} ids | ||
*/ | ||
@@ -527,3 +625,3 @@ | ||
value: function relate(builder, owner, ids) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
@@ -541,19 +639,23 @@ | ||
value: function unrelate(builder, owner) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
/** | ||
* @private | ||
* @protected | ||
*/ | ||
}, { | ||
key: '_propertyName', | ||
value: function _propertyName(column, modelClass) { | ||
var propertyName = modelClass.columnNameToPropertyName(column.name); | ||
key: 'propertyName', | ||
value: function propertyName(columns, modelClass) { | ||
var _this5 = this; | ||
if (!propertyName) { | ||
throw new Error(modelClass.name + '.$parseDatabaseJson probably transforms the value of the column ' + column.name + '.' + ' This is a no-no because ' + column.name + ' is needed in the relation ' + this.ownerModelClass.tableName + '.' + this.name); | ||
} | ||
return _lodash2.default.map(columns, function (column) { | ||
var propertyName = modelClass.columnNameToPropertyName(column); | ||
return propertyName; | ||
if (!propertyName) { | ||
throw new Error(modelClass.name + '.$parseDatabaseJson probably transforms the value of the column ' + column + '.' + ' This is a no-no because ' + column + ' is needed in the relation ' + _this5.ownerModelClass.tableName + '.' + _this5.name); | ||
} | ||
return propertyName; | ||
}); | ||
} | ||
@@ -565,8 +667,2 @@ | ||
}], [{ | ||
key: 'extend', | ||
value: function extend(subclassConstructor) { | ||
_utils2.default.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
} | ||
}, { | ||
@@ -591,15 +687,101 @@ key: 'parseFilter', | ||
}, { | ||
key: 'parseColumn', | ||
value: function parseColumn(column) { | ||
var parts = column.split('.'); | ||
key: 'parseReference', | ||
value: function parseReference(ref) { | ||
if (!_lodash2.default.isArray(ref)) { | ||
ref = [ref]; | ||
} | ||
var table = null; | ||
var columns = []; | ||
for (var i = 0; i < ref.length; ++i) { | ||
var parts = ref[i].split('.'); | ||
var tableName = parts[0] && parts[0].trim(); | ||
var columnName = parts[1] && parts[1].trim(); | ||
if (!tableName || table && table !== tableName || !columnName) { | ||
return { | ||
table: null, | ||
columns: [] | ||
}; | ||
} else { | ||
table = tableName; | ||
} | ||
columns.push(columnName); | ||
} | ||
return { | ||
table: parts[0] && parts[0].trim(), | ||
name: parts[1] && parts[1].trim() | ||
table: table, | ||
columns: columns | ||
}; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
}, { | ||
key: 'normalizeId', | ||
value: function normalizeId(ids, compositeLength) { | ||
var _this6 = this; | ||
var isComposite = compositeLength > 1; | ||
if (isComposite) { | ||
// For composite ids these two are okay: | ||
// | ||
// 1. [1, 3, 4] | ||
// 2. [[1, 3, 4], [4, 6, 1]] | ||
// | ||
if (!_lodash2.default.isArray(ids) || !_lodash2.default.isArray(ids[0]) && ids.length !== compositeLength) { | ||
this.throwError('Invalid composite key ' + ids); | ||
} | ||
// Normalize to array of arrays. | ||
if (!_lodash2.default.isArray(ids[0])) { | ||
ids = [ids]; | ||
} | ||
} else { | ||
// Normalize to array of arrays. | ||
if (!_lodash2.default.isArray(ids)) { | ||
ids = [[ids]]; | ||
} else if (!_lodash2.default.isArray(ids[0])) { | ||
ids = _lodash2.default.map(ids, function (id) { | ||
return [id]; | ||
}); | ||
} | ||
} | ||
_lodash2.default.each(ids, function (id) { | ||
if (id.length !== compositeLength) { | ||
_this6.throwError('Id ' + id + ' has invalid length. Expected ' + compositeLength); | ||
} | ||
}); | ||
return ids; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
}, { | ||
key: 'throwError', | ||
value: function throwError(message) { | ||
if (this.ownerModelClass && this.ownerModelClass.name && this.name) { | ||
throw new Error(this.ownerModelClass.name + '.relationMappings.' + this.name + ': ' + message); | ||
} else { | ||
throw new Error(this.constructor.name + ': ' + message); | ||
} | ||
} | ||
}], [{ | ||
key: 'extend', | ||
value: function extend(subclassConstructor) { | ||
(0, _classUtils.inherits)(subclassConstructor, this); | ||
return subclassConstructor; | ||
} | ||
}]); | ||
return Relation; | ||
})(); | ||
})(), (_applyDecoratedDescriptor(_class.prototype, 'fullOwnerCol', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'fullOwnerCol'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'fullRelatedCol', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'fullRelatedCol'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'relatedTableAlias', [_decorators.memoize], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'relatedTableAlias'), _class.prototype)), _class); | ||
exports.default = Relation; |
@@ -20,6 +20,4 @@ 'use strict'; | ||
var _utils = require('./utils'); | ||
var _classUtils = require('./utils/classUtils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -116,3 +114,3 @@ | ||
for (i = 0; i < modelClasses.length; ++i) { | ||
if (!_utils2.default.isSubclassOf(modelClasses[i], _Model2.default)) { | ||
if (!(0, _classUtils.isSubclassOf)(modelClasses[i], _Model2.default)) { | ||
return _bluebird2.default.reject(new Error('objection.transaction: all but the last argument should be Model subclasses')); | ||
@@ -186,3 +184,3 @@ } | ||
if (_utils2.default.isSubclassOf(modelClassOrKnex, _Model2.default)) { | ||
if ((0, _classUtils.isSubclassOf)(modelClassOrKnex, _Model2.default)) { | ||
knex = modelClassOrKnex.knex(); | ||
@@ -189,0 +187,0 @@ } |
'use strict'; | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _typeof2 = require('babel-runtime/helpers/typeof'); | ||
var _typeof3 = _interopRequireDefault(_typeof2); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var _ = require('lodash'), | ||
@@ -17,67 +7,3 @@ util = require('util'); | ||
/** | ||
* Makes the `Constructor` inherit `SuperConstructor`. | ||
* | ||
* Calls node.js `util.inherits` but also copies the "static" properties from | ||
* `SuperConstructor` to `Constructor`. | ||
* | ||
* This function is taken from Babel transpiler. | ||
* | ||
* @ignore | ||
* @param {Object} subClass | ||
* @param {Object} superClass | ||
*/ | ||
module.exports.inherits = function (subClass, superClass) { | ||
if (typeof superClass !== "function" && superClass !== null) { | ||
throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : (0, _typeof3.default)(superClass))); | ||
} | ||
subClass.prototype = (0, _create2.default)(superClass && superClass.prototype, { | ||
constructor: { | ||
value: subClass, | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
if (superClass) { | ||
subClass.__proto__ = superClass; | ||
} | ||
return subClass; | ||
}; | ||
/** | ||
* Tests if a constructor function inherits another constructor function. | ||
* | ||
* @ignore | ||
* @param {Object} Constructor | ||
* @param {Object} SuperConstructor | ||
* @returns {boolean} | ||
*/ | ||
module.exports.isSubclassOf = function (Constructor, SuperConstructor) { | ||
if (!_.isFunction(SuperConstructor)) { | ||
return false; | ||
} | ||
while (_.isFunction(Constructor)) { | ||
if (Constructor === SuperConstructor) return true; | ||
var proto = Constructor.prototype.__proto__; | ||
Constructor = proto && proto.constructor; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @ignore | ||
* @param knexQueryBuilder | ||
* @returns {boolean} | ||
*/ | ||
module.exports.isKnexQueryBuilder = function (knexQueryBuilder) { | ||
return knexQueryBuilder && knexQueryBuilder.client && _.isString(knexQueryBuilder.client.dialect); | ||
}; | ||
/** | ||
* @ignore | ||
* @param knex | ||
@@ -84,0 +10,0 @@ * @returns {boolean} |
{ | ||
"name": "objection", | ||
"version": "0.4.0-rc.2", | ||
"version": "0.4.0-rc.3", | ||
"description": "An SQL-friendly ORM for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/objection.js", |
import _ from 'lodash'; | ||
import utils from '../utils'; | ||
import ModelBase from './ModelBase'; | ||
@@ -9,2 +8,3 @@ import QueryBuilder from '../queryBuilder/QueryBuilder'; | ||
import EagerFetcher from '../queryBuilder/EagerFetcher'; | ||
import {memoize} from '../utils/decorators'; | ||
@@ -150,5 +150,7 @@ import Relation from '../relations/Relation'; | ||
* | ||
* Composite id can be specified by giving an array of column names. | ||
* | ||
* Defaults to 'id'. | ||
* | ||
* @type {string} | ||
* @type {string|Array.<string>} | ||
*/ | ||
@@ -307,8 +309,6 @@ static idColumn = 'id'; | ||
$id() { | ||
const ModelClass = this.constructor; | ||
if (arguments.length > 0) { | ||
this[ModelClass.getIdProperty()] = arguments[0]; | ||
return setId(this, arguments[0]); | ||
} else { | ||
return this[ModelClass.getIdProperty()]; | ||
return getId(this); | ||
} | ||
@@ -370,3 +370,3 @@ } | ||
builder.onBuild((builder) => { | ||
builder.where(ModelClass.getFullIdColumn(), this.$id()); | ||
builder.whereComposite(ModelClass.getFullIdColumn(), this.$id()); | ||
}); | ||
@@ -386,3 +386,3 @@ }) | ||
builder.onBuild((builder) => { | ||
builder.$$update(update).where(ModelClass.getFullIdColumn(), this.$id()); | ||
builder.$$update(update).whereComposite(ModelClass.getFullIdColumn(), this.$id()); | ||
}); | ||
@@ -396,3 +396,3 @@ }) | ||
builder.onBuild((builder) => { | ||
builder.$$update(patch).where(ModelClass.getFullIdColumn(), this.$id()); | ||
builder.$$update(patch).whereComposite(ModelClass.getFullIdColumn(), this.$id()); | ||
}); | ||
@@ -402,3 +402,3 @@ }) | ||
builder.onBuild((builder) => { | ||
builder.$$delete().where(ModelClass.getFullIdColumn(), this.$id()); | ||
builder.$$delete().whereComposite(ModelClass.getFullIdColumn(), this.$id()); | ||
}); | ||
@@ -1068,3 +1068,3 @@ }) | ||
* @param {knex} knex | ||
* @returns {Model} | ||
* @returns {Constructor.<Model>} | ||
*/ | ||
@@ -1126,3 +1126,3 @@ static bindKnex(knex) { | ||
* @param trx | ||
* @returns {Model} | ||
* @returns {Constructor.<Model>} | ||
*/ | ||
@@ -1188,2 +1188,14 @@ static bindTransaction(trx) { | ||
/** | ||
* @ignore | ||
* @returns {number} | ||
*/ | ||
static getIdColumnDimension() { | ||
if (_.isArray(this.idColumn)) { | ||
return this.idColumn.length; | ||
} else { | ||
return 1; | ||
} | ||
} | ||
/** | ||
* Returns the name of the identifier property. | ||
@@ -1195,14 +1207,13 @@ * | ||
* | ||
* @returns {string} | ||
* @returns {string|Array.<string>} | ||
*/ | ||
@memoize | ||
static getIdProperty() { | ||
let idProperty = this.columnNameToPropertyName(this.idColumn); | ||
let ModelClass = this; | ||
if (!idProperty) { | ||
throw new Error(this.name + | ||
'.$parseDatabaseJson probably changes the value of the id column `' + this.idColumn + | ||
'` which is a no-no.'); | ||
if (_.isArray(ModelClass.idColumn)) { | ||
return _.map(ModelClass.idColumn, col => idColumnToIdProperty(ModelClass, col)); | ||
} else { | ||
return idColumnToIdProperty(ModelClass, ModelClass.idColumn); | ||
} | ||
return idProperty; | ||
} | ||
@@ -1213,6 +1224,11 @@ | ||
* | ||
* @returns {string} | ||
* @returns {string|Array.<string>} | ||
*/ | ||
@memoize | ||
static getFullIdColumn() { | ||
return this.tableName + '.' + this.idColumn; | ||
if (_.isArray(this.idColumn)) { | ||
return _.map(this.idColumn, col => this.tableName + '.' + col); | ||
} else { | ||
return this.tableName + '.' + this.idColumn; | ||
} | ||
} | ||
@@ -1445,2 +1461,64 @@ | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function idColumnToIdProperty(ModelClass, idColumn) { | ||
let idProperty = ModelClass.columnNameToPropertyName(idColumn); | ||
if (!idProperty) { | ||
throw new Error(ModelClass.tableName + '.$parseDatabaseJson probably changes the value of the id column `' + idColumn + '` which is a no-no.'); | ||
} | ||
return idProperty; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function setId(model, id) { | ||
const idProp = model.constructor.getIdProperty(); | ||
const isArray = _.isArray(idProp); | ||
if (_.isArray(id)) { | ||
if (isArray) { | ||
if (id.length !== idProp.length) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
for (let i = 0; i < id.length; ++i) { | ||
model[idProp[i]] = id[i]; | ||
} | ||
} else { | ||
if (id.length !== 1) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
model[idProp] = id[0]; | ||
} | ||
} else { | ||
if (isArray) { | ||
if (idProp.length > 1) { | ||
throw new Error('trying to set an invalid identifier for a model'); | ||
} | ||
model[idProp[0]] = id; | ||
} else { | ||
model[idProp] = id; | ||
} | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function getId(model) { | ||
const idProp = model.constructor.getIdProperty(); | ||
if (_.isArray(idProp)) { | ||
return model.$values(idProp); | ||
} else { | ||
return model[idProp]; | ||
} | ||
} |
import _ from 'lodash'; | ||
import tv4 from 'tv4'; | ||
import tv4Formats from 'tv4-formats'; | ||
import utils from '../utils'; | ||
import ValidationError from '../ValidationError'; | ||
import {inherits} from '../utils/classUtils'; | ||
import {memoize} from '../utils/decorators'; | ||
@@ -115,12 +116,2 @@ /** | ||
/** | ||
* @private | ||
*/ | ||
static $$colToProp = null; | ||
/** | ||
* @private | ||
*/ | ||
static $$propToCol = null; | ||
/** | ||
* This is called before validation. | ||
@@ -565,3 +556,3 @@ * | ||
utils.inherits(subclassConstructor, this); | ||
inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
@@ -625,10 +616,14 @@ } | ||
*/ | ||
@memoize | ||
static columnNameToPropertyName(columnName) { | ||
this.$$ensurePropNameConversionCache(); | ||
let model = new this(); | ||
let addedProps = _.keys(model.$parseDatabaseJson({})); | ||
if (!this.$$colToProp[columnName]) { | ||
this.$$cachePropNameConversion(columnNameToPropertyName(this, columnName), columnName); | ||
} | ||
let row = {}; | ||
row[columnName] = null; | ||
return this.$$colToProp[columnName]; | ||
let props = _.keys(_.omit(model.$parseDatabaseJson(row), addedProps)); | ||
let propertyName = _.first(props); | ||
return propertyName || null; | ||
} | ||
@@ -641,32 +636,15 @@ | ||
*/ | ||
@memoize | ||
static propertyNameToColumnName(propertyName) { | ||
this.$$ensurePropNameConversionCache(); | ||
let model = new this(); | ||
let addedCols = _.keys(model.$formatDatabaseJson({})); | ||
if (!this.$$propToCol[propertyName]) { | ||
this.$$cachePropNameConversion(propertyName, propertyNameToColumnName(this, propertyName)); | ||
} | ||
let obj = {}; | ||
obj[propertyName] = null; | ||
return this.$$propToCol[propertyName]; | ||
} | ||
let cols = _.keys(_.omit(model.$formatDatabaseJson(obj), addedCols)); | ||
let columnName = _.first(cols); | ||
/** | ||
* @private | ||
*/ | ||
static $$ensurePropNameConversionCache() { | ||
if (!this.$$propToCol) { | ||
this.$$propToCol = Object.create(null) | ||
} | ||
if (!this.$$colToProp) { | ||
this.$$colToProp = Object.create(null); | ||
} | ||
return columnName || null; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
static $$cachePropNameConversion(propertyName, columnName) { | ||
this.$$propToCol[propertyName] = columnName; | ||
this.$$colToProp[columnName] = propertyName; | ||
} | ||
} | ||
@@ -894,36 +872,4 @@ | ||
/** | ||
* @private | ||
*/ | ||
function propertyNameToColumnName(ModelClass, propertyName) { | ||
let model = new ModelClass(); | ||
let addedCols = _.keys(model.$formatDatabaseJson({})); | ||
let obj = {}; | ||
obj[propertyName] = null; | ||
let cols = _.keys(_.omit(model.$formatDatabaseJson(obj), addedCols)); | ||
let columnName = _.first(cols); | ||
return columnName || null; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function columnNameToPropertyName(ModelClass, columnName) { | ||
let model = new ModelClass(); | ||
let addedProps = _.keys(model.$parseDatabaseJson({})); | ||
let row = {}; | ||
row[columnName] = null; | ||
let props = _.keys(_.omit(model.$parseDatabaseJson(row), addedProps)); | ||
let propertyName = _.first(props); | ||
return propertyName || null; | ||
} | ||
// Add validation formats, so that for example the following schema validation works: | ||
// createTime: {type: 'string', format: 'date-time'} | ||
tv4.addFormat(tv4Formats); |
@@ -154,3 +154,2 @@ import _ from 'lodash'; | ||
let batch = Object.create(null); | ||
let notUnique = Object.create(null); | ||
@@ -164,27 +163,27 @@ for (let n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
let sourceVal = node.model[conn.relation.ownerProp]; | ||
let targetVal = conn.node.model[conn.relation.relatedProp]; | ||
let sourceVal = node.model.$values(conn.relation.ownerProp); | ||
let targetVal = conn.node.model.$values(conn.relation.relatedProp); | ||
let uniqueKey; | ||
let joinModel = {}; | ||
let knex = conn.relation.ownerModelClass.knex(); | ||
let modelClass = conn.relation.joinTableModelClass; | ||
if (conn.relation.joinTableOwnerCol < conn.relation.joinTableRelatedCol) { | ||
uniqueKey = conn.relation.joinTable + '_' + sourceVal + '_' + targetVal; | ||
} else { | ||
uniqueKey = conn.relation.joinTable + '_' + targetVal + '_' + sourceVal; | ||
if (knex) { | ||
// TODO: Because the joinTableModelClass may have been created inside ManyToManyRelation, it may not be bound. We really should not have to know about it here... | ||
modelClass = modelClass.bindKnex(knex); | ||
} | ||
if (notUnique[uniqueKey]) { | ||
continue; | ||
for (let i = 0; i < sourceVal.length; ++i) { | ||
joinModel[conn.relation.joinTableOwnerProp[i]] = sourceVal[i]; | ||
} | ||
notUnique[uniqueKey] = true; | ||
for (let i = 0; i < targetVal.length; ++i) { | ||
joinModel[conn.relation.joinTableRelatedProp[i]] = targetVal[i]; | ||
} | ||
let joinModel = {}; | ||
joinModel[conn.relation.joinTableOwnerProp] = sourceVal; | ||
joinModel[conn.relation.joinTableRelatedProp] = targetVal; | ||
joinModel = conn.relation.joinTableModelClass.fromJson(joinModel); | ||
joinModel = modelClass.fromJson(joinModel); | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(conn.relation.joinTableModelClass, true); | ||
setTableInsertion(batch, conn.relation.joinTable, tableInsertion) | ||
tableInsertion = new TableInsertion(modelClass, true); | ||
setTableInsertion(batch, modelClass.tableName, tableInsertion) | ||
} | ||
@@ -197,2 +196,11 @@ | ||
// Remove duplicates. | ||
_.each(batch, tableInsertion => { | ||
if (tableInsertion.models.length) { | ||
let keys = _.keys(tableInsertion.models[0]); | ||
tableInsertion.models = _.unique(tableInsertion.models, model => model.$values(keys).join()); | ||
tableInsertion.isInputModel = _.times(tableInsertion.models.length, _.constant(false)); | ||
} | ||
}); | ||
return batch; | ||
@@ -344,7 +352,11 @@ } | ||
node.needs.push(new Dependency(parentNode, function (model) { | ||
model[rel.relatedProp] = this.node.model[rel.ownerProp]; | ||
for (let i = 0; i < rel.relatedProp.length; ++i) { | ||
model[rel.relatedProp[i]] = this.node.model[rel.ownerProp[i]]; | ||
} | ||
})); | ||
parentNode.isNeededBy.push(new Dependency(node, function (model) { | ||
this.node.model[rel.relatedProp] = model[rel.ownerProp]; | ||
for (let i = 0; i < rel.relatedProp.length; ++i) { | ||
this.node.model[rel.relatedProp[i]] = model[rel.ownerProp[i]]; | ||
} | ||
})); | ||
@@ -355,7 +367,11 @@ | ||
node.isNeededBy.push(new Dependency(parentNode, function (model) { | ||
this.node.model[rel.ownerProp] = model[rel.relatedProp]; | ||
for (let i = 0; i < rel.relatedProp.length; ++i) { | ||
this.node.model[rel.ownerProp[i]] = model[rel.relatedProp[i]]; | ||
} | ||
})); | ||
parentNode.needs.push(new Dependency(node, function (model) { | ||
model[rel.ownerProp] = this.node.model[rel.relatedProp]; | ||
for (let i = 0; i < rel.relatedProp.length; ++i) { | ||
model[rel.ownerProp[i]] = this.node.model[rel.relatedProp[i]]; | ||
} | ||
})); | ||
@@ -362,0 +378,0 @@ |
@@ -9,3 +9,4 @@ import _ from 'lodash'; | ||
import EagerFetcher from './EagerFetcher'; | ||
import utils from '../utils'; | ||
import {isPostgres} from '../utils/dbUtils'; | ||
import {deprecated} from '../utils/decorators'; | ||
@@ -132,2 +133,6 @@ /** | ||
this.internalContext(query.internalContext()); | ||
if (query.has(/debug/)) { | ||
this.debug(); | ||
} | ||
} | ||
@@ -699,2 +704,3 @@ return this; | ||
*/ | ||
@deprecated({removedIn: '0.6.0', useInstead: 'debug()'}) | ||
dumpSql(logger) { | ||
@@ -1328,2 +1334,22 @@ (logger || console.log)(this.toString()); | ||
/** | ||
* Shortcut for finding a model by id. | ||
* | ||
* ```js | ||
* Person.query().findById(1); | ||
* ``` | ||
* | ||
* Composite key: | ||
* | ||
* ```js | ||
* Person.query().findById([1, '10']); | ||
* ``` | ||
* | ||
* @param {*|Array.<*>} id | ||
* @returns {QueryBuilder} | ||
*/ | ||
findById(id) { | ||
return this.whereComposite(this._modelClass.getFullIdColumn(), id).first(); | ||
} | ||
/** | ||
* Creates an insert query. | ||
@@ -1399,3 +1425,3 @@ * | ||
this.runBefore((result, builder) => { | ||
if (insertion.models().length > 1 && !utils.isPostgres(ModelClass.knex())) { | ||
if (insertion.models().length > 1 && !isPostgres(ModelClass.knex())) { | ||
throw new Error('batch insert only works with Postgresql'); | ||
@@ -1416,15 +1442,14 @@ } else { | ||
this.runAfterModelCreatePushFront(ret => { | ||
// If the user specified a `returning` clause the result may already be | ||
// an array of objects. | ||
if (!_.isEmpty(ret) && _.isObject(ret[0])) { | ||
if (!_.isArray(ret) || _.isEmpty(ret)) { | ||
// Early exit if there is nothing to do. | ||
return insertion.models(); | ||
} | ||
// If the user specified a `returning` clause the result may already bean array of objects. | ||
if (_.all(ret, _.isObject)) { | ||
_.forEach(insertion.models(), (model, index) => { | ||
model.$set(ret[index]); | ||
// The returning clause must contain at least the identifier. | ||
if (!model.$id()) { | ||
throw new Error(`the identifier column "${ModelClass.idColumn}" must be selected by the "returning" clause.`); | ||
} | ||
}); | ||
} else if (_.isArray(ret)) { | ||
// If the return value is not an array of models, we assume | ||
// it is an array of identifiers. | ||
} else { | ||
// If the return value is not an array of objects, we assume it is an array of identifiers. | ||
_.forEach(insertion.models(), (model, idx) => { | ||
@@ -1438,2 +1463,3 @@ // Don't set the id if the model already has one. MySQL and Sqlite don't return the correct | ||
} | ||
return insertion.models(); | ||
@@ -1477,5 +1503,5 @@ }); | ||
.childQueryOf(builder) | ||
.whereIn(ModelClass.getFullIdColumn(), _.invoke(insertedModelArray, '$id')) | ||
.whereInComposite(ModelClass.getFullIdColumn(), _.map(insertedModelArray, model => model.$id())) | ||
.then(fetchedModels => { | ||
fetchedModels = _.indexBy(fetchedModels, ModelClass.getIdProperty()); | ||
fetchedModels = _.indexBy(fetchedModels, (model) => model.$id()); | ||
@@ -1583,3 +1609,3 @@ // Instead of returning the freshly fetched models, update the input | ||
const ModelClass = this._modelClass; | ||
const batchSize = utils.isPostgres(ModelClass.knex()) ? 100 : 1; | ||
const batchSize = isPostgres(ModelClass.knex()) ? 100 : 1; | ||
@@ -1741,3 +1767,3 @@ let insertion = new InsertionOrUpdate({ | ||
* | ||
* @param {number|string} id | ||
* @param {number|string|Array.<number|string>} id | ||
* The identifier of the object to update. | ||
@@ -1753,3 +1779,3 @@ * | ||
.$$updateWithOptions(modelOrObject, 'update', {}, id) | ||
.where(this._modelClass.getFullIdColumn(), id); | ||
.whereComposite(this._modelClass.getFullIdColumn(), id); | ||
} | ||
@@ -1784,3 +1810,3 @@ | ||
.childQueryOf(builder) | ||
.where(ModelClass.getFullIdColumn(), fetchId) | ||
.whereComposite(ModelClass.getFullIdColumn(), fetchId) | ||
.then(model => model ? update.model().$set(model) : null); | ||
@@ -1807,2 +1833,3 @@ } else { | ||
let input = update; | ||
let idColumn = this._modelClass.idColumn; | ||
@@ -1816,3 +1843,9 @@ if (update instanceof InsertionOrUpdate) { | ||
// We never want to update the identifier. | ||
delete input[this._modelClass.idColumn]; | ||
if (_.isArray(idColumn)) { | ||
_.each(idColumn, col => { | ||
delete input[col] | ||
}); | ||
} else { | ||
delete input[idColumn]; | ||
} | ||
@@ -1897,3 +1930,3 @@ return super.update(input); | ||
* | ||
* @param {number|string} id | ||
* @param {number|string|Array.<number|string>} id | ||
* The identifier of the object to update. | ||
@@ -1909,3 +1942,3 @@ * | ||
.$$updateWithOptions(modelOrObject, 'patch', {patch: true}, id) | ||
.where(this._modelClass.getFullIdColumn(), id); | ||
.whereComposite(this._modelClass.getFullIdColumn(), id); | ||
} | ||
@@ -1946,2 +1979,22 @@ | ||
/** | ||
* Delete a model by id. | ||
* | ||
* ```js | ||
* Person.query().deleteById(1); | ||
* ``` | ||
* | ||
* Composite key: | ||
* | ||
* ```js | ||
* Person.query().deleteById([1, '2', 10]); | ||
* ``` | ||
* | ||
* @param {*|Array.<*>} id | ||
* @returns {QueryBuilder} | ||
*/ | ||
deleteById(id) { | ||
return this.delete().whereComposite(this._modelClass.getFullIdColumn(), id); | ||
} | ||
/** | ||
* @ignore | ||
@@ -1975,3 +2028,33 @@ * @returns {QueryBuilder} | ||
* | ||
* @param {number|string|Array.<number|string>} ids | ||
* Composite key: | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .where('id', 123) | ||
* .first() | ||
* .then(function (person) { | ||
* return person.$relatedQuery('movies').relate([50, 20, 10]); | ||
* }) | ||
* .then(function () { | ||
* console.log('movie 50 is now related to person 123 through `movies` relation'); | ||
* }); | ||
* ``` | ||
* | ||
* Multiple models with composite key (postgres only): | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .where('id', 123) | ||
* .first() | ||
* .then(function (person) { | ||
* return person.$relatedQuery('movies').relate([[50, 20, 10], [24, 46, 12]]); | ||
* }) | ||
* .then(function () { | ||
* console.log('movie 50 is now related to person 123 through `movies` relation'); | ||
* }); | ||
* ``` | ||
* | ||
* @param {number|string|Array.<number|string>|Array.<Array.<number|string>>} ids | ||
* @returns {QueryBuilder} | ||
@@ -1981,9 +2064,3 @@ */ | ||
relate(ids) { | ||
let inputArray = _.isArray(ids) ? ids : [ids]; | ||
let maybeIds = this.$$callWriteMethodImpl('relate', [inputArray, this]); | ||
if (_.isArray(maybeIds)) { | ||
ids = maybeIds; | ||
} | ||
this.$$callWriteMethodImpl('relate', [ids, this]); | ||
return this.runAfterModelCreate(() => ids); | ||
@@ -2017,7 +2094,3 @@ } | ||
this.$$callWriteMethodImpl('unrelate', [this]); | ||
this.runAfterModelCreate(function () { | ||
return {}; | ||
}); | ||
this.runAfterModelCreate(() => { return {}; }); | ||
return this; | ||
@@ -2024,0 +2097,0 @@ } |
import _ from 'lodash'; | ||
import jsonFieldExpressionParser from './parsers/jsonFieldExpressionParser'; | ||
import InsertionOrUpdate from './InsertionOrUpdate'; | ||
import utils from '../utils'; | ||
import {inherits} from '../utils/classUtils'; | ||
import {isKnexQueryBuilder, overwriteForDatabase} from '../utils/dbUtils'; | ||
@@ -15,2 +16,3 @@ /** | ||
*/ | ||
@overwriteForDatabase() | ||
export default class QueryBuilderBase { | ||
@@ -28,6 +30,6 @@ | ||
* @param {function=} subclassConstructor | ||
* @return {QueryBuilderBase} | ||
* @return {function} | ||
*/ | ||
static extend(subclassConstructor) { | ||
utils.inherits(subclassConstructor, this); | ||
inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
@@ -49,2 +51,11 @@ } | ||
/** | ||
* Returns the knex connection passed to the constructor. | ||
* | ||
* @ignore | ||
*/ | ||
knex() { | ||
return this._knex; | ||
} | ||
/** | ||
* Sets/gets the query full internal context. | ||
@@ -248,3 +259,2 @@ * | ||
/** | ||
@@ -257,3 +267,2 @@ * See <a href="http://knexjs.org">knex documentation</a> | ||
/** | ||
@@ -773,14 +782,14 @@ * See <a href="http://knexjs.org">knex documentation</a> | ||
let formatter = this._knex.client.formatter(); | ||
formatter.operator(op); | ||
let colsIsArray = _.isArray(cols); | ||
let valuesIsArray = _.isArray(values); | ||
if (_.isString(cols) && !_.isArray(values)) { | ||
if (!colsIsArray && !valuesIsArray) { | ||
return this.where(cols, op, values); | ||
} else if (_.isArray(cols) && cols.length === 1 && !_.isArray(values)) { | ||
} else if (colsIsArray && cols.length === 1 && !valuesIsArray) { | ||
return this.where(cols[0], op, values); | ||
} else if (_.isArray(cols) && _.isArray(values) && cols.length === values.length) { | ||
} else if (colsIsArray && valuesIsArray && cols.length === values.length) { | ||
_.each(cols, (col, idx) => this.where(col, op, values[idx])); | ||
return this; | ||
} else { | ||
throw new Error('cols and values must both be either scalars or arrays. Arrays must have the same length.'); | ||
throw new Error('both cols and values must have same dimensions'); | ||
} | ||
@@ -811,2 +820,5 @@ } | ||
*/ | ||
@overwriteForDatabase({ | ||
sqlite3: 'whereInComposite_sqlite3' | ||
}) | ||
whereInComposite(columns, values) { | ||
@@ -816,31 +828,47 @@ let isCompositeKey = _.isArray(columns) && columns.length > 1; | ||
if (isCompositeKey) { | ||
if (utils.isSqlite(this._knex)) { | ||
if (!_.isArray(values)) { | ||
// If the `values` is not an array of values but a function or a subquery | ||
// we have no way to implement this method. | ||
throw new Error('sqlite doesn\'t support multi-column where in clauses'); | ||
} | ||
if (_.isArray(values)) { | ||
return this.whereIn(columns, values); | ||
} else { | ||
// Because of a bug in knex, we need to build the where-in query from pieces | ||
// if the value is a subquery. | ||
let formatter = this._knex.client.formatter(); | ||
let sql = '(' + _.map(columns, col => formatter.wrap(col)).join() + ')'; | ||
return this.whereIn(this._knex.raw(sql), values); | ||
} | ||
} else { | ||
let col = _.isString(columns) ? columns : columns[0]; | ||
// Sqlite doesn't support the `where in` syntax for multiple columns but | ||
// we can emulate it using grouped `or` clauses. | ||
return this.where(builder => { | ||
_.each(values, (val) => { | ||
builder.orWhere(builder => { | ||
_.each(columns, (col, idx) => { | ||
builder.andWhere(col, val[idx]); | ||
}); | ||
if (_.isArray(values)) { | ||
values = _.compact(_.flatten(values)); | ||
} | ||
// For non-composite keys we can use the normal whereIn. | ||
return this.whereIn(col, values); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
whereInComposite_sqlite3(columns, values) { | ||
let isCompositeKey = _.isArray(columns) && columns.length > 1; | ||
if (isCompositeKey) { | ||
if (!_.isArray(values)) { | ||
// If the `values` is not an array of values but a function or a subquery | ||
// we have no way to implement this method. | ||
throw new Error('sqlite doesn\'t support multi-column where in clauses'); | ||
} | ||
// Sqlite doesn't support the `where in` syntax for multiple columns but | ||
// we can emulate it using grouped `or` clauses. | ||
return this.where(builder => { | ||
_.each(values, (val) => { | ||
builder.orWhere(builder => { | ||
_.each(columns, (col, idx) => { | ||
builder.andWhere(col, val[idx]); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
if (_.isArray(values)) { | ||
return this.whereIn(columns, values); | ||
} else { | ||
// Because of a bug in knex, we need to build the where-in query from pieces | ||
// if the value is a subquery. | ||
let formatter = this._knex.client.formatter(); | ||
let sql = '(' + _.map(columns, col => formatter.wrap(col)).join() + ')'; | ||
return this.whereIn(this._knex.raw(sql), values); | ||
} | ||
} | ||
}); | ||
} else { | ||
@@ -1284,3 +1312,3 @@ let col = _.isString(columns) ? columns : columns[0]; | ||
return function () { | ||
if (utils.isKnexQueryBuilder(this)) { | ||
if (isKnexQueryBuilder(this)) { | ||
let context = query.internalContext(); | ||
@@ -1287,0 +1315,0 @@ let builder = new QueryBuilderBase(query._knex).internalContext(context); |
import _ from 'lodash'; | ||
import utils from '../utils'; | ||
import Relation from './Relation'; | ||
import inheritModel from '../model/inheritModel'; | ||
const ownerJoinColumnAlias = 'objectiontmpjoin';; | ||
import {overwriteForDatabase} from '../utils/dbUtils'; | ||
import {isSubclassOf} from '../utils/classUtils'; | ||
import {memoize} from '../utils/decorators'; | ||
const ownerJoinColumnAliasPrefix = 'objectiontmpjoin'; | ||
const sqliteBuiltInRowId = '_rowid_'; | ||
/** | ||
@@ -11,2 +15,3 @@ * @ignore | ||
*/ | ||
@overwriteForDatabase() | ||
export default class ManyToManyRelation extends Relation { | ||
@@ -27,3 +32,3 @@ | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -35,3 +40,3 @@ this.joinTableOwnerCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -43,3 +48,3 @@ this.joinTableOwnerProp = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -51,3 +56,3 @@ this.joinTableRelatedCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -72,30 +77,29 @@ this.joinTableRelatedProp = null; | ||
setMapping(mapping) { | ||
let retVal = super.setMapping(mapping); | ||
// Avoid require loop and import here. | ||
let Model = require(__dirname + '/../model/Model').default; | ||
let retVal = Relation.prototype.setMapping.call(this, mapping); | ||
let errorPrefix = this.ownerModelClass.name + '.relationMappings.' + this.name; | ||
if (!_.isObject(mapping.join.through)) { | ||
throw new Error(errorPrefix + '.join must have the `through` that describes the join table.'); | ||
this.throwError('join must have the `through` that describes the join table.'); | ||
} | ||
if (!_.isString(mapping.join.through.from) || !_.isString(mapping.join.through.to)) { | ||
throw new Error(errorPrefix + '.join.through must be an object that describes the join table. For example: {from: \'JoinTable.someId\', to: \'JoinTable.someOtherId\'}'); | ||
if (!mapping.join.through.from || !mapping.join.through.to) { | ||
this.throwError('join.through must be an object that describes the join table. For example: {from: "JoinTable.someId", to: "JoinTable.someOtherId"}'); | ||
} | ||
let joinFrom = Relation.parseColumn(mapping.join.from); | ||
let joinTableFrom = Relation.parseColumn(mapping.join.through.from); | ||
let joinTableTo = Relation.parseColumn(mapping.join.through.to); | ||
let joinFrom = this.parseReference(mapping.join.from); | ||
let joinTableFrom = this.parseReference(mapping.join.through.from); | ||
let joinTableTo = this.parseReference(mapping.join.through.to); | ||
if (!joinTableFrom.table || !joinTableFrom.name) { | ||
throw new Error(errorPrefix + '.join.through.from must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
if (!joinTableFrom.table || _.isEmpty(joinTableFrom.columns)) { | ||
this.throwError('join.through.from must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'); | ||
} | ||
if (!joinTableTo.table || !joinTableTo.name) { | ||
throw new Error(errorPrefix + '.join.through.to must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
if (!joinTableTo.table || _.isEmpty(joinTableTo.columns)) { | ||
this.throwError('join.through.to must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'); | ||
} | ||
if (joinTableFrom.table !== joinTableTo.table) { | ||
throw new Error(errorPrefix + '.join.through `from` and `to` must point to the same join table.'); | ||
this.throwError('join.through `from` and `to` must point to the same join table.'); | ||
} | ||
@@ -106,12 +110,12 @@ | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
this.joinTableOwnerCol = joinTableFrom.name; | ||
this.joinTableRelatedCol = joinTableTo.name; | ||
this.joinTableOwnerCol = joinTableFrom.columns; | ||
this.joinTableRelatedCol = joinTableTo.columns; | ||
} else { | ||
this.joinTableRelatedCol = joinTableFrom.name; | ||
this.joinTableOwnerCol = joinTableTo.name; | ||
this.joinTableRelatedCol = joinTableFrom.columns; | ||
this.joinTableOwnerCol = joinTableTo.columns; | ||
} | ||
if (mapping.join.through.modelClass) { | ||
if (!utils.isSubclassOf(mapping.join.through.modelClass, Model)) { | ||
throw new Error('Join table model class is not a subclass of Model'); | ||
if (!isSubclassOf(mapping.join.through.modelClass, Model)) { | ||
this.throwError('Join table model class is not a subclass of Model'); | ||
} | ||
@@ -128,4 +132,4 @@ | ||
this.joinTableOwnerProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableOwnerCol); | ||
this.joinTableRelatedProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableRelatedCol); | ||
this.joinTableOwnerProp = this.propertyName(this.joinTableOwnerCol, this.joinTableModelClass); | ||
this.joinTableRelatedProp = this.propertyName(this.joinTableRelatedCol, this.joinTableModelClass); | ||
@@ -138,8 +142,9 @@ return retVal; | ||
* | ||
* For example: `Person_Movie.actorId`. | ||
* For example: [`Person_Movie.actorId`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@memoize | ||
fullJoinTableOwnerCol() { | ||
return this.joinTable + '.' + this.joinTableOwnerCol; | ||
return _.map(this.joinTableOwnerCol, col => this.joinTable + '.' + col); | ||
} | ||
@@ -150,8 +155,9 @@ | ||
* | ||
* For example: `Person_Movie.movieId`. | ||
* For example: [`Person_Movie.movieId`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@memoize | ||
fullJoinTableRelatedCol() { | ||
return this.joinTable + '.' + this.joinTableRelatedCol; | ||
return _.map(this.joinTableRelatedCol, col => this.joinTable + '.' + col); | ||
} | ||
@@ -193,5 +199,3 @@ | ||
let bound = super.bindKnex(knex); | ||
bound.joinTableModelClass = this.joinTableModelClass.bindKnex(knex); | ||
return bound; | ||
@@ -205,12 +209,21 @@ } | ||
*/ | ||
findQuery(builder, ownerCol, isColumnRef) { | ||
builder.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()); | ||
findQuery(builder, ownerIds, isColumnRef) { | ||
let fullRelatedCol = this.fullRelatedCol(); | ||
builder.join(this.joinTable, join => { | ||
_.each(this.fullJoinTableRelatedCol(), (joinTableRelatedCol, idx) => { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullJoinTableOwnerCol(), ownerCol); | ||
_.each(this.fullJoinTableOwnerCol(), (joinTableOwnerCol, idx) => { | ||
builder.whereRef(joinTableOwnerCol, ownerIds[idx]); | ||
}); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullJoinTableOwnerCol(), ownerCol); | ||
if (_(ownerIds).flatten().all(id => _.isNull(id) || _.isUndefined(id))) { | ||
// Nothing to fetch. | ||
builder.resolve([]); | ||
} else { | ||
builder.where(this.fullJoinTableOwnerCol(), ownerCol); | ||
builder.whereInComposite(this.fullJoinTableOwnerCol(), ownerIds); | ||
} | ||
@@ -239,11 +252,19 @@ } | ||
let joinTableOwnerCol = joinTableAlias + '.' + this.joinTableOwnerCol; | ||
let joinTableRelatedCol = joinTableAlias + '.' + this.joinTableRelatedCol; | ||
let joinTableOwnerCol = _.map(this.joinTableOwnerCol, col => joinTableAlias + '.' + col); | ||
let joinTableRelatedCol = _.map(this.joinTableRelatedCol, col => joinTableAlias + '.' + col); | ||
let ownerCol = this.fullOwnerCol(); | ||
let relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
let relatedCol = _.map(this.relatedCol, col => relatedTableAlias + '.' + col); | ||
return builder | ||
[joinMethod](joinTableAsAlias, joinTableOwnerCol, ownerCol) | ||
[joinMethod](relatedTableAsAlias, joinTableRelatedCol, relatedCol) | ||
[joinMethod](joinTableAsAlias, join => { | ||
_.each(joinTableOwnerCol, (joinTableOwnerCol, idx) => { | ||
join.on(joinTableOwnerCol, ownerCol[idx]); | ||
}); | ||
}) | ||
[joinMethod](relatedTableAsAlias, join => { | ||
_.each(joinTableRelatedCol, (joinTableRelatedCol, idx) => { | ||
join.on(joinTableRelatedCol, relatedCol[idx]); | ||
}); | ||
}) | ||
.call(this.filter); | ||
@@ -257,5 +278,10 @@ } | ||
find(builder, owners) { | ||
const ownerJoinColumnAlias = _.times(this.joinTableOwnerCol.length, idx => ownerJoinColumnAliasPrefix + idx); | ||
const ownerJoinPropertyAlias = _.map(ownerJoinColumnAlias, alias => this.relatedModelClass.columnNameToPropertyName(alias)); | ||
builder.onBuild(builder => { | ||
let ownerIds = _.pluck(owners, this.ownerProp); | ||
let ownerJoinColumn = this.fullJoinTableOwnerCol(); | ||
let ids = _(owners) | ||
.map(owner => owner.$values(this.ownerProp)) | ||
.unique(id => id.join()) | ||
.value(); | ||
@@ -268,17 +294,23 @@ if (!builder.has(/select/)) { | ||
this.findQuery(builder, ownerIds).select(ownerJoinColumn + ' as ' + ownerJoinColumnAlias); | ||
this.findQuery(builder, ids); | ||
// We must select the owner join columns so that we know for which owner model the related | ||
// models belong to after the requests. | ||
_.each(this.fullJoinTableOwnerCol(), (fullJoinTableOwnerCol, idx) => { | ||
builder.select(fullJoinTableOwnerCol + ' as ' + ownerJoinColumnAlias[idx]); | ||
}); | ||
}); | ||
builder.runAfterModelCreate(related => { | ||
// The ownerJoinColumnAlias column name may have been changed by the `$parseDatabaseJson` | ||
// method of the related model class. We need to do the same conversion here. | ||
let ownerJoinPropAlias = this.relatedModelClass.columnNameToPropertyName(ownerJoinColumnAlias); | ||
let relatedByOwnerId = _.groupBy(related, ownerJoinPropAlias); | ||
let relatedByOwnerId = _.groupBy(related, related => related.$values(ownerJoinPropertyAlias)); | ||
_.each(owners,owner => { | ||
owner[this.name] = relatedByOwnerId[owner[this.ownerProp]] || []; | ||
_.each(owners, owner => { | ||
owner[this.name] = relatedByOwnerId[owner.$values(this.ownerProp)] || []; | ||
}); | ||
// Delete the temporary join aliases. | ||
_.each(related, rel => { | ||
delete rel[ownerJoinPropAlias]; | ||
_.each(ownerJoinPropertyAlias, alias => { | ||
delete rel[alias]; | ||
}); | ||
}); | ||
@@ -300,4 +332,4 @@ | ||
builder.runAfterModelCreate(related => { | ||
let ownerId = owner[this.ownerProp]; | ||
let relatedIds = _.pluck(related, this.relatedProp); | ||
let ownerId = owner.$values(this.ownerProp); | ||
let relatedIds = _.map(related, related => related.$values(this.relatedProp)); | ||
let joinModels = this._createJoinModels(ownerId, relatedIds); | ||
@@ -309,3 +341,3 @@ | ||
return this.joinTableModelClass | ||
.bindKnex(builder.modelClass().knex()) | ||
.bindKnex(builder.knex()) | ||
.query() | ||
@@ -324,8 +356,3 @@ .childQueryOf(builder) | ||
builder.onBuild(builder => { | ||
let idSelectQuery = this._makeFindIdQuery(builder, owner[this.ownerProp]); | ||
builder | ||
.$$update(update) | ||
.whereIn(this.relatedModelClass.getFullIdColumn(), idSelectQuery) | ||
.call(this.filter); | ||
this._selectForModify(builder, owner).$$update(update).call(this.filter); | ||
}); | ||
@@ -338,18 +365,5 @@ } | ||
*/ | ||
patch(builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
delete(builder, owner) { | ||
builder.onBuild(builder => { | ||
let idSelectQuery = this._makeFindIdQuery(builder, owner[this.ownerProp]); | ||
builder | ||
.$$delete() | ||
.whereIn(this.relatedModelClass.getFullIdColumn(), idSelectQuery) | ||
.call(this.filter); | ||
this._selectForModify(builder, owner).$$delete().call(this.filter); | ||
}); | ||
@@ -363,7 +377,9 @@ } | ||
relate(builder, owner, ids) { | ||
builder.setQueryExecutor(() => { | ||
let joinModels = this._createJoinModels(owner[this.ownerProp], ids); | ||
ids = this.normalizeId(ids, this.relatedProp.length); | ||
builder.setQueryExecutor(builder => { | ||
let joinModels = this._createJoinModels(owner.$values(this.ownerProp), ids); | ||
return this.joinTableModelClass | ||
.bindKnex(this.ownerModelClass.knex()) | ||
.bindKnex(builder.knex()) | ||
.query() | ||
@@ -380,5 +396,8 @@ .childQueryOf(builder) | ||
*/ | ||
@overwriteForDatabase({ | ||
sqlite3: 'unrelate_sqlite3' | ||
}) | ||
unrelate(builder, owner) { | ||
builder.setQueryExecutor(builder => { | ||
let idSelectQuery = this.relatedModelClass | ||
let selectRelatedColQuery = this.relatedModelClass | ||
.query() | ||
@@ -391,8 +410,8 @@ .childQueryOf(builder) | ||
return this.joinTableModelClass | ||
.bindKnex(this.ownerModelClass.knex()) | ||
.bindKnex(builder.knex()) | ||
.query() | ||
.childQueryOf(builder) | ||
.delete() | ||
.where(this.fullJoinTableOwnerCol(), owner[this.ownerProp]) | ||
.whereIn(this.fullJoinTableRelatedCol(), idSelectQuery) | ||
.whereComposite(this.fullJoinTableOwnerCol(), owner.$values(this.ownerProp)) | ||
.whereInComposite(this.fullJoinTableRelatedCol(), selectRelatedColQuery) | ||
.runAfter(_.constant({})); | ||
@@ -403,16 +422,93 @@ }); | ||
/** | ||
* Special unrelate implementation for sqlite3. sqlite3 doesn't support multi-value | ||
* where-in clauses. We need to use the built-in _rowid_ instead. | ||
* | ||
* @private | ||
*/ | ||
_makeFindIdQuery(builder, ownerId) { | ||
return this.joinTableModelClass | ||
.bindKnex(this.ownerModelClass.knex()) | ||
unrelate_sqlite3(builder, owner) { | ||
builder.setQueryExecutor(builder => { | ||
let joinTableAlias = this.joinTableAlias(); | ||
let joinTableAsAlias = this.joinTable + ' as ' + joinTableAlias; | ||
let joinTableAliasRowId = joinTableAlias + '.' + sqliteBuiltInRowId; | ||
let joinTableRowId = this.joinTable + '.' + sqliteBuiltInRowId; | ||
let ownerId = owner.$values(this.ownerProp); | ||
let fullRelatedCol = this.fullRelatedCol(); | ||
let selectRelatedQuery = this.relatedModelClass | ||
.query() | ||
.childQueryOf(builder) | ||
.copyFrom(builder, /where/i) | ||
.select(joinTableAliasRowId) | ||
.call(this.filter) | ||
.whereComposite(this.fullJoinTableOwnerCol(), ownerId) | ||
.join(joinTableAsAlias, join => { | ||
_.each(this.fullJoinTableRelatedCol(), (joinTableRelatedCol, idx) => { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
return this.joinTableModelClass | ||
.bindKnex(builder.knex()) | ||
.query() | ||
.childQueryOf(builder) | ||
.delete() | ||
.whereIn(joinTableRowId, selectRelatedQuery) | ||
.runAfter(_.constant({})); | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
@overwriteForDatabase({ | ||
sqlite3: '_selectForModify_sqlite3' | ||
}) | ||
_selectForModify(builder, owner) { | ||
let ownerId = owner.$values(this.ownerProp); | ||
let idQuery = this.joinTableModelClass | ||
.bindKnex(builder.knex()) | ||
.query() | ||
.childQueryOf(builder) | ||
.select(this.fullJoinTableRelatedCol()) | ||
.where(this.fullJoinTableOwnerCol(), ownerId); | ||
.whereComposite(this.fullJoinTableOwnerCol(), ownerId); | ||
return builder.whereInComposite(this.fullRelatedCol(), idQuery); | ||
} | ||
/** | ||
* Special _selectForModify implementation for sqlite3. sqlite3 doesn't support multi-value | ||
* where-in clauses. We need to use the built-in _rowid_ instead. | ||
* | ||
* @private | ||
*/ | ||
_selectForModify_sqlite3(builder, owner) { | ||
let relatedTable = this.relatedModelClass.tableName; | ||
let relatedTableAlias = this.relatedTableAlias(); | ||
let relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
let relatedTableAliasRowId = relatedTableAlias + '.' + sqliteBuiltInRowId; | ||
let relatedTableRowId = relatedTable + '.' + sqliteBuiltInRowId; | ||
let fullRelatedCol = this.fullRelatedCol(); | ||
let ownerId = owner.$values(this.ownerProp); | ||
let selectRelatedQuery = this.joinTableModelClass | ||
.bindKnex(builder.knex()) | ||
.query() | ||
.childQueryOf(builder) | ||
.select(relatedTableAliasRowId) | ||
.whereComposite(this.fullJoinTableOwnerCol(), ownerId) | ||
.join(relatedTableAsAlias, join => { | ||
_.each(this.fullJoinTableRelatedCol(), (joinTableRelatedCol, idx) => { | ||
join.on(joinTableRelatedCol, fullRelatedCol[idx]); | ||
}); | ||
}); | ||
return builder.whereInComposite(relatedTableRowId, selectRelatedQuery); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_createJoinModels(ownerId, relatedIds) { | ||
@@ -422,5 +518,10 @@ return _.map(relatedIds, relatedId => { | ||
joinModel[this.joinTableOwnerProp] = ownerId; | ||
joinModel[this.joinTableRelatedProp] = relatedId; | ||
_.each(this.joinTableOwnerProp, (joinTableOwnerProp, idx) => { | ||
joinModel[joinTableOwnerProp] = ownerId[idx]; | ||
}); | ||
_.each(this.joinTableRelatedProp, (joinTableRelatedProp, idx) => { | ||
joinModel[joinTableRelatedProp] = relatedId[idx]; | ||
}); | ||
return joinModel; | ||
@@ -427,0 +528,0 @@ }); |
@@ -9,57 +9,22 @@ import _ from 'lodash'; | ||
export default class OneToManyRelation extends Relation { | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
findQuery(builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
let relatedTable = this.relatedModelClass.tableName; | ||
let relatedTableAlias = this.relatedTableAlias(); | ||
let relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
let relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
return builder | ||
[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
find(builder, owners) { | ||
let ownerIds = _.unique(_.pluck(owners, this.ownerProp)); | ||
builder.onBuild(builder => { | ||
let ids = _(owners) | ||
.map(owner => owner.$values(this.ownerProp)) | ||
.unique(id => id.join()) | ||
.value(); | ||
builder.onBuild(builder => { | ||
this.findQuery(builder, ownerIds); | ||
this.findQuery(builder, ids); | ||
}); | ||
builder.runAfterModelCreate(related => { | ||
var relatedByOwnerId = _.groupBy(related, this.relatedProp); | ||
var relatedByOwnerId = _.groupBy(related, related => related.$values(this.relatedProp)); | ||
_.each(owners, owner => { | ||
owner[this.name] = relatedByOwnerId[owner[this.ownerProp]] || []; | ||
let ownerId = owner.$values(this.ownerProp); | ||
owner[this.name] = relatedByOwnerId[ownerId] || []; | ||
}); | ||
@@ -77,3 +42,5 @@ | ||
_.each(insertion.models(), insert => { | ||
insert[this.relatedProp] = owner[this.ownerProp]; | ||
_.each(this.relatedProp, (relatedProp, idx) => { | ||
insert[relatedProp] = owner[this.ownerProp[idx]]; | ||
}); | ||
}); | ||
@@ -95,36 +62,12 @@ | ||
*/ | ||
update(builder, owner, update) { | ||
builder.onBuild(builder => { | ||
this.findQuery(builder, owner[this.ownerProp]); | ||
builder.$$update(update); | ||
}); | ||
} | ||
relate(builder, owner, ids) { | ||
ids = this.normalizeId(ids, this.relatedModelClass.getIdColumnDimension()); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
patch(builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
} | ||
builder.setQueryExecutor(builder => { | ||
var patch = {}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
delete(builder, owner) { | ||
builder.onBuild(builder => { | ||
this.findQuery(builder, owner[this.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
_.each(this.relatedProp, (relatedProp, idx) => { | ||
patch[relatedProp] = owner[this.ownerProp[idx]]; | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
relate(builder, owner, ids) { | ||
builder.setQueryExecutor(() => { | ||
var patch = relatePatch(this, owner[this.ownerProp]); | ||
return this.relatedModelClass | ||
@@ -135,3 +78,3 @@ .query() | ||
.copyFrom(builder, /where/i) | ||
.whereIn(this.relatedModelClass.getFullIdColumn(), ids) | ||
.whereInComposite(this.relatedModelClass.getFullIdColumn(), ids) | ||
.call(this.filter) | ||
@@ -148,4 +91,8 @@ .runAfter(_.constant({})); | ||
builder.setQueryExecutor(builder => { | ||
var patch = relatePatch(this, null); | ||
var patch = {}; | ||
_.each(this.relatedProp, relatedProp => { | ||
patch[relatedProp] = null; | ||
}); | ||
return this.relatedModelClass | ||
@@ -156,3 +103,3 @@ .query() | ||
.copyFrom(builder, /where/i) | ||
.where(this.fullRelatedCol(), owner[this.ownerProp]) | ||
.whereComposite(this.fullRelatedCol(), owner.$values(this.ownerProp)) | ||
.call(this.filter) | ||
@@ -163,10 +110,1 @@ .runAfter(_.constant({})); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function relatePatch(relation, value) { | ||
var patch = {}; | ||
patch[relation.relatedProp] = value; | ||
return patch; | ||
} |
@@ -12,52 +12,19 @@ import _ from 'lodash'; | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
findQuery(builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), _.compact(ownerCol)); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol) | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
let relatedTable = this.relatedModelClass.tableName; | ||
let relatedTableAlias = this.relatedTableAlias(); | ||
let relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
let relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
return builder | ||
[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
find(builder, owners) { | ||
builder.onBuild(builder => { | ||
let relatedIds = _.unique(_.compact(_.pluck(owners, this.ownerProp))); | ||
this._makeFindQuery(builder, relatedIds); | ||
let ids = _(owners) | ||
.map(owner => owner.$values(this.ownerProp)) | ||
.unique(id => id.join()) | ||
.value(); | ||
this.findQuery(builder, ids); | ||
}); | ||
builder.runAfterModelCreate(related => { | ||
let relatedById = _.indexBy(related, this.relatedProp); | ||
let relatedByOwnerId = _.indexBy(related, related => related.$values(this.relatedProp)); | ||
_.each(owners, owner => { | ||
owner[this.name] = relatedById[owner[this.ownerProp]] || null; | ||
let ownerId = owner.$values(this.ownerProp); | ||
owner[this.name] = relatedByOwnerId[ownerId] || null; | ||
}); | ||
@@ -75,3 +42,3 @@ | ||
if (insertion.models().length > 1) { | ||
throw new Error('can only insert one model to a OneToOneRelation'); | ||
this.throwError('can only insert one model to a OneToOneRelation'); | ||
} | ||
@@ -84,8 +51,11 @@ | ||
builder.runAfterModelCreate(inserted => { | ||
owner[this.ownerProp] = inserted[0][this.relatedProp]; | ||
owner[this.name] = inserted[0]; | ||
let patch = {}; | ||
patch[this.ownerProp] = inserted[0][this.relatedProp]; | ||
_.each(this.ownerProp, (ownerProp, idx) => { | ||
let relatedValue = inserted[0][this.relatedProp[idx]]; | ||
owner[ownerProp] = relatedValue; | ||
patch[ownerProp] = relatedValue; | ||
}); | ||
return this.ownerModelClass | ||
@@ -95,3 +65,3 @@ .query() | ||
.patch(patch) | ||
.where(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.whereComposite(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.return(inserted); | ||
@@ -105,35 +75,7 @@ }); | ||
*/ | ||
update(builder, owner, update) { | ||
builder.onBuild(builder => { | ||
this._makeFindQuery(builder, owner[this.ownerProp]); | ||
builder.$$update(update); | ||
}); | ||
} | ||
relate(builder, owner, ids) { | ||
ids = this.normalizeId(ids, this.relatedProp.length); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
patch(builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
delete(builder, owner) { | ||
builder.onBuild(builder => { | ||
this._makeFindQuery(builder, owner[this.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
relate(builder, owner, ids) { | ||
if (ids.length > 1) { | ||
throw new Error('can only relate one model to a OneToOneRelation'); | ||
this.throwError('can only relate one model to a OneToOneRelation'); | ||
} | ||
@@ -144,4 +86,6 @@ | ||
patch[this.ownerProp] = ids[0]; | ||
owner[this.ownerProp] = ids[0]; | ||
_.each(this.ownerProp, (prop, idx) => { | ||
patch[prop] = ids[0][idx]; | ||
owner[prop] = ids[0][idx]; | ||
}); | ||
@@ -153,3 +97,3 @@ return this.ownerModelClass | ||
.copyFrom(builder, /where/i) | ||
.where(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.whereComposite(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.runAfterModelCreate(_.constant({})); | ||
@@ -167,4 +111,6 @@ }); | ||
patch[this.ownerProp] = null; | ||
owner[this.ownerProp] = null; | ||
_.each(this.ownerProp, prop => { | ||
patch[prop] = null; | ||
owner[prop] = null; | ||
}); | ||
@@ -176,19 +122,8 @@ return this.ownerModelClass | ||
.copyFrom(builder, /where/i) | ||
.where(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.whereComposite(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.runAfterModelCreate(_.constant({})); | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_makeFindQuery(builder, relatedIds) { | ||
if ((_.isArray(relatedIds) && _.isEmpty(relatedIds)) || !relatedIds) { | ||
return builder.resolve([]); | ||
} else { | ||
return this.findQuery(builder, relatedIds); | ||
} | ||
} | ||
} | ||
import _ from 'lodash'; | ||
import utils from '../utils'; | ||
import {inherits, isSubclassOf} from '../utils/classUtils'; | ||
import {memoize} from '../utils/decorators'; | ||
import QueryBuilder from '../queryBuilder/QueryBuilder'; | ||
@@ -30,11 +31,14 @@ | ||
* | ||
* @property {string} from | ||
* @property {string|Array.<string>} from | ||
* The relation column in the owner table. Must be given with the table name. | ||
* For example `Person.id`. Note that neither this nor `to` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* For example `Person.id`. Composite key can be specified using an array of | ||
* columns e.g. `['Person.a', 'Person.b']`. Note that neither this nor `to` | ||
* need to be foreign keys or primary keys. You can join any column to | ||
* any column. | ||
* | ||
* @property {string} to | ||
* @property {string|Array.<string>} to | ||
* The relation column in the related table. Must be given with the table name. | ||
* For example `Movie.id`. Note that neither this nor `from` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* For example `Movie.id`. Composite key can be specified using an array of | ||
* columns e.g. `['Movie.a', 'Movie.b']`. Note that neither this nor `from` | ||
* need to be foreign keys or primary keys. You can join any column to any column. | ||
* | ||
@@ -48,9 +52,11 @@ * @property {Object} through | ||
* | ||
* @property {string} through.from | ||
* @property {string|Array.<string>} through.from | ||
* The column that is joined to `from` property of the `RelationJoin`. For example | ||
* `Person_Movie.actorId` where `Person_Movie` is the join table. | ||
* `Person_Movie.actorId` where `Person_Movie` is the join table. Composite key can | ||
* be specified using an array of columns e.g. `['Person_Movie.a', 'Person_Movie.b']`. | ||
* | ||
* @property {string} through.to | ||
* @property {string|Array.<string>} through.to | ||
* The column that is joined to `to` property of the `RelationJoin`. For example | ||
* `Person_Movie.movieId` where `Person_Movie` is the join table. | ||
* `Person_Movie.movieId` where `Person_Movie` is the join table. Composite key can | ||
* be specified using an array of columns e.g. `['Person_Movie.a', 'Person_Movie.b']`. | ||
*/ | ||
@@ -121,3 +127,3 @@ | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -129,3 +135,3 @@ this.ownerCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -137,3 +143,3 @@ this.ownerProp = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -145,3 +151,3 @@ this.relatedCol = null; | ||
* | ||
* @type {string} | ||
* @type {Array.<string>} | ||
*/ | ||
@@ -165,3 +171,3 @@ this.relatedProp = null; | ||
static extend(subclassConstructor) { | ||
utils.inherits(subclassConstructor, this); | ||
inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
@@ -179,10 +185,8 @@ } | ||
if (!utils.isSubclassOf(this.ownerModelClass, Model)) { | ||
throw new Error('Relation\'s owner is not a subclass of Model'); | ||
if (!isSubclassOf(this.ownerModelClass, Model)) { | ||
this.throwError('Relation\'s owner is not a subclass of Model'); | ||
} | ||
let errorPrefix = `${this.ownerModelClass.name}.relationMappings.${this.name}`; | ||
if (!mapping.modelClass) { | ||
throw new Error(errorPrefix + '.modelClass is not defined'); | ||
this.throwError('modelClass is not defined'); | ||
} | ||
@@ -194,10 +198,10 @@ | ||
let relatedModelClassModule = require(mapping.modelClass); | ||
this.relatedModelClass = utils.isSubclassOf(relatedModelClassModule.default, Model) ? | ||
this.relatedModelClass = isSubclassOf(relatedModelClassModule.default, Model) ? | ||
relatedModelClassModule.default : relatedModelClassModule; | ||
} catch (err) { | ||
throw new Error(errorPrefix + '.modelClass is an invalid file path to a model class.'); | ||
this.throwError('modelClass is an invalid file path to a model class.'); | ||
} | ||
if (!utils.isSubclassOf(this.relatedModelClass, Model)) { | ||
throw new Error(errorPrefix + '.modelClass is a valid path to a module, but the module doesn\'t export a Model subclass.'); | ||
if (!isSubclassOf(this.relatedModelClass, Model)) { | ||
this.throwError('modelClass is a valid path to a module, but the module doesn\'t export a Model subclass.'); | ||
} | ||
@@ -207,4 +211,4 @@ } else { | ||
if (!utils.isSubclassOf(this.relatedModelClass, Model)) { | ||
throw new Error(errorPrefix + '.modelClass is not a subclass of Model or a file path to a module that exports one.'); | ||
if (!isSubclassOf(this.relatedModelClass, Model)) { | ||
this.throwError('modelClass is not a subclass of Model or a file path to a module that exports one.'); | ||
} | ||
@@ -214,11 +218,11 @@ } | ||
if (!mapping.relation) { | ||
throw new Error(errorPrefix + '.relation is not defined'); | ||
this.throwError('relation is not defined'); | ||
} | ||
if (!utils.isSubclassOf(mapping.relation, Relation)) { | ||
throw new Error(errorPrefix + '.relation is not a subclass of Relation'); | ||
if (!isSubclassOf(mapping.relation, Relation)) { | ||
this.throwError('relation is not a subclass of Relation'); | ||
} | ||
if (!mapping.join || !_.isString(mapping.join.from) || !_.isString(mapping.join.to)) { | ||
throw new Error(errorPrefix + '.join must be an object that maps the columns of the related models together. For example: {from: \'SomeTable.id\', to: \'SomeOtherTable.someModelId\'}'); | ||
if (!mapping.join || !mapping.join.from || !mapping.join.to) { | ||
this.throwError('join must be an object that maps the columns of the related models together. For example: {from: "SomeTable.id", to: "SomeOtherTable.someModelId"}'); | ||
} | ||
@@ -229,11 +233,11 @@ | ||
let joinFrom = Relation.parseColumn(mapping.join.from); | ||
let joinTo = Relation.parseColumn(mapping.join.to); | ||
let joinFrom = this.parseReference(mapping.join.from); | ||
let joinTo = this.parseReference(mapping.join.to); | ||
if (!joinFrom.table || !joinFrom.name) { | ||
throw new Error(errorPrefix + '.join.from must have format TableName.columnName. For example `SomeTable.id`.'); | ||
if (!joinFrom.table || _.isEmpty(joinFrom.columns)) { | ||
this.throwError('join.from must have format TableName.columnName. For example "SomeTable.id" or in case of composite key ["SomeTable.a", "SomeTable.b"].'); | ||
} | ||
if (!joinTo.table || !joinTo.name) { | ||
throw new Error(errorPrefix + '.join.to must have format TableName.columnName. For example `SomeTable.id`.'); | ||
if (!joinTo.table || _.isEmpty(joinTo.columns)) { | ||
this.throwError('join.to must have format TableName.columnName. For example "SomeTable.id" or in case of composite key ["SomeTable.a", "SomeTable.b"].'); | ||
} | ||
@@ -248,25 +252,33 @@ | ||
} else { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the owner model table.'); | ||
this.throwError('join: either `from` or `to` must point to the owner model table.'); | ||
} | ||
if (joinRelated.table !== this.relatedModelClass.tableName) { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the related model table.'); | ||
this.throwError('join: either `from` or `to` must point to the related model table.'); | ||
} | ||
this.ownerProp = this._propertyName(joinOwner, this.ownerModelClass); | ||
this.ownerCol = joinOwner.name; | ||
this.relatedProp = this._propertyName(joinRelated, this.relatedModelClass); | ||
this.relatedCol = joinRelated.name; | ||
this.filter = Relation.parseFilter(mapping); | ||
this.ownerCol = joinOwner.columns; | ||
this.ownerProp = this.propertyName(this.ownerCol, this.ownerModelClass); | ||
this.relatedCol = joinRelated.columns; | ||
this.relatedProp = this.propertyName(this.relatedCol, this.relatedModelClass); | ||
this.filter = this.parseFilter(mapping); | ||
} | ||
/** | ||
* Return the knex connection. | ||
*/ | ||
knex() { | ||
return this.ownerModelClass.knex(); | ||
} | ||
/** | ||
* Reference to the relation column in the owner model's table. | ||
* | ||
* For example: `Person.id`. | ||
* For example: [`Person.id`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@memoize | ||
fullOwnerCol() { | ||
return this.ownerModelClass.tableName + '.' + this.ownerCol; | ||
return _.map(this.ownerCol, col => this.ownerModelClass.tableName + '.' + col); | ||
} | ||
@@ -277,8 +289,9 @@ | ||
* | ||
* For example: `Movie.id`. | ||
* For example: [`Movie.id`]. | ||
* | ||
* @returns {string} | ||
* @returns {Array.<string>} | ||
*/ | ||
@memoize | ||
fullRelatedCol() { | ||
return this.relatedModelClass.tableName + '.' + this.relatedCol; | ||
return _.map(this.relatedCol, col => this.relatedModelClass.tableName + '.' + col); | ||
} | ||
@@ -293,2 +306,3 @@ | ||
*/ | ||
@memoize | ||
relatedTableAlias() { | ||
@@ -340,5 +354,6 @@ return this.relatedModelClass.tableName + '_rel_' + this.name; | ||
mergeModels(models1, models2) { | ||
let modelsById = Object.create(null); | ||
models1 = _.compact(models1); | ||
models2 = _.compact(models2); | ||
let modelsById = Object.create(null); | ||
@@ -353,28 +368,63 @@ _.forEach(models1, function (model) { | ||
return _.sortBy(_.values(modelsById), function (model) { | ||
return model.$id(); | ||
}) | ||
let models = _.values(modelsById); | ||
if (models.length === 0) { | ||
return []; | ||
} | ||
let modelClass = models[0].constructor; | ||
let idProperty = modelClass.getIdProperty(); | ||
if (!_.isArray(idProperty)) { | ||
idProperty = [idProperty]; | ||
} | ||
return _.sortByAll(models, idProperty); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {number|string} ownerCol | ||
* @param {boolean} isColumnRef | ||
* @param {Array.<string>|Array.<Array.<(string|number)>>} ownerIds | ||
* @param {boolean=} isColumnRef | ||
* @returns {QueryBuilder} | ||
*/ | ||
findQuery(builder, ownerCol, isColumnRef) { | ||
throw new Error('not implemented'); | ||
findQuery(builder, ownerIds, isColumnRef) { | ||
let fullRelatedCol = this.fullRelatedCol(); | ||
if (isColumnRef) { | ||
_.each(fullRelatedCol, (col, idx) => { | ||
builder.whereRef(col, ownerIds[idx]); | ||
}); | ||
} else { | ||
if (_(ownerIds).flatten().all(id => _.isNull(id) || _.isUndefined(id))) { | ||
// Nothing to fetch. | ||
builder.resolve([]); | ||
} else { | ||
builder.whereInComposite(fullRelatedCol, ownerIds); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {string} joinMethod | ||
* @param {string=} joinMethod | ||
* @returns {QueryBuilder} | ||
*/ | ||
join(builder, joinMethod) { | ||
throw new Error('not implemented'); | ||
joinMethod = joinMethod || 'join'; | ||
let relatedTable = this.relatedModelClass.tableName; | ||
let relatedTableAlias = this.relatedTableAlias(); | ||
let relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
let relatedCol = _.map(this.relatedCol, col => relatedTableAlias + '.' + col); | ||
let ownerCol = this.fullOwnerCol(); | ||
return builder | ||
[joinMethod](relatedTableAsAlias, join => { | ||
_.each(relatedCol, (relatedCol, idx) => { | ||
join.on(relatedCol, '=', ownerCol[idx]); | ||
}); | ||
}) | ||
.call(this.filter); | ||
} | ||
@@ -386,6 +436,6 @@ | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} owners | ||
* @param {Array.<Model>} owners | ||
*/ | ||
find(builder, owners) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
@@ -395,40 +445,41 @@ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} insertion | ||
*/ | ||
insert(builder, owner, insertion) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} update | ||
*/ | ||
update(builder, owner, update) { | ||
return builder; | ||
builder.onBuild(builder => { | ||
this.findQuery(builder, [owner.$values(this.ownerProp)]); | ||
builder.$$update(update); | ||
}); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
* @param {InsertionOrUpdate} patch | ||
*/ | ||
patch(builder, owner, patch) { | ||
throw new Error('not implemented'); | ||
return this.update(builder, owner, patch); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {Model} owner | ||
*/ | ||
delete(builder, owner) { | ||
throw new Error('not implemented'); | ||
builder.onBuild(builder => { | ||
this.findQuery(builder, [owner.$values(this.ownerProp)]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
@@ -441,6 +492,6 @@ | ||
* @param {Model|Object} owner | ||
* @param {number|string|Array.<number>|Array.<string>} ids | ||
* @param {number|string|Array.<number|string>|Array.<Array.<number|string>>} ids | ||
*/ | ||
relate(builder, owner, ids) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
@@ -455,19 +506,21 @@ | ||
unrelate(builder, owner) { | ||
throw new Error('not implemented'); | ||
this.throwError('not implemented'); | ||
} | ||
/** | ||
* @private | ||
* @protected | ||
*/ | ||
_propertyName(column, modelClass) { | ||
let propertyName = modelClass.columnNameToPropertyName(column.name); | ||
propertyName(columns, modelClass) { | ||
return _.map(columns, column => { | ||
let propertyName = modelClass.columnNameToPropertyName(column); | ||
if (!propertyName) { | ||
throw new Error(modelClass.name + | ||
'.$parseDatabaseJson probably transforms the value of the column ' + column.name + '.' + | ||
' This is a no-no because ' + column.name + | ||
' is needed in the relation ' + this.ownerModelClass.tableName + '.' + this.name); | ||
} | ||
if (!propertyName) { | ||
throw new Error(modelClass.name + | ||
'.$parseDatabaseJson probably transforms the value of the column ' + column + '.' + | ||
' This is a no-no because ' + column + | ||
' is needed in the relation ' + this.ownerModelClass.tableName + '.' + this.name); | ||
} | ||
return propertyName; | ||
return propertyName; | ||
}); | ||
} | ||
@@ -478,3 +531,3 @@ | ||
*/ | ||
static parseFilter(mapping) { | ||
parseFilter(mapping) { | ||
if (_.isFunction(mapping.filter)) { | ||
@@ -494,10 +547,81 @@ return mapping.filter; | ||
*/ | ||
static parseColumn(column) { | ||
let parts = column.split('.'); | ||
parseReference(ref) { | ||
if (!_.isArray(ref)) { | ||
ref = [ref]; | ||
} | ||
let table = null; | ||
let columns = []; | ||
for (let i = 0; i < ref.length; ++i) { | ||
let parts = ref[i].split('.'); | ||
let tableName = parts[0] && parts[0].trim(); | ||
let columnName = parts[1] && parts[1].trim(); | ||
if (!tableName || (table && table !== tableName) || !columnName) { | ||
return { | ||
table: null, | ||
columns: [] | ||
}; | ||
} else { | ||
table = tableName; | ||
} | ||
columns.push(columnName); | ||
} | ||
return { | ||
table: parts[0] && parts[0].trim(), | ||
name: parts[1] && parts[1].trim() | ||
table: table, | ||
columns: columns | ||
}; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
normalizeId(ids, compositeLength) { | ||
let isComposite = compositeLength > 1; | ||
if (isComposite) { | ||
// For composite ids these two are okay: | ||
// | ||
// 1. [1, 3, 4] | ||
// 2. [[1, 3, 4], [4, 6, 1]] | ||
// | ||
if (!_.isArray(ids) || (!_.isArray(ids[0]) && ids.length !== compositeLength)) { | ||
this.throwError(`Invalid composite key ${ids}`); | ||
} | ||
// Normalize to array of arrays. | ||
if (!_.isArray(ids[0])) { | ||
ids = [ids]; | ||
} | ||
} else { | ||
// Normalize to array of arrays. | ||
if (!_.isArray(ids)) { | ||
ids = [[ids]]; | ||
} else if (!_.isArray(ids[0])) { | ||
ids = _.map(ids, id => [id]); | ||
} | ||
} | ||
_.each(ids, id => { | ||
if (id.length !== compositeLength) { | ||
this.throwError(`Id ${id} has invalid length. Expected ${compositeLength}`) | ||
} | ||
}); | ||
return ids; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
throwError(message) { | ||
if (this.ownerModelClass && this.ownerModelClass.name && this.name) { | ||
throw new Error(`${this.ownerModelClass.name}.relationMappings.${this.name}: ${message}`); | ||
} else { | ||
throw new Error(`${this.constructor.name}: ${message}`); | ||
} | ||
} | ||
} |
import _ from 'lodash'; | ||
import Promise from 'bluebird'; | ||
import Model from './model/Model'; | ||
import utils from './utils'; | ||
import {isSubclassOf} from './utils/classUtils'; | ||
@@ -95,3 +95,3 @@ /** | ||
for (i = 0; i < modelClasses.length; ++i) { | ||
if (!utils.isSubclassOf(modelClasses[i], Model)) { | ||
if (!isSubclassOf(modelClasses[i], Model)) { | ||
return Promise.reject(new Error('objection.transaction: all but the last argument should be Model subclasses')); | ||
@@ -165,3 +165,3 @@ } | ||
if (utils.isSubclassOf(modelClassOrKnex, Model)) { | ||
if (isSubclassOf(modelClassOrKnex, Model)) { | ||
knex = modelClassOrKnex.knex(); | ||
@@ -168,0 +168,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
652462
52
19997