Socket
Socket
Sign inDemoInstall

objection

Package Overview
Dependencies
Maintainers
2
Versions
200
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

objection - npm Package Compare versions

Comparing version 0.4.0-rc.2 to 0.4.0-rc.3

lib/utils/classUtils.js

174

lib/model/Model.js
'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];
}
}

140

lib/model/ModelBase.js
'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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc