objection
Advanced tools
Comparing version 0.3.3 to 0.4.0-rc.1
@@ -19,2 +19,3 @@ 'use strict'; | ||
*/ | ||
try { | ||
@@ -24,2 +25,2 @@ module.exports = require('./inheritModelEs6'); | ||
module.exports = require('./inheritModelEs5'); | ||
} | ||
} |
@@ -10,3 +10,2 @@ 'use strict'; | ||
return AnonymousModelSubclass; | ||
}; | ||
}; |
'use strict'; | ||
var _ = require('lodash') | ||
, utils = require('./../utils') | ||
, ModelBase = require('./ModelBase') | ||
, QueryBuilder = require('./../queryBuilder/QueryBuilder') | ||
, inheritModel = require('./inheritModel') | ||
, RelationExpression = require('./../queryBuilder/RelationExpression') | ||
, ValidationError = require('./../ValidationError') | ||
, EagerFetcher = require('./../queryBuilder/EagerFetcher') | ||
, Relation = require('./../relations/Relation') | ||
, OneToOneRelation = require('./../relations/OneToOneRelation') | ||
, OneToManyRelation = require('./../relations/OneToManyRelation') | ||
, ManyToManyRelation = require('./../relations/ManyToManyRelation'); | ||
var _class, _temp; | ||
/** | ||
* Subclasses of this class represent database tables. | ||
* | ||
* Subclass can be created like this: | ||
* | ||
* ```js | ||
var Model = require('objection').Model; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
function Person() { | ||
Model.apply(this, arguments); | ||
} | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
Model.extend(Person); | ||
module.exports = Person; | ||
var _create2 = _interopRequireDefault(_create); | ||
// Table name is the only required property. | ||
Person.tableName = 'Person'; | ||
var _stringify = require('babel-runtime/core-js/json/stringify'); | ||
// This is not the database schema! Nothing is generated based on this. Whenever a | ||
// Person object is created from a JSON object, the JSON is checked against this | ||
// schema. For example when you call Person.fromJson({firstName: 'Jennifer'}); | ||
Person.jsonSchema = { | ||
type: 'object', | ||
required: ['firstName', 'lastName'], | ||
var _stringify2 = _interopRequireDefault(_stringify); | ||
properties: { | ||
id: {type: 'integer'}, | ||
parentId: {type: ['integer', 'null']}, | ||
firstName: {type: 'string', minLength: 1, maxLength: 255}, | ||
lastName: {type: 'string', minLength: 1, maxLength: 255}, | ||
age: {type: 'number'}, | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
address: { | ||
type: 'object', | ||
properties: { | ||
street: {type: 'string'}, | ||
city: {type: 'string'}, | ||
zipCode: {type: 'string'} | ||
} | ||
} | ||
} | ||
}; | ||
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | ||
// This object defines the relations to other models. | ||
Person.relationMappings = { | ||
pets: { | ||
relation: Model.OneToManyRelation, | ||
// The related model. This can be either a Model subclass constructor or an | ||
// absolute file path to a module that exports one. We use the file path version | ||
// here to prevent require loops. | ||
modelClass: __dirname + '/Animal', | ||
join: { | ||
from: 'Person.id', | ||
to: 'Animal.ownerId' | ||
} | ||
}, | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
movies: { | ||
relation: Model.ManyToManyRelation, | ||
modelClass: __dirname + '/Movie', | ||
join: { | ||
from: 'Person.id', | ||
// ManyToMany relation needs the `through` object to describe the join table. | ||
through: { | ||
from: 'Person_Movie.personId', | ||
to: 'Person_Movie.movieId' | ||
}, | ||
to: 'Movie.id' | ||
} | ||
}, | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
children: { | ||
relation: Model.OneToManyRelation, | ||
modelClass: Person, | ||
join: { | ||
from: 'Person.id', | ||
to: 'Person.parentId' | ||
} | ||
} | ||
}; | ||
* ``` | ||
* | ||
* @extends ModelBase | ||
* @constructor | ||
*/ | ||
function Model() { | ||
ModelBase.apply(this, arguments); | ||
} | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
ModelBase.extend(Model); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
/** | ||
* Returns or sets the identifier of a model instance. | ||
* | ||
* ```js | ||
* // Returns the id. | ||
* model.$id(); | ||
* // Sets the id. | ||
* model.$id(100); | ||
* ``` | ||
* | ||
* The identifier property does not have to be accessed or set using this method. | ||
* If the identifier property is known it can be accessed or set just like any | ||
* other property: | ||
* | ||
* ```js | ||
* console.log(model.id); | ||
* model.id = 100; | ||
* ``` | ||
* | ||
* This method is just a helper for the cases where the id property is not known. | ||
* | ||
* @param {*=} id | ||
* @returns {*} | ||
*/ | ||
Model.prototype.$id = function () { | ||
var ModelClass = this.constructor; | ||
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | ||
if (arguments.length > 0) { | ||
this[ModelClass.getIdProperty()] = arguments[0]; | ||
} else { | ||
return this[ModelClass.getIdProperty()]; | ||
} | ||
}; | ||
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | ||
var _get2 = require('babel-runtime/helpers/get'); | ||
var _get3 = _interopRequireDefault(_get2); | ||
var _inherits2 = require('babel-runtime/helpers/inherits'); | ||
var _inherits3 = _interopRequireDefault(_inherits2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _ModelBase2 = require('./ModelBase'); | ||
var _ModelBase3 = _interopRequireDefault(_ModelBase2); | ||
var _QueryBuilder = require('../queryBuilder/QueryBuilder'); | ||
var _QueryBuilder2 = _interopRequireDefault(_QueryBuilder); | ||
var _inheritModel = require('./inheritModel'); | ||
var _inheritModel2 = _interopRequireDefault(_inheritModel); | ||
var _RelationExpression = require('../queryBuilder/RelationExpression'); | ||
var _RelationExpression2 = _interopRequireDefault(_RelationExpression); | ||
var _ValidationError = require('../ValidationError'); | ||
var _ValidationError2 = _interopRequireDefault(_ValidationError); | ||
var _EagerFetcher = require('../queryBuilder/EagerFetcher'); | ||
var _EagerFetcher2 = _interopRequireDefault(_EagerFetcher); | ||
var _Relation = require('../relations/Relation'); | ||
var _Relation2 = _interopRequireDefault(_Relation); | ||
var _OneToOneRelation = require('../relations/OneToOneRelation'); | ||
var _OneToOneRelation2 = _interopRequireDefault(_OneToOneRelation); | ||
var _OneToManyRelation = require('../relations/OneToManyRelation'); | ||
var _OneToManyRelation2 = _interopRequireDefault(_OneToManyRelation); | ||
var _ManyToManyRelation = require('../relations/ManyToManyRelation'); | ||
var _ManyToManyRelation2 = _interopRequireDefault(_ManyToManyRelation); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* Creates a query builder for this model instance. | ||
* Subclasses of this class represent database tables. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* Subclass can be created like this: | ||
* | ||
* All queries built using the returned builder only affect this instance. | ||
* ```js | ||
* var Model = require('objection').Model; | ||
* | ||
* Examples: | ||
* function Person() { | ||
* Model.apply(this, arguments); | ||
* } | ||
* | ||
* Re-fetch the instance from the database: | ||
* Model.extend(Person); | ||
* module.exports = Person; | ||
* | ||
* ```js | ||
* person.$query().then(function (person) { | ||
* console.log(person); | ||
* }); | ||
* ``` | ||
* // Table name is the only required property. | ||
* Person.tableName = 'Person'; | ||
* | ||
* Insert a new model to database: | ||
* // This is not the database schema! Nothing is generated based on this. Whenever a | ||
* // Person object is created from a JSON object, the JSON is checked against this | ||
* // schema. For example when you call Person.fromJson({firstName: 'Jennifer'}); | ||
* Person.jsonSchema = { | ||
* type: 'object', | ||
* required: ['firstName', 'lastName'], | ||
* | ||
* ```js | ||
* Person.fromJson({firstName: 'Jennifer'}).$query().insert().then(function (jennifer) { | ||
* console.log(jennifer.id); | ||
* }); | ||
* ``` | ||
* properties: { | ||
* id: {type: 'integer'}, | ||
* parentId: {type: ['integer', 'null']}, | ||
* firstName: {type: 'string', minLength: 1, maxLength: 255}, | ||
* lastName: {type: 'string', minLength: 1, maxLength: 255}, | ||
* age: {type: 'number'}, | ||
* | ||
* Patch a model: | ||
* address: { | ||
* type: 'object', | ||
* properties: { | ||
* street: {type: 'string'}, | ||
* city: {type: 'string'}, | ||
* zipCode: {type: 'string'} | ||
* } | ||
* } | ||
* } | ||
* }; | ||
* | ||
* ```js | ||
* person.$query().patch({lastName: 'Cooper'}).then(function (person) { | ||
* console.log(person.lastName); // --> 'Cooper'. | ||
* }); | ||
* ``` | ||
* // This object defines the relations to other models. | ||
* Person.relationMappings = { | ||
* pets: { | ||
* relation: Model.OneToManyRelation, | ||
* // The related model. This can be either a Model subclass constructor or an | ||
* // absolute file path to a module that exports one. We use the file path version | ||
* // here to prevent require loops. | ||
* modelClass: __dirname + '/Animal', | ||
* join: { | ||
* from: 'Person.id', | ||
* to: 'Animal.ownerId' | ||
* } | ||
* }, | ||
* | ||
* Delete a model. | ||
* movies: { | ||
* relation: Model.ManyToManyRelation, | ||
* modelClass: __dirname + '/Movie', | ||
* join: { | ||
* from: 'Person.id', | ||
* // ManyToMany relation needs the `through` object to describe the join table. | ||
* through: { | ||
* from: 'Person_Movie.personId', | ||
* to: 'Person_Movie.movieId' | ||
* }, | ||
* to: 'Movie.id' | ||
* } | ||
* }, | ||
* | ||
* ```js | ||
* person.$query().delete().then(function () { | ||
* console.log('person deleted'); | ||
* }); | ||
* children: { | ||
* relation: Model.OneToManyRelation, | ||
* modelClass: Person, | ||
* join: { | ||
* from: 'Person.id', | ||
* to: 'Person.parentId' | ||
* } | ||
* } | ||
* }; | ||
* ``` | ||
* | ||
* @returns {QueryBuilder} | ||
* @extends ModelBase | ||
* @constructor | ||
*/ | ||
Model.prototype.$query = function () { | ||
var ModelClass = this.constructor; | ||
var self = this; | ||
var Model = (_temp = _class = (function (_ModelBase) { | ||
(0, _inherits3.default)(Model, _ModelBase); | ||
return ModelClass.QueryBuilder | ||
.forClass(ModelClass) | ||
.findImpl(function () { | ||
this.first(); | ||
this.onBuild(function (builder) { | ||
builder.where(ModelClass.getFullIdColumn(), self.$id()); | ||
}); | ||
}) | ||
.insertImpl(function (insertion) { | ||
insertion.setData(self); | ||
function Model() { | ||
(0, _classCallCheck3.default)(this, Model); | ||
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Model).apply(this, arguments)); | ||
} | ||
this.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
}) | ||
.updateImpl(function (update) { | ||
if (!update.model()) { | ||
update.setData(self); | ||
(0, _createClass3.default)(Model, [{ | ||
key: '$id', | ||
/** | ||
* Returns or sets the identifier of a model instance. | ||
* | ||
* ```js | ||
* // Returns the id. | ||
* model.$id(); | ||
* // Sets the id. | ||
* model.$id(100); | ||
* ``` | ||
* | ||
* The identifier property does not have to be accessed or set using this method. | ||
* If the identifier property is known it can be accessed or set just like any | ||
* other property: | ||
* | ||
* ```js | ||
* console.log(model.id); | ||
* model.id = 100; | ||
* ``` | ||
* | ||
* This method is just a helper for the cases where the id property is not known. | ||
* | ||
* @param {*=} id | ||
* @returns {*} | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
/** | ||
* Properties that should be saved to database as JSON strings. | ||
* | ||
* The properties listed here are serialized to JSON strings upon insertion to the database | ||
* and parsed back to objects when models are read from the database. Combined with the | ||
* postgresql's json or jsonb data type, this is a powerful way of representing documents | ||
* as single database rows. | ||
* | ||
* If this property is left unset all properties declared as objects or arrays in the | ||
* `jsonSchema` are implicitly added to this list. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* Person.jsonAttributes = ['address']; | ||
* | ||
* var jennifer = Person.fromJson({ | ||
* name: 'Jennifer', | ||
* address: { | ||
* address: 'Someroad 10', | ||
* zipCode: '1234', | ||
* city: 'Los Angeles' | ||
* } | ||
* }); | ||
* | ||
* var dbRow = jennifer.$toDatabaseJson(); | ||
* console.log(dbRow); | ||
* // --> {name: 'Jennifer', address: '{"address":"Someroad 10","zipCode":"1234","city":"Los Angeles"}'} | ||
* ``` | ||
* | ||
* @type {Array.<string>} | ||
*/ | ||
/** | ||
* Name of the property used to store a reference to a `uidProp`. | ||
* | ||
* Defaults to '#ref'. | ||
* | ||
* @type {string} | ||
*/ | ||
/** | ||
* Name of the primary key column in the database table. | ||
* | ||
* Defaults to 'id'. | ||
* | ||
* @type {string} | ||
*/ | ||
/** | ||
* may-to-many relation type. | ||
* | ||
* @type {ManyToManyRelation} | ||
*/ | ||
/** | ||
* one-to-many relation type. | ||
* | ||
* @type {OneToOneRelation} | ||
*/ | ||
/** | ||
* QueryBuilder subclass to use in `query()` or `$query()` methods. | ||
* | ||
* This constructor is used whenever a query builder is created using `query()` or `$query()` methods. | ||
* You can override this to use your own `QueryBuilder` subclass. | ||
*/ | ||
value: function $id() { | ||
var ModelClass = this.constructor; | ||
if (arguments.length > 0) { | ||
this[ModelClass.getIdProperty()] = arguments[0]; | ||
} else { | ||
return this[ModelClass.getIdProperty()]; | ||
} | ||
} | ||
this.onBuild(function (builder) { | ||
builder.$$update(update).where(ModelClass.getFullIdColumn(), self.$id()); | ||
/** | ||
* Creates a query builder for this model instance. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* | ||
* All queries built using the returned builder only affect this instance. | ||
* | ||
* Examples: | ||
* | ||
* Re-fetch the instance from the database: | ||
* | ||
* ```js | ||
* person.$query().then(function (person) { | ||
* console.log(person); | ||
* }); | ||
* ``` | ||
* | ||
* Insert a new model to database: | ||
* | ||
* ```js | ||
* Person.fromJson({firstName: 'Jennifer'}).$query().insert().then(function (jennifer) { | ||
* console.log(jennifer.id); | ||
* }); | ||
* ``` | ||
* | ||
* Patch a model: | ||
* | ||
* ```js | ||
* person.$query().patch({lastName: 'Cooper'}).then(function (person) { | ||
* console.log(person.lastName); // --> 'Cooper'. | ||
* }); | ||
* ``` | ||
* | ||
* Delete a model. | ||
* | ||
* ```js | ||
* person.$query().delete().then(function () { | ||
* console.log('person deleted'); | ||
* }); | ||
* ``` | ||
* | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
/** | ||
* This property defines the relations to other models. | ||
* | ||
* Relations to other models can be defined by setting this property. The best way to explain how to | ||
* do this is by example: | ||
* | ||
* ```js | ||
* Person.relationMappings = { | ||
* pets: { | ||
* relation: Model.OneToManyRelation, | ||
* modelClass: Animal, | ||
* join: { | ||
* from: 'Person.id', | ||
* to: 'Animal.ownerId' | ||
* } | ||
* }, | ||
* | ||
* father: { | ||
* relation: Model.OneToOneRelation, | ||
* modelClass: Person, | ||
* join: { | ||
* from: 'Person.fatherId', | ||
* to: 'Person.id' | ||
* } | ||
* }, | ||
* | ||
* movies: { | ||
* relation: Model.ManyToManyRelation, | ||
* modelClass: Movie, | ||
* join: { | ||
* from: 'Person.id', | ||
* through: { | ||
* from: 'Person_Movie.actorId', | ||
* to: 'Person_Movie.movieId' | ||
* }, | ||
* to: 'Movie.id' | ||
* } | ||
* } | ||
* }; | ||
* ``` | ||
* | ||
* relationMappings is an object whose keys are relation names and values define the relation. The | ||
* `join` property in addition to the relation type define how the models are related to one another. | ||
* The `from` and `to` properties of the `join` object define the database columns through which the | ||
* models are associated. Note that neither of these columns need to be primary keys. They can be any | ||
* columns!. In the case of ManyToManyRelation also the join table needs to be defined. This is | ||
* done using the `through` object. | ||
* | ||
* The `modelClass` passed to the relation mappings is the class of the related model. It can be either | ||
* a Model subclass constructor or an absolute path to a module that exports one. Using file paths | ||
* is a handy way to prevent require loops. | ||
* | ||
* @type {Object.<string, RelationMapping>} | ||
*/ | ||
/** | ||
* Regular expression for parsing a reference to a property. | ||
* | ||
* @type {RegExp} | ||
*/ | ||
/** | ||
* Name of the property used to store a temporary non-db identifier for the model. | ||
* | ||
* Defaults to '#id'. | ||
* | ||
* @type {string} | ||
*/ | ||
/** | ||
* Name of the database table of this model. | ||
* | ||
* @type {string} | ||
*/ | ||
/** | ||
* one-to-many relation type. | ||
* | ||
* @type {OneToManyRelation} | ||
*/ | ||
/** | ||
* QueryBuilder subclass to use in `$relatedQuery()` method. | ||
* | ||
* This constructor is used whenever a query builder is created using the `$relatedQuery()` method. | ||
* You can override this to use your own `QueryBuilder` subclass. | ||
*/ | ||
}, { | ||
key: '$query', | ||
value: function $query() { | ||
var _this2 = this; | ||
var ModelClass = this.constructor; | ||
return ModelClass.QueryBuilder.forClass(ModelClass).findImpl(function (builder) { | ||
builder.first(); | ||
builder.onBuild(function (builder) { | ||
builder.where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
}).insertImpl(function (insertion, builder) { | ||
insertion.setData(_this2); | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
}).updateImpl(function (update, builder) { | ||
if (!update.model()) { | ||
update.setData(_this2); | ||
} | ||
builder.onBuild(function (builder) { | ||
builder.$$update(update).where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
}).patchImpl(function (patch, builder) { | ||
if (!patch.model()) { | ||
patch.setData(_this2); | ||
} | ||
builder.onBuild(function (builder) { | ||
builder.$$update(patch).where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
}).deleteImpl(function (builder) { | ||
builder.onBuild(function (builder) { | ||
builder.$$delete().where(ModelClass.getFullIdColumn(), _this2.$id()); | ||
}); | ||
}).relateImpl(function () { | ||
throw new Error('`relate` makes no sense in this context'); | ||
}).unrelateImpl(function () { | ||
throw new Error('`unrelate` makes no sense in this context'); | ||
}); | ||
}) | ||
.patchImpl(function (patch) { | ||
if (!patch.model()) { | ||
patch.setData(self); | ||
} | ||
} | ||
this.onBuild(function (builder) { | ||
builder.$$update(patch).where(ModelClass.getFullIdColumn(), self.$id()); | ||
/** | ||
* Use this to build a query that only affects the models related to this instance through a relation. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* | ||
* Examples: | ||
* | ||
* Fetch all models related to this instance through a relation. The fetched models are | ||
* also stored to the owner model's property named after the relation: | ||
* | ||
* ```js | ||
* jennifer.$relatedQuery('pets').then(function (pets) { | ||
* console.log('jennifer has', pets.length, 'pets'); | ||
* console.log(jennifer.pets === pets); // --> true | ||
* }); | ||
* ``` | ||
* | ||
* The related query is just like any other query. All *knex* methods are available: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .select('Animal.*', 'Person.name as ownerName') | ||
* .where('species', '=', 'dog') | ||
* .orWhere('breed', '=', 'cat') | ||
* .innerJoin('Person', 'Person.id', 'Animal.ownerId') | ||
* .orderBy('Animal.name') | ||
* .then(function (dogsAndCats) { | ||
* // All the dogs and cats have the owner's name "Jennifer" joined as the `ownerName` property. | ||
* console.log(dogsAndCats); | ||
* }); | ||
* ``` | ||
* | ||
* This inserts a new model to the database and binds it to the owner model as defined | ||
* by the relation: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .insert({species: 'dog', name: 'Fluffy'}) | ||
* .then(function (waldo) { | ||
* console.log(waldo.id); | ||
* }); | ||
* ``` | ||
* | ||
* To add an existing model to a relation the `relate` method can be used. In this example | ||
* the dog `fluffy` already exists in the database but it isn't related to `jennifer` through | ||
* the `pets` relation. We can make the connection like this: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .relate(fluffy.id) | ||
* .then(function () { | ||
* console.log('fluffy is now related to jennifer through pets relation'); | ||
* }); | ||
* ``` | ||
* | ||
* The connection can be removed using the `unrelate` method. Again, this doesn't delete the | ||
* related model. Only the connection is removed. For example in the case of ManyToMany relation | ||
* the join table entries are deleted. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .unrelate() | ||
* .where('id', fluffy.id) | ||
* .then(function () { | ||
* console.log('jennifer no longer has fluffy as a pet'); | ||
* }); | ||
* ``` | ||
* | ||
* Related models can be deleted using the delete method. Note that in the case of ManyToManyRelation | ||
* the join table entries are not deleted. Naturally the delete query can be chained with any *knex* | ||
* methods. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .delete() | ||
* .where('species', 'cat') | ||
* .then(function () { | ||
* console.log('jennifer no longer has any cats'); | ||
* }); | ||
* ``` | ||
* | ||
* `update` and `patch` can be used to update related models. Only difference between the mentioned | ||
* methods is that `update` validates the input objects using the related model class's full schema | ||
* and `patch` ignores the `required` property of the schema. Use `update` when you want to update | ||
* _all_ properties of a model and `patch` when only a subset should be updated. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .update({species: 'dog', name: 'Fluffy the great', vaccinated: false}) | ||
* .where('id', fluffy.id) | ||
* .then(function (updatedFluffy) { | ||
* console.log('fluffy\'s new name is', updatedFluffy.name); | ||
* }); | ||
* | ||
* // This will throw assuming that `name` or `species` is a required property for an Animal. | ||
* jennifer.$relatedQuery('pets').patch({vaccinated: true}); | ||
* | ||
* // This will _not_ throw. | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .patch({vaccinated: true}) | ||
* .where('species', 'dog') | ||
* .then(function () { | ||
* console.log('jennifer just got all her dogs vaccinated'); | ||
* }); | ||
* ``` | ||
* | ||
* @param {string} relationName | ||
* Name of the relation. | ||
* | ||
* @returns {QueryBuilder} | ||
*/ | ||
}, { | ||
key: '$relatedQuery', | ||
value: function $relatedQuery(relationName) { | ||
var _this3 = this; | ||
var relation = this.constructor.getRelation(relationName); | ||
var ModelClass = relation.relatedModelClass; | ||
return ModelClass.RelatedQueryBuilder.forClass(ModelClass).findImpl(function (builder) { | ||
relation.find(builder, [_this3]); | ||
}).insertImpl(function (insert, builder) { | ||
relation.insert(builder, _this3, insert); | ||
}).updateImpl(function (update, builder) { | ||
relation.update(builder, _this3, update); | ||
}).patchImpl(function (patch, builder) { | ||
relation.patch(builder, _this3, patch); | ||
}).deleteImpl(function (builder) { | ||
relation.delete(builder, _this3); | ||
}).relateImpl(function (ids, builder) { | ||
relation.relate(builder, _this3, ids); | ||
}).unrelateImpl(function (builder) { | ||
relation.unrelate(builder, _this3); | ||
}); | ||
}) | ||
.deleteImpl(function () { | ||
this.onBuild(function (builder) { | ||
builder.$$delete().where(ModelClass.getFullIdColumn(), self.$id()); | ||
}); | ||
}) | ||
.relateImpl(function () { | ||
throw new Error('`relate` makes no sense in this context'); | ||
}) | ||
.unrelateImpl(function () { | ||
throw new Error('`unrelate` makes no sense in this context'); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Use this to build a query that only affects the models related to this instance through a relation. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* | ||
* Examples: | ||
* | ||
* Fetch all models related to this instance through a relation. The fetched models are | ||
* also stored to the owner model's property named after the relation: | ||
* | ||
* ```js | ||
* jennifer.$relatedQuery('pets').then(function (pets) { | ||
* console.log('jennifer has', pets.length, 'pets'); | ||
* console.log(jennifer.pets === pets); // --> true | ||
* }); | ||
* ``` | ||
* | ||
* The related query is just like any other query. All *knex* methods are available: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .select('Animal.*', 'Person.name as ownerName') | ||
* .where('species', '=', 'dog') | ||
* .orWhere('breed', '=', 'cat') | ||
* .innerJoin('Person', 'Person.id', 'Animal.ownerId') | ||
* .orderBy('Animal.name') | ||
* .then(function (dogsAndCats) { | ||
* // All the dogs and cats have the owner's name "Jennifer" joined as the `ownerName` property. | ||
* console.log(dogsAndCats); | ||
* }); | ||
* ``` | ||
* | ||
* This inserts a new model to the database and binds it to the owner model as defined | ||
* by the relation: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .insert({species: 'dog', name: 'Fluffy'}) | ||
* .then(function (waldo) { | ||
* console.log(waldo.id); | ||
* }); | ||
* ``` | ||
* | ||
* To add an existing model to a relation the `relate` method can be used. In this example | ||
* the dog `fluffy` already exists in the database but it isn't related to `jennifer` through | ||
* the `pets` relation. We can make the connection like this: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .relate(fluffy.id) | ||
* .then(function () { | ||
* console.log('fluffy is now related to jennifer through pets relation'); | ||
* }); | ||
* ``` | ||
* | ||
* The connection can be removed using the `unrelate` method. Again, this doesn't delete the | ||
* related model. Only the connection is removed. For example in the case of ManyToMany relation | ||
* the join table entries are deleted. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .unrelate() | ||
* .where('id', fluffy.id) | ||
* .then(function () { | ||
* console.log('jennifer no longer has fluffy as a pet'); | ||
* }); | ||
* ``` | ||
* | ||
* Related models can be deleted using the delete method. Note that in the case of ManyToManyRelation | ||
* the join table entries are not deleted. Naturally the delete query can be chained with any *knex* | ||
* methods. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .delete() | ||
* .where('species', 'cat') | ||
* .then(function () { | ||
* console.log('jennifer no longer has any cats'); | ||
* }); | ||
* ``` | ||
* | ||
* `update` and `patch` can be used to update related models. Only difference between the mentioned | ||
* methods is that `update` validates the input objects using the related model class's full schema | ||
* and `patch` ignores the `required` property of the schema. Use `update` when you want to update | ||
* _all_ properties of a model and `patch` when only a subset should be updated. | ||
* | ||
* ```js | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .update({species: 'dog', name: 'Fluffy the great', vaccinated: false}) | ||
* .where('id', fluffy.id) | ||
* .then(function (updatedFluffy) { | ||
* console.log('fluffy\'s new name is', updatedFluffy.name); | ||
* }); | ||
* | ||
* // This will throw assuming that `name` or `species` is a required property for an Animal. | ||
* jennifer.$relatedQuery('pets').patch({vaccinated: true}); | ||
* | ||
* // This will _not_ throw. | ||
* jennifer | ||
* .$relatedQuery('pets') | ||
* .patch({vaccinated: true}) | ||
* .where('species', 'dog') | ||
* .then(function () { | ||
* console.log('jennifer just got all her dogs vaccinated'); | ||
* }); | ||
* ``` | ||
* | ||
* @param {String} relationName | ||
* Name of the relation. | ||
* | ||
* @returns {QueryBuilder} | ||
*/ | ||
Model.prototype.$relatedQuery = function (relationName) { | ||
var relation = this.constructor.getRelation(relationName); | ||
var ModelClass = relation.relatedModelClass; | ||
var self = this; | ||
/** | ||
* Loads related models using a {@link RelationExpression}. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* jennifer.$loadRelated('[pets, children.[pets, father]]').then(function (jennifer) { | ||
* console.log('Jennifer has', jennifer.pets.length, 'pets'); | ||
* console.log('Jennifer has', jennifer.children.length, 'children'); | ||
* console.log('Jennifer\'s first child has', jennifer.children[0].pets.length, 'pets'); | ||
* console.log('Jennifer had her first child with', jennifer.children[0].father.name); | ||
* }); | ||
* ``` | ||
* | ||
* Relations can be filtered by giving named filter functions as arguments | ||
* to the relations: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$loadRelated('children(orderByAge).[pets(onlyDogs, orderByName), movies]', { | ||
* orderByAge: function (builder) { | ||
* builder.orderBy('age') | ||
* }, | ||
* orderByName: function (builder) { | ||
* builder.orderBy('name'); | ||
* }, | ||
* onlyDogs: function (builder) { | ||
* builder.where('species', 'dog') | ||
* } | ||
* }) | ||
* .then(function (jennifer) { | ||
* console.log(jennifer.children.pets[0]); | ||
* }); | ||
* ``` | ||
* | ||
* @see {@link RelationExpression} for more examples on relation expressions. | ||
* | ||
* @param {string|RelationExpression} relationExpression | ||
* @param {Object.<string, function(QueryBuilder)>=} filters | ||
* @returns {Promise} | ||
*/ | ||
return ModelClass.RelatedQueryBuilder | ||
.forClass(ModelClass) | ||
.findImpl(function () { | ||
var queryBuilder = this; | ||
relation.find(queryBuilder, [self]); | ||
}) | ||
.insertImpl(function (insert) { | ||
var queryBuilder = this; | ||
relation.insert(queryBuilder, self, insert); | ||
}) | ||
.updateImpl(function (update) { | ||
var queryBuilder = this; | ||
relation.update(queryBuilder, self, update); | ||
}) | ||
.patchImpl(function (patch) { | ||
var queryBuilder = this; | ||
relation.patch(queryBuilder, self, patch); | ||
}) | ||
.deleteImpl(function () { | ||
var queryBuilder = this; | ||
relation.delete(queryBuilder, self); | ||
}) | ||
.relateImpl(function (ids) { | ||
var queryBuilder = this; | ||
relation.relate(queryBuilder, self, ids); | ||
}) | ||
.unrelateImpl(function () { | ||
var queryBuilder = this; | ||
relation.unrelate(queryBuilder, self); | ||
}); | ||
}; | ||
}, { | ||
key: '$loadRelated', | ||
value: function $loadRelated(relationExpression, filters) { | ||
return this.constructor.loadRelated(this, relationExpression, filters); | ||
} | ||
/** | ||
* Loads related models using a {@link RelationExpression}. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* jennifer.$loadRelated('[pets, children.[pets, father]]').then(function (jennifer) { | ||
* console.log('Jennifer has', jennifer.pets.length, 'pets'); | ||
* console.log('Jennifer has', jennifer.children.length, 'children'); | ||
* console.log('Jennifer\'s first child has', jennifer.children[0].pets.length, 'pets'); | ||
* console.log('Jennifer had her first child with', jennifer.children[0].father.name); | ||
* }); | ||
* ``` | ||
* | ||
* Relations can be filtered by giving named filter functions as arguments | ||
* to the relations: | ||
* | ||
* ```js | ||
* jennifer | ||
* .$loadRelated('children(orderByAge).[pets(onlyDogs, orderByName), movies]', { | ||
* orderByAge: function (builder) { | ||
* builder.orderBy('age') | ||
* }, | ||
* orderByName: function (builder) { | ||
* builder.orderBy('name'); | ||
* }, | ||
* onlyDogs: function (builder) { | ||
* builder.where('species', 'dog') | ||
* } | ||
* }) | ||
* .then(function (jennifer) { | ||
* console.log(jennifer.children.pets[0]); | ||
* }); | ||
* ``` | ||
* | ||
* @see {@link RelationExpression} for more examples on relation expressions. | ||
* | ||
* @param {String|RelationExpression} relationExpression | ||
* @param {Object.<String, Function(QueryBuilder)>=} filters | ||
* @returns {Promise} | ||
*/ | ||
Model.prototype.$loadRelated = function (relationExpression, filters) { | ||
return this.constructor.loadRelated(this, relationExpression, filters); | ||
}; | ||
/** | ||
* Shortcut for `Model.traverse(filterConstructor, this, callback)`. | ||
* | ||
* See the static method `Model.traverse` for more info. | ||
* | ||
* @param {function=} filterConstructor | ||
* @param {function(Model)} callback | ||
* @return {Model} | ||
*/ | ||
/** | ||
* Shortcut for `Model.traverse(filterConstructor, this, callback)`. | ||
* | ||
* See the static method `Model.traverse` for more info. | ||
* | ||
* @param {Function=} filterConstructor | ||
* @param {Function(Model)} callback | ||
* @return {Model} | ||
*/ | ||
Model.prototype.$traverse = function (filterConstructor, callback) { | ||
if (_.isUndefined(callback)) { | ||
callback = filterConstructor; | ||
filterConstructor = null; | ||
} | ||
}, { | ||
key: '$traverse', | ||
value: function $traverse(filterConstructor, callback) { | ||
if (_lodash2.default.isUndefined(callback)) { | ||
callback = filterConstructor; | ||
filterConstructor = null; | ||
} | ||
this.constructor.traverse(filterConstructor, this, callback); | ||
return this; | ||
}; | ||
this.constructor.traverse(filterConstructor, this, callback); | ||
return this; | ||
} | ||
/** | ||
* @override | ||
*/ | ||
Model.prototype.$parseDatabaseJson = function (json) { | ||
var ModelClass = this.constructor; | ||
var jsonAttr = ModelClass.$$getJsonAttributes(); | ||
/** | ||
* @override | ||
*/ | ||
if (jsonAttr.length) { | ||
for (var i = 0, l = jsonAttr.length; i < l; ++i) { | ||
var attr = jsonAttr[i]; | ||
var value = json[attr]; | ||
}, { | ||
key: '$parseDatabaseJson', | ||
value: function $parseDatabaseJson(json) { | ||
var ModelClass = this.constructor; | ||
var jsonAttr = ModelClass.$$getJsonAttributes(); | ||
if (_.isString(value)) { | ||
json[attr] = JSON.parse(value); | ||
if (jsonAttr.length) { | ||
for (var i = 0, l = jsonAttr.length; i < l; ++i) { | ||
var attr = jsonAttr[i]; | ||
var value = json[attr]; | ||
if (_lodash2.default.isString(value)) { | ||
json[attr] = JSON.parse(value); | ||
} | ||
} | ||
} | ||
return json; | ||
} | ||
} | ||
return json; | ||
}; | ||
/** | ||
* @override | ||
*/ | ||
/** | ||
* @override | ||
*/ | ||
Model.prototype.$formatDatabaseJson = function (json) { | ||
var ModelClass = this.constructor; | ||
var jsonAttr = ModelClass.$$getJsonAttributes(); | ||
}, { | ||
key: '$formatDatabaseJson', | ||
value: function $formatDatabaseJson(json) { | ||
var ModelClass = this.constructor; | ||
var jsonAttr = ModelClass.$$getJsonAttributes(); | ||
if (jsonAttr.length) { | ||
for (var i = 0, l = jsonAttr.length; i < l; ++i) { | ||
var attr = jsonAttr[i]; | ||
var value = json[attr]; | ||
if (jsonAttr.length) { | ||
for (var i = 0, l = jsonAttr.length; i < l; ++i) { | ||
var attr = jsonAttr[i]; | ||
var value = json[attr]; | ||
if (_.isObject(value)) { | ||
json[attr] = JSON.stringify(value); | ||
if (_lodash2.default.isObject(value)) { | ||
json[attr] = (0, _stringify2.default)(value); | ||
} | ||
} | ||
} | ||
return json; | ||
} | ||
} | ||
return json; | ||
}; | ||
/** | ||
* @override | ||
*/ | ||
/** | ||
* @override | ||
*/ | ||
Model.prototype.$setJson = function (json, options) { | ||
ModelBase.prototype.$setJson.call(this, json, options); | ||
}, { | ||
key: '$setJson', | ||
value: function $setJson(json, options) { | ||
(0, _get3.default)((0, _getPrototypeOf2.default)(Model.prototype), '$setJson', this).call(this, json, options); | ||
if (!_.isObject(json)) { | ||
return; | ||
} | ||
if (!_lodash2.default.isObject(json)) { | ||
return; | ||
} | ||
var relations = this.constructor.getRelations(); | ||
// Parse relations into Model instances. | ||
for (var relationName in relations) { | ||
if (_.has(json, relationName)) { | ||
var relationJson = json[relationName]; | ||
var relation = relations[relationName]; | ||
var relations = this.constructor.getRelations(); | ||
// Parse relations into Model instances. | ||
for (var relationName in relations) { | ||
if (_lodash2.default.has(json, relationName)) { | ||
var relationJson = json[relationName]; | ||
var relation = relations[relationName]; | ||
if (_.isArray(relationJson)) { | ||
this[relationName] = relation.relatedModelClass.ensureModelArray(relationJson, options); | ||
} else if (relationJson) { | ||
this[relationName] = relation.relatedModelClass.ensureModel(relationJson, options); | ||
if (_lodash2.default.isArray(relationJson)) { | ||
this[relationName] = relation.relatedModelClass.ensureModelArray(relationJson, options); | ||
} else if (relationJson) { | ||
this[relationName] = relation.relatedModelClass.ensureModel(relationJson, options); | ||
} else { | ||
this[relationName] = null; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @override | ||
* | ||
* @param {boolean} shallow | ||
* If true the relations are omitted from the json. | ||
*/ | ||
}, { | ||
key: '$toJson', | ||
value: function $toJson(shallow) { | ||
if (shallow) { | ||
return this.$$toJson(false, this.constructor.getRelations(), null); | ||
} else { | ||
this[relationName] = null; | ||
return this.$$toJson(false, null, null); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* @override | ||
* | ||
* @param {Boolean} shallow | ||
* If true the relations are omitted from the json. | ||
*/ | ||
Model.prototype.$toJson = function (shallow) { | ||
if (shallow) { | ||
return this.$$toJson(false, this.constructor.getRelations(), null); | ||
} else { | ||
return this.$$toJson(false, null, null); | ||
} | ||
}; | ||
/** | ||
* @override | ||
*/ | ||
/** | ||
* @override | ||
*/ | ||
Model.prototype.$toDatabaseJson = function () { | ||
var jsonSchema = this.constructor.jsonSchema; | ||
var pick = jsonSchema && jsonSchema.properties; | ||
var omit; | ||
}, { | ||
key: '$toDatabaseJson', | ||
value: function $toDatabaseJson() { | ||
var jsonSchema = this.constructor.jsonSchema; | ||
var pick = jsonSchema && jsonSchema.properties; | ||
var omit = undefined; | ||
if (!pick) { | ||
omit = this.constructor.getRelations(); | ||
} | ||
if (!pick) { | ||
omit = this.constructor.getRelations(); | ||
} | ||
return this.$$toJson(true, omit, pick); | ||
}; | ||
return this.$$toJson(true, omit, pick); | ||
} | ||
/** | ||
* Called before a model is inserted into the database. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. You can | ||
* also throw an exception to abort the insert and reject the query. This can be useful if | ||
* you need to do insert specific validation. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
Model.prototype.$beforeInsert = function () { | ||
/** | ||
* Called before a model is inserted into the database. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. You can | ||
* also throw an exception to abort the insert and reject the query. This can be useful if | ||
* you need to do insert specific validation. | ||
* | ||
* @param {Object} queryContext | ||
* The context object of the insert query. See {@link QueryBuilder#context}. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
}; | ||
}, { | ||
key: '$beforeInsert', | ||
value: function $beforeInsert(queryContext) {} | ||
/** | ||
* Called after a model has been inserted into the database. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
Model.prototype.$afterInsert = function () { | ||
/** | ||
* Called after a model has been inserted into the database. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. | ||
* | ||
* @param {Object} queryContext | ||
* The context object of the insert query. See {@link QueryBuilder#context}. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
}; | ||
}, { | ||
key: '$afterInsert', | ||
value: function $afterInsert(queryContext) {} | ||
/** | ||
* Called before a model is updated. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. You can | ||
* also throw an exception to abort the update and reject the query. This can be useful if | ||
* you need to do update specific validation. | ||
* | ||
* This method is also called before a model is patched. Therefore all the model's properties | ||
* may not exist. You can check if the update operation is a patch by checking the `opt.patch` | ||
* boolean. | ||
* | ||
* Also note that this method is called only once when you do something like this: | ||
* | ||
* ```js | ||
* Person | ||
* .$query() | ||
* .patch({firstName: 'Jennifer'}) | ||
* .where('gender', 'female') | ||
* .then(function () { | ||
* ... | ||
* }); | ||
* ``` | ||
* | ||
* The example above updates all rows whose `gender` equals `female` but the `$beforeUpdate` | ||
* method is called only once for the `{firstName: 'Jennifer'}` model. This is because the | ||
* updating is done completely in the database and the affected models are never fetched | ||
* to the javascript side. | ||
* | ||
* @param {ModelOptions} opt | ||
* @returns {Promise|*} | ||
*/ | ||
Model.prototype.$beforeUpdate = function (opt) { | ||
_.noop(opt); | ||
}; | ||
/** | ||
* Called before a model is updated. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. You can | ||
* also throw an exception to abort the update and reject the query. This can be useful if | ||
* you need to do update specific validation. | ||
* | ||
* This method is also called before a model is patched. Therefore all the model's properties | ||
* may not exist. You can check if the update operation is a patch by checking the `opt.patch` | ||
* boolean. | ||
* | ||
* Also note that this method is called only once when you do something like this: | ||
* | ||
* ```js | ||
* Person | ||
* .$query() | ||
* .patch({firstName: 'Jennifer'}) | ||
* .where('gender', 'female') | ||
* .then(function () { | ||
* ... | ||
* }); | ||
* ``` | ||
* | ||
* The example above updates all rows whose `gender` equals `female` but the `$beforeUpdate` | ||
* method is called only once for the `{firstName: 'Jennifer'}` model. This is because the | ||
* updating is done completely in the database and the affected models are never fetched | ||
* to the javascript side. | ||
* | ||
* @param {ModelOptions} opt | ||
* | ||
* @param {Object} queryContext | ||
* The context object of the update query. See {@link QueryBuilder#context}. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
/** | ||
* Called after a model is updated. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. | ||
* | ||
* This method is also called after a model is patched. Therefore all the model's properties | ||
* may not exist. You can check if the update operation is a patch by checking the `opt.patch` | ||
* boolean. | ||
* | ||
* Also note that this method is called only once when you do something like this: | ||
* | ||
* ```js | ||
* Person | ||
* .$query() | ||
* .patch({firstName: 'Jennifer'}) | ||
* .where('gender', 'female') | ||
* .then(function () { | ||
* ... | ||
* }); | ||
* ``` | ||
* | ||
* The example above updates all rows whose `gender` equals `female` but the `$beforeUpdate` | ||
* method is called only once for the `{firstName: 'Jennifer'}` model. This is because the | ||
* updating is done completely in the database and the affected models are never fetched | ||
* to the javascript side. | ||
* | ||
* @param {ModelOptions} opt | ||
* @returns {Promise|*} | ||
*/ | ||
Model.prototype.$afterUpdate = function (opt) { | ||
_.noop(opt); | ||
}; | ||
}, { | ||
key: '$beforeUpdate', | ||
value: function $beforeUpdate(opt, queryContext) {} | ||
/** | ||
* QueryBuilder subclass to use in `query()` or `$query()` methods. | ||
* | ||
* This constructor is used whenever a query builder is created using `query()` or `$query()` methods. | ||
* You can override this to use your own `QueryBuilder` subclass. | ||
*/ | ||
Model.QueryBuilder = QueryBuilder; | ||
/** | ||
* Called after a model is updated. | ||
* | ||
* You can return a promise from this function if you need to do asynchronous stuff. | ||
* | ||
* This method is also called after a model is patched. Therefore all the model's properties | ||
* may not exist. You can check if the update operation is a patch by checking the `opt.patch` | ||
* boolean. | ||
* | ||
* Also note that this method is called only once when you do something like this: | ||
* | ||
* ```js | ||
* Person | ||
* .$query() | ||
* .patch({firstName: 'Jennifer'}) | ||
* .where('gender', 'female') | ||
* .then(function () { | ||
* ... | ||
* }); | ||
* ``` | ||
* | ||
* The example above updates all rows whose `gender` equals `female` but the `$beforeUpdate` | ||
* method is called only once for the `{firstName: 'Jennifer'}` model. This is because the | ||
* updating is done completely in the database and the affected models are never fetched | ||
* to the javascript side. | ||
* | ||
* @param {ModelOptions} opt | ||
* | ||
* @param {Object} queryContext | ||
* The context object of the update query. See {@link QueryBuilder#context}. | ||
* | ||
* @returns {Promise|*} | ||
*/ | ||
/** | ||
* QueryBuilder subclass to use in `$relatedQuery()` method. | ||
* | ||
* This constructor is used whenever a query builder is created using the `$relatedQuery()` method. | ||
* You can override this to use your own `QueryBuilder` subclass. | ||
*/ | ||
Model.RelatedQueryBuilder = QueryBuilder; | ||
}, { | ||
key: '$afterUpdate', | ||
value: function $afterUpdate(opt, queryContext) {} | ||
/** | ||
* one-to-many relation type. | ||
* | ||
* @type {OneToOneRelation} | ||
*/ | ||
Model.OneToOneRelation = OneToOneRelation; | ||
/** | ||
* Creates a query builder for this table. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* | ||
* Examples: | ||
* | ||
* Read models from the database: | ||
* | ||
* ```js | ||
* // Get all rows. | ||
* Person.query().then(function(allPersons) { | ||
* console.log('there are', allPersons.length, 'persons in the database'); | ||
* }); | ||
* | ||
* // Example of a more complex WHERE clause. This generates: | ||
* // SELECT * FROM "Person" WHERE ("firstName" = 'Jennifer' AND "age" < 30) OR ("firstName" = "Mark" AND "age" > 30) | ||
* Person | ||
* .query() | ||
* .where(function () { | ||
* this.where('firstName', 'Jennifer').where('age', '<', 30); | ||
* }) | ||
* .orWhere(function () { | ||
* this.where('firstName', 'Mark').where('age', '>', 30); | ||
* }) | ||
* .then(function (marksAndJennifers) { | ||
* console.log(marksAndJennifers); | ||
* }); | ||
* | ||
* // Get a subset of rows and fetch related models for each row. | ||
* Person | ||
* .query() | ||
* .where('age', '>', 60) | ||
* .eager('children.children.movies') | ||
* .then(function (oldPeople) { | ||
* console.log('some old person\'s grand child has appeared in', | ||
* oldPeople[0].children[0].children[0].movies.length, | ||
* 'movies'); | ||
* }); | ||
* ``` | ||
* | ||
* Insert models to the database: | ||
* | ||
* ```js | ||
* Person.query().insert({firstName: 'Sylvester', lastName: 'Stallone'}).then(function (sylvester) { | ||
* console.log(sylvester.fullName()); // --> 'Sylvester Stallone'. | ||
* }); | ||
* | ||
* // Batch insert. This only works on Postgresql as it is the only database that returns the | ||
* // identifiers of _all_ inserted rows. If you need to do batch inserts on other databases use | ||
* // *knex* directly. (See .knexQuery() method). | ||
* Person | ||
* .query() | ||
* .insert([ | ||
* {firstName: 'Arnold', lastName: 'Schwarzenegger'}, | ||
* {firstName: 'Sylvester', lastName: 'Stallone'} | ||
* ]) | ||
* .then(function (inserted) { | ||
* console.log(inserted[0].fullName()); // --> 'Arnold Schwarzenegger' | ||
* }); | ||
* ``` | ||
* | ||
* `update` and `patch` can be used to update models. Only difference between the mentioned methods | ||
* is that `update` validates the input objects using the model class's full jsonSchema and `patch` | ||
* ignores the `required` property of the schema. Use `update` when you want to update _all_ properties | ||
* of a model and `patch` when only a subset should be updated. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .update({firstName: 'Jennifer', lastName: 'Lawrence', age: 35}) | ||
* .where('id', jennifer.id) | ||
* .then(function (updatedJennifer) { | ||
* console.log('Jennifer is now', updatedJennifer.age, 'years old'); | ||
* }); | ||
* | ||
* // This will throw assuming that `firstName` or `lastName` is a required property for a Person. | ||
* Person.query().patch({age: 100}); | ||
* | ||
* // This will _not_ throw. | ||
* Person | ||
* .query() | ||
* .patch({age: 100}) | ||
* .then(function () { | ||
* console.log('Everyone is now 100 years old'); | ||
* }); | ||
* ``` | ||
* | ||
* Models can be deleted using the delete method. Naturally the delete query can be chained with | ||
* any *knex* methods: | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .delete() | ||
* .where('age', '>', 90) | ||
* .then(function () { | ||
* console.log('anyone over 90 is now removed from the database'); | ||
* }); | ||
* ``` | ||
* | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* one-to-many relation type. | ||
* | ||
* @type {OneToManyRelation} | ||
*/ | ||
Model.OneToManyRelation = OneToManyRelation; | ||
}], [{ | ||
key: 'query', | ||
value: function query() { | ||
var ModelClass = this; | ||
/** | ||
* may-to-many relation type. | ||
* | ||
* @type {ManyToManyRelation} | ||
*/ | ||
Model.ManyToManyRelation = ManyToManyRelation; | ||
return ModelClass.QueryBuilder.forClass(ModelClass).relateImpl(function () { | ||
throw new Error('`relate` makes no sense in this context'); | ||
}).unrelateImpl(function () { | ||
throw new Error('`unrelate` makes no sense in this context'); | ||
}); | ||
} | ||
/** | ||
* Name of the database table of this model. | ||
* | ||
* @type {String} | ||
*/ | ||
Model.tableName = null; | ||
/** | ||
* Get/Set the knex instance for this model class. | ||
* | ||
* Subclasses inherit the connection. A system-wide knex instance can thus be set by calling | ||
* `Model.knex(knex)`. This works even after subclasses have been created. | ||
* | ||
* ```js | ||
* var knex = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database.db' | ||
* } | ||
* }); | ||
* | ||
* Model.knex(knex); | ||
* knex = Model.knex(); | ||
* ``` | ||
* | ||
* @param {knex=} knex | ||
* The knex to set. | ||
* | ||
* @returns {knex|undefined} | ||
*/ | ||
/** | ||
* Name of the primary key column in the database table. | ||
* | ||
* Defaults to 'id'. | ||
* | ||
* @type {String} | ||
*/ | ||
Model.idColumn = 'id'; | ||
}, { | ||
key: 'knex', | ||
value: function knex(_knex) { | ||
if (arguments.length) { | ||
this.$$knex = _knex; | ||
} else { | ||
var modelClass = this; | ||
/** | ||
* Name of the property used to store a temporary non-db identifier for the model. | ||
* | ||
* Defaults to '#id'. | ||
* | ||
* @type {String} | ||
*/ | ||
Model.uidProp = '#id'; | ||
while (modelClass && !modelClass.$$knex) { | ||
var proto = modelClass.prototype.__proto__; | ||
modelClass = proto && proto.constructor; | ||
} | ||
/** | ||
* Name of the property used to store a reference to a `uidProp`. | ||
* | ||
* Defaults to '#ref'. | ||
* | ||
* @type {String} | ||
*/ | ||
Model.uidRefProp = '#ref'; | ||
return modelClass && modelClass.$$knex; | ||
} | ||
} | ||
/** | ||
* Regular expression for parsing a reference to a property. | ||
* | ||
* @type {RegExp} | ||
*/ | ||
Model.propRefRegex = /#ref{([^\.]+)\.([^}]+)}/g; | ||
/** | ||
* Shortcut for `SomeModel.knex().raw()`. | ||
*/ | ||
/** | ||
* Properties that should be saved to database as JSON strings. | ||
* | ||
* The properties listed here are serialized to JSON strings upon insertion to the database | ||
* and parsed back to objects when models are read from the database. Combined with the | ||
* postgresql's json or jsonb data type, this is a powerful way of representing documents | ||
* as single database rows. | ||
* | ||
* If this property is left unset all properties declared as objects or arrays in the | ||
* `jsonSchema` are implicitly added to this list. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* Person.jsonAttributes = ['address']; | ||
* | ||
* var jennifer = Person.fromJson({ | ||
* name: 'Jennifer', | ||
* address: { | ||
* address: 'Someroad 10', | ||
* zipCode: '1234', | ||
* city: 'Los Angeles' | ||
* } | ||
* }); | ||
* | ||
* var dbRow = jennifer.$toDatabaseJson(); | ||
* console.log(dbRow); | ||
* // --> {name: 'Jennifer', address: '{"address":"Someroad 10","zipCode":"1234","city":"Los Angeles"}'} | ||
* ``` | ||
* | ||
* @type {Array.<String>} | ||
*/ | ||
Model.jsonAttributes = null; | ||
}, { | ||
key: 'raw', | ||
value: function raw() { | ||
var knex = this.knex(); | ||
return knex.raw.apply(knex, arguments); | ||
} | ||
/** | ||
* This property defines the relations to other models. | ||
* | ||
* Relations to other models can be defined by setting this property. The best way to explain how to | ||
* do this is by example: | ||
* | ||
* ```js | ||
* Person.relationMappings = { | ||
* pets: { | ||
* relation: Model.OneToManyRelation, | ||
* modelClass: Animal, | ||
* join: { | ||
* from: 'Person.id', | ||
* to: 'Animal.ownerId' | ||
* } | ||
* }, | ||
* | ||
* father: { | ||
* relation: Model.OneToOneRelation, | ||
* modelClass: Person, | ||
* join: { | ||
* from: 'Person.fatherId', | ||
* to: 'Person.id' | ||
* } | ||
* }, | ||
* | ||
* movies: { | ||
* relation: Model.ManyToManyRelation, | ||
* modelClass: Movie, | ||
* join: { | ||
* from: 'Person.id', | ||
* through: { | ||
* from: 'Person_Movie.actorId', | ||
* to: 'Person_Movie.movieId' | ||
* }, | ||
* to: 'Movie.id' | ||
* } | ||
* } | ||
* }; | ||
* ``` | ||
* | ||
* relationMappings is an object whose keys are relation names and values define the relation. The | ||
* `join` property in addition to the relation type define how the models are related to one another. | ||
* The `from` and `to` properties of the `join` object define the database columns through which the | ||
* models are associated. Note that neither of these columns need to be primary keys. They can be any | ||
* columns!. In the case of ManyToManyRelation also the join table needs to be defined. This is | ||
* done using the `through` object. | ||
* | ||
* The `modelClass` passed to the relation mappings is the class of the related model. It can be either | ||
* a Model subclass constructor or an absolute path to a module that exports one. Using file paths | ||
* is a handy way to prevent require loops. | ||
* | ||
* @type {Object.<String, RelationMapping>} | ||
*/ | ||
Model.relationMappings = null; | ||
/** | ||
* Shortcut for `SomeModel.knex().fn`. | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
Model.$$knex = null; | ||
}, { | ||
key: 'fn', | ||
value: function fn() { | ||
var knex = this.knex(); | ||
return knex.fn; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
Model.$$relations = null; | ||
/** | ||
* Shortcut for `SomeModel.knex().client.formatter()`. | ||
* | ||
* @return {Formatter} | ||
*/ | ||
/** | ||
* Creates a query builder for this table. | ||
* | ||
* The returned query builder has all the methods a *knex* query builder has. See | ||
* {@link QueryBuilder} and <a href="http://knexjs.org/#Builder">knexjs.org</a> | ||
* for more information. | ||
* | ||
* Examples: | ||
* | ||
* Read models from the database: | ||
* | ||
* ```js | ||
* // Get all rows. | ||
* Person.query().then(function(allPersons) { | ||
* console.log('there are', allPersons.length, 'persons in the database'); | ||
* }); | ||
* | ||
* // Example of a more complex WHERE clause. This generates: | ||
* // SELECT * FROM "Person" WHERE ("firstName" = 'Jennifer' AND "age" < 30) OR ("firstName" = "Mark" AND "age" > 30) | ||
* Person | ||
* .query() | ||
* .where(function () { | ||
* this.where('firstName', 'Jennifer').where('age', '<', 30); | ||
* }) | ||
* .orWhere(function () { | ||
* this.where('firstName', 'Mark').where('age', '>', 30); | ||
* }) | ||
* .then(function (marksAndJennifers) { | ||
* console.log(marksAndJennifers); | ||
* }); | ||
* | ||
* // Get a subset of rows and fetch related models for each row. | ||
* Person | ||
* .query() | ||
* .where('age', '>', 60) | ||
* .eager('children.children.movies') | ||
* .then(function (oldPeople) { | ||
* console.log('some old person\'s grand child has appeared in', | ||
* oldPeople[0].children[0].children[0].movies.length, | ||
* 'movies'); | ||
* }); | ||
* ``` | ||
* | ||
* Insert models to the database: | ||
* | ||
* ```js | ||
* Person.query().insert({firstName: 'Sylvester', lastName: 'Stallone'}).then(function (sylvester) { | ||
* console.log(sylvester.fullName()); // --> 'Sylvester Stallone'. | ||
* }); | ||
* | ||
* // Batch insert. This only works on Postgresql as it is the only database that returns the | ||
* // identifiers of _all_ inserted rows. If you need to do batch inserts on other databases use | ||
* // *knex* directly. (See .knexQuery() method). | ||
* Person | ||
* .query() | ||
* .insert([ | ||
* {firstName: 'Arnold', lastName: 'Schwarzenegger'}, | ||
* {firstName: 'Sylvester', lastName: 'Stallone'} | ||
* ]) | ||
* .then(function (inserted) { | ||
* console.log(inserted[0].fullName()); // --> 'Arnold Schwarzenegger' | ||
* }); | ||
* ``` | ||
* | ||
* `update` and `patch` can be used to update models. Only difference between the mentioned methods | ||
* is that `update` validates the input objects using the model class's full jsonSchema and `patch` | ||
* ignores the `required` property of the schema. Use `update` when you want to update _all_ properties | ||
* of a model and `patch` when only a subset should be updated. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .update({firstName: 'Jennifer', lastName: 'Lawrence', age: 35}) | ||
* .where('id', jennifer.id) | ||
* .then(function (updatedJennifer) { | ||
* console.log('Jennifer is now', updatedJennifer.age, 'years old'); | ||
* }); | ||
* | ||
* // This will throw assuming that `firstName` or `lastName` is a required property for a Person. | ||
* Person.query().patch({age: 100}); | ||
* | ||
* // This will _not_ throw. | ||
* Person | ||
* .query() | ||
* .patch({age: 100}) | ||
* .then(function () { | ||
* console.log('Everyone is now 100 years old'); | ||
* }); | ||
* ``` | ||
* | ||
* Models can be deleted using the delete method. Naturally the delete query can be chained with | ||
* any *knex* methods: | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .delete() | ||
* .where('age', '>', 90) | ||
* .then(function () { | ||
* console.log('anyone over 90 is now removed from the database'); | ||
* }); | ||
* ``` | ||
* | ||
* @returns {QueryBuilder} | ||
*/ | ||
Model.query = function () { | ||
var ModelClass = this; | ||
}, { | ||
key: 'formatter', | ||
value: function formatter() { | ||
return this.knex().client.formatter(); | ||
} | ||
return ModelClass.QueryBuilder | ||
.forClass(ModelClass) | ||
.relateImpl(function () { | ||
throw new Error('`relate` makes no sense in this context'); | ||
}) | ||
.unrelateImpl(function () { | ||
throw new Error('`unrelate` makes no sense in this context'); | ||
}); | ||
}; | ||
/** | ||
* Shortcut for `SomeModel.knex().table(SomeModel.tableName)`. | ||
* | ||
* @returns {knex.QueryBuilder} | ||
*/ | ||
/** | ||
* Get/Set the knex instance for this model class. | ||
* | ||
* Subclasses inherit the connection. A system-wide knex instance can thus be set by calling | ||
* `Model.knex(knex)`. This works even after subclasses have been created. | ||
* | ||
* ```js | ||
* var knex = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database.db' | ||
* } | ||
* }); | ||
* | ||
* Model.knex(knex); | ||
* knex = Model.knex(); | ||
* ``` | ||
* | ||
* @param {knex=} knex | ||
* The knex to set. | ||
* | ||
* @returns {knex|undefined} | ||
*/ | ||
Model.knex = function (knex) { | ||
if (arguments.length) { | ||
this.$$knex = knex; | ||
} else { | ||
var modelClass = this; | ||
while (modelClass && !modelClass.$$knex) { | ||
var proto = modelClass.prototype.__proto__; | ||
modelClass = proto && proto.constructor; | ||
}, { | ||
key: 'knexQuery', | ||
value: function knexQuery() { | ||
return this.knex().table(this.tableName); | ||
} | ||
return modelClass && modelClass.$$knex; | ||
} | ||
}; | ||
/** | ||
* Creates a subclass of this class that is bound to the given knex. | ||
* | ||
* This method can be used to bind a Model subclass to multiple databases for example in | ||
* a multi tenant system. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* var knex1 = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database1.db' | ||
* } | ||
* }); | ||
* | ||
* var knex2 = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database2.db' | ||
* } | ||
* }); | ||
* | ||
* SomeModel.knex(null); | ||
* | ||
* var BoundModel1 = SomeModel.bindKnex(knex1); | ||
* var BoundModel2 = SomeModel.bindKnex(knex2); | ||
* | ||
* // Throws since the knex instance is null. | ||
* SomeModel.query().then(); | ||
* | ||
* // Works. | ||
* BoundModel1.query().then(function (models) { | ||
* console.log(models[0] instanceof SomeModel); // --> true | ||
* console.log(models[0] instanceof BoundModel1); // --> true | ||
* }); | ||
* | ||
* // Works. | ||
* BoundModel2.query().then(function (models) { | ||
* console.log(models[0] instanceof SomeModel); // --> true | ||
* console.log(models[0] instanceof BoundModel2); // --> true | ||
* }); | ||
* | ||
* ``` | ||
* | ||
* @param {knex} knex | ||
* @returns {Model} | ||
*/ | ||
/** | ||
* Shortcut for `SomeModel.knex().raw()`. | ||
*/ | ||
Model.raw = function () { | ||
var knex = this.knex(); | ||
return knex.raw.apply(knex, arguments); | ||
}; | ||
}, { | ||
key: 'bindKnex', | ||
value: function bindKnex(knex) { | ||
var ModelClass = this; | ||
/** | ||
* Shortcut for `SomeModel.knex().fn`. | ||
*/ | ||
Model.fn = function () { | ||
var knex = this.knex(); | ||
return knex.fn; | ||
}; | ||
if (!knex.$$objection) { | ||
knex.$$objection = {}; | ||
knex.$$objection.id = _lodash2.default.uniqueId(); | ||
knex.$$objection.boundModels = (0, _create2.default)(null); | ||
} | ||
/** | ||
* Shortcut for `SomeModel.knex().client.formatter()`. | ||
* | ||
* @return {Formatter} | ||
*/ | ||
Model.formatter = function () { | ||
return this.knex().client.formatter(); | ||
}; | ||
// Check if this model class has already been bound to the given knex. | ||
if (knex.$$objection.boundModels[ModelClass.tableName]) { | ||
return knex.$$objection.boundModels[ModelClass.tableName]; | ||
} | ||
/** | ||
* Shortcut for `SomeModel.knex().table(SomeModel.tableName)`. | ||
* | ||
* @returns {knex.QueryBuilder} | ||
*/ | ||
Model.knexQuery = function () { | ||
return this.knex().table(this.tableName); | ||
}; | ||
// Create a new subclass of this class. | ||
var BoundModelClass = (0, _inheritModel2.default)(ModelClass); | ||
/** | ||
* Creates a subclass of this class that is bound to the given knex. | ||
* | ||
* This method can be used to bind a Model subclass to multiple databases for example in | ||
* a multi tenant system. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* var knex1 = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database1.db' | ||
* } | ||
* }); | ||
* | ||
* var knex2 = require('knex')({ | ||
* client: 'sqlite3', | ||
* connection: { | ||
* filename: 'database2.db' | ||
* } | ||
* }); | ||
* | ||
* SomeModel.knex(null); | ||
* | ||
* var BoundModel1 = SomeModel.bindKnex(knex1); | ||
* var BoundModel2 = SomeModel.bindKnex(knex2); | ||
* | ||
* // Throws since the knex instance is null. | ||
* SomeModel.query().then(); | ||
* | ||
* // Works. | ||
* BoundModel1.query().then(function (models) { | ||
* console.log(models[0] instanceof SomeModel); // --> true | ||
* console.log(models[0] instanceof BoundModel1); // --> true | ||
* }); | ||
* | ||
* // Works. | ||
* BoundModel2.query().then(function (models) { | ||
* console.log(models[0] instanceof SomeModel); // --> true | ||
* console.log(models[0] instanceof BoundModel2); // --> true | ||
* }); | ||
* | ||
* ``` | ||
* | ||
* @param {knex} knex | ||
* @returns {Model} | ||
*/ | ||
Model.bindKnex = function (knex) { | ||
var ModelClass = this; | ||
BoundModelClass.knex(knex); | ||
knex.$$objection.boundModels[ModelClass.tableName] = BoundModelClass; | ||
if (!knex.$$objection) { | ||
knex.$$objection = {}; | ||
knex.$$objection.id = _.uniqueId(); | ||
knex.$$objection.boundModels = Object.create(null); | ||
} | ||
BoundModelClass.$$relations = _lodash2.default.reduce(ModelClass.getRelations(), function (relations, relation, relationName) { | ||
relations[relationName] = relation.bindKnex(knex); | ||
return relations; | ||
}, (0, _create2.default)(null)); | ||
// Check if this model class has already been bound to the given knex. | ||
if (knex.$$objection.boundModels[ModelClass.tableName]) { | ||
return knex.$$objection.boundModels[ModelClass.tableName]; | ||
} | ||
return BoundModelClass; | ||
} | ||
// Create a new subclass of this class. | ||
var BoundModelClass = inheritModel(ModelClass); | ||
/** | ||
* Alias for bindKnex. | ||
* | ||
* ```js | ||
* var Person = require('./models/Person'); | ||
* var transaction; | ||
* | ||
* objection.transaction.start(Person).then(function (trx) { | ||
* transaction = trx; | ||
* return Person | ||
* .bindTransaction(transaction) | ||
* .query() | ||
* .insert({firstName: 'Jennifer'}); | ||
* }).then(function (jennifer) { | ||
* return Person | ||
* .bindTransaction(transaction) | ||
* .query() | ||
* .patch({lastName: 'Lawrence'}) | ||
* .where('id', jennifer.id); | ||
* }).then(function () { | ||
* return transaction.commit(); | ||
* }).catch(function () { | ||
* return transaction.rollback(); | ||
* }); | ||
* ``` | ||
* | ||
* @param trx | ||
* @returns {Model} | ||
*/ | ||
BoundModelClass.knex(knex); | ||
knex.$$objection.boundModels[ModelClass.tableName] = BoundModelClass; | ||
}, { | ||
key: 'bindTransaction', | ||
value: function bindTransaction(trx) { | ||
return this.bindKnex(trx); | ||
} | ||
BoundModelClass.$$relations = _.reduce(ModelClass.getRelations(), function (relations, relation, relationName) { | ||
relations[relationName] = relation.bindKnex(knex); | ||
return relations; | ||
}, Object.create(null)); | ||
/** | ||
* Ensures that the given model is an instance of this class. | ||
* | ||
* If `model` is already an instance of this class, nothing is done. | ||
* | ||
* @param {Model|Object} model | ||
* @param {ModelOptions=} options | ||
* @returns {Model} | ||
*/ | ||
return BoundModelClass; | ||
}; | ||
}, { | ||
key: 'ensureModel', | ||
value: function ensureModel(model, options) { | ||
var ModelClass = this; | ||
/** | ||
* Alias for bindKnex. | ||
* | ||
* ```js | ||
* var Person = require('./models/Person'); | ||
* var transaction; | ||
* | ||
* objection.transaction.start(Person).then(function (trx) { | ||
* transaction = trx; | ||
* return Person | ||
* .bindTransaction(transaction) | ||
* .query() | ||
* .insert({firstName: 'Jennifer'}); | ||
* }).then(function (jennifer) { | ||
* return Person | ||
* .bindTransaction(transaction) | ||
* .query() | ||
* .patch({lastName: 'Lawrence'}) | ||
* .where('id', jennifer.id); | ||
* }).then(function () { | ||
* return transaction.commit(); | ||
* }).catch(function () { | ||
* return transaction.rollback(); | ||
* }); | ||
* ``` | ||
* | ||
* @param trx | ||
* @returns {Model} | ||
*/ | ||
Model.bindTransaction = function (trx) { | ||
return this.bindKnex(trx); | ||
}; | ||
if (!model) { | ||
return null; | ||
} | ||
/** | ||
* Ensures that the given model is an instance of this class. | ||
* | ||
* If `model` is already an instance of this class, nothing is done. | ||
* | ||
* @param {Model|Object} model | ||
* @param {ModelOptions=} options | ||
* @returns {Model} | ||
*/ | ||
Model.ensureModel = function (model, options) { | ||
var ModelClass = this; | ||
if (model instanceof ModelClass) { | ||
return model; | ||
} else if (model instanceof Model) { | ||
throw new Error('model is already an instance of another Model'); | ||
} else { | ||
return ModelClass.fromJson(model, options); | ||
} | ||
} | ||
if (!model) { | ||
return null; | ||
} | ||
/** | ||
* Ensures that each element in the given array is an instance of this class. | ||
* | ||
* If an element is already an instance of this class, nothing is done for it. | ||
* | ||
* @param {Array.<Model|Object>} input | ||
* @param {ModelOptions=} options | ||
* @returns {Array.<Model>} | ||
*/ | ||
if (model instanceof ModelClass) { | ||
return model; | ||
} else if (model instanceof Model) { | ||
throw new Error('model is already an instance of another Model'); | ||
} else { | ||
return ModelClass.fromJson(model, options); | ||
} | ||
}; | ||
}, { | ||
key: 'ensureModelArray', | ||
value: function ensureModelArray(input, options) { | ||
var ModelClass = this; | ||
/** | ||
* Ensures that each element in the given array is an instance of this class. | ||
* | ||
* If an element is already an instance of this class, nothing is done for it. | ||
* | ||
* @param {Array.<Model|Object>} input | ||
* @param {ModelOptions=} options | ||
* @returns {Array.<Model>} | ||
*/ | ||
Model.ensureModelArray = function (input, options) { | ||
var ModelClass = this; | ||
if (!input) { | ||
return []; | ||
} | ||
if (!input) { | ||
return []; | ||
} | ||
if (_lodash2.default.isArray(input)) { | ||
var models = new Array(input.length); | ||
if (_.isArray(input)) { | ||
var models = new Array(input.length); | ||
for (var i = 0, l = input.length; i < l; ++i) { | ||
models[i] = ModelClass.ensureModel(input[i], options); | ||
} | ||
for (var i = 0, l = input.length; i < l; ++i) { | ||
models[i] = ModelClass.ensureModel(input[i], options); | ||
return models; | ||
} else { | ||
return [ModelClass.ensureModel(input, options)]; | ||
} | ||
} | ||
return models; | ||
} else { | ||
return [ModelClass.ensureModel(input, options)]; | ||
} | ||
}; | ||
/** | ||
* Returns the name of the identifier property. | ||
* | ||
* The identifier property is equal to the `idColumn` if `$parseDatabaseJson` is not | ||
* implemented. If `$parseDatabaseJson` is implemented it may change the id property's | ||
* name. This method passes the `idColumn` through `$parseDatabaseJson`. | ||
* | ||
* @returns {string} | ||
*/ | ||
/** | ||
* Returns the name of the identifier property. | ||
* | ||
* The identifier property is equal to the `idColumn` if `$parseDatabaseJson` is not | ||
* implemented. If `$parseDatabaseJson` is implemented it may change the id property's | ||
* name. This method passes the `idColumn` through `$parseDatabaseJson`. | ||
* | ||
* @returns {String} | ||
*/ | ||
Model.getIdProperty = function () { | ||
var idProperty = this.columnNameToPropertyName(this.idColumn); | ||
}, { | ||
key: 'getIdProperty', | ||
value: function getIdProperty() { | ||
var idProperty = this.columnNameToPropertyName(this.idColumn); | ||
if (!idProperty) { | ||
throw new Error(this.name + | ||
'.$parseDatabaseJson probably changes the value of the id column `' + this.idColumn + | ||
'` which is a no-no.'); | ||
} | ||
if (!idProperty) { | ||
throw new Error(this.name + '.$parseDatabaseJson probably changes the value of the id column `' + this.idColumn + '` which is a no-no.'); | ||
} | ||
return idProperty; | ||
}; | ||
return idProperty; | ||
} | ||
/** | ||
* Full identifier column name like 'SomeTable.id'. | ||
* | ||
* @returns {String} | ||
*/ | ||
Model.getFullIdColumn = function () { | ||
return this.tableName + '.' + this.idColumn; | ||
}; | ||
/** | ||
* Full identifier column name like 'SomeTable.id'. | ||
* | ||
* @returns {string} | ||
*/ | ||
/** | ||
* All relations of this class. | ||
* | ||
* @return {Object.<String, Relation>} | ||
*/ | ||
Model.getRelations = function () { | ||
var ModelClass = this; | ||
}, { | ||
key: 'getFullIdColumn', | ||
value: function getFullIdColumn() { | ||
return this.tableName + '.' + this.idColumn; | ||
} | ||
if (!this.$$relations) { | ||
// Lazy-load the relations to prevent require loops. | ||
this.$$relations = _.reduce(this.relationMappings, function (relations, mapping, relationName) { | ||
relations[relationName] = new mapping.relation(relationName, ModelClass); | ||
relations[relationName].setMapping(mapping); | ||
return relations; | ||
}, Object.create(null)); | ||
} | ||
/** | ||
* All relations of this class. | ||
* | ||
* @return {Object.<string, Relation>} | ||
*/ | ||
return this.$$relations; | ||
}; | ||
}, { | ||
key: 'getRelations', | ||
value: function getRelations() { | ||
var ModelClass = this; | ||
/** | ||
* Get a relation by name. | ||
* | ||
* This should not be used to make queries. Use `$relatedQuery` or `loadRelated` instead. | ||
* | ||
* @return {Relation} | ||
*/ | ||
Model.getRelation = function (name) { | ||
var relation = this.getRelations()[name]; | ||
if (!this.$$relations) { | ||
// Lazy-load the relations to prevent require loops. | ||
this.$$relations = _lodash2.default.reduce(this.relationMappings, function (relations, mapping, relationName) { | ||
relations[relationName] = new mapping.relation(relationName, ModelClass); | ||
relations[relationName].setMapping(mapping); | ||
return relations; | ||
}, (0, _create2.default)(null)); | ||
} | ||
if (!relation) { | ||
throw new Error("model class '" + this.name + "' doesn't have relation '" + name + "'"); | ||
} | ||
return this.$$relations; | ||
} | ||
return relation; | ||
}; | ||
/** | ||
* Get a relation by name. | ||
* | ||
* This should not be used to make queries. Use `$relatedQuery` or `loadRelated` instead. | ||
* | ||
* @return {Relation} | ||
*/ | ||
/** | ||
* Exactly like $loadRelated but for multiple instances. | ||
* | ||
* ```js | ||
* Person.loadRelated([person1, person2], 'children.pets').then(function (persons) { | ||
* var person1 = persons[0]; | ||
* var person2 = persons[1]; | ||
* }); | ||
* ``` | ||
* | ||
* Relations can be filtered by giving named filter functions as arguments | ||
* to the relations: | ||
* | ||
* ```js | ||
* Person | ||
* .loadRelated([person1, person2], 'children(orderByAge).[pets(onlyDogs, orderByName), movies]', { | ||
* orderByAge: function (builder) { | ||
* builder.orderBy('age') | ||
* }, | ||
* orderByName: function (builder) { | ||
* builder.orderBy('name'); | ||
* }, | ||
* onlyDogs: function (builder) { | ||
* builder.where('species', 'dog') | ||
* } | ||
* }) | ||
* .then(function (persons) { | ||
* console.log(persons[1].children.pets[0]); | ||
* }); | ||
* ``` | ||
* | ||
* @param {Array.<Model|Object>} $models | ||
* @param {String|RelationExpression} expression | ||
* @param {Object.<String, Function(QueryBuilder)>=} filters | ||
* @returns {Promise} | ||
*/ | ||
Model.loadRelated = function ($models, expression, filters) { | ||
if (!(expression instanceof RelationExpression)) { | ||
expression = RelationExpression.parse(expression); | ||
} | ||
}, { | ||
key: 'getRelation', | ||
value: function getRelation(name) { | ||
var relation = this.getRelations()[name]; | ||
if (!expression) { | ||
throw new Error('invalid expression ' + expression); | ||
} | ||
if (!relation) { | ||
throw new Error("model class '" + this.name + "' doesn't have relation '" + name + "'"); | ||
} | ||
return new EagerFetcher({ | ||
modelClass: this, | ||
models: this.ensureModelArray($models), | ||
eager: expression, | ||
filters: filters | ||
}).fetch().then(function (models) { | ||
return _.isArray($models) ? models : models[0]; | ||
}); | ||
}; | ||
return relation; | ||
} | ||
/** | ||
* Traverses the relation tree of a list of models. | ||
* | ||
* Calls the callback for each related model recursively. The callback is called | ||
* also for the input models themselves. | ||
* | ||
* There are two ways to call this method: | ||
* | ||
* ```js | ||
* Model.traverse(models, function (model, parentModel, relationName) { | ||
* doSomething(model); | ||
* }); | ||
* ``` | ||
* | ||
* and | ||
* | ||
* ```js | ||
* Model.traverse(Person, models, function (person, parentModel, relationName) { | ||
* doSomethingForPerson(person); | ||
* }); | ||
* ``` | ||
* | ||
* In the second example the traverser function is only called for `Person` instances. | ||
* | ||
* @param {Function=} filterConstructor | ||
* If this optional constructor is given, the `traverser` is only called for | ||
* models for which `model instanceof filterConstructor` returns true. | ||
* | ||
* @param {Model|Array.<Model>} models | ||
* The model(s) whose relation trees to traverse. | ||
* | ||
* @param {function(Model, Model, String)} traverser | ||
* The traverser function that is called for each model. The first argument | ||
* is the model itself. If the model is in a relation of some other model | ||
* the second argument is the parent model and the third argument is the | ||
* name of the relation. | ||
* | ||
* @return {Model} | ||
*/ | ||
Model.traverse = function (filterConstructor, models, traverser) { | ||
filterConstructor = filterConstructor || null; | ||
/** | ||
* Exactly like $loadRelated but for multiple instances. | ||
* | ||
* ```js | ||
* Person.loadRelated([person1, person2], 'children.pets').then(function (persons) { | ||
* var person1 = persons[0]; | ||
* var person2 = persons[1]; | ||
* }); | ||
* ``` | ||
* | ||
* Relations can be filtered by giving named filter functions as arguments | ||
* to the relations: | ||
* | ||
* ```js | ||
* Person | ||
* .loadRelated([person1, person2], 'children(orderByAge).[pets(onlyDogs, orderByName), movies]', { | ||
* orderByAge: function (builder) { | ||
* builder.orderBy('age') | ||
* }, | ||
* orderByName: function (builder) { | ||
* builder.orderBy('name'); | ||
* }, | ||
* onlyDogs: function (builder) { | ||
* builder.where('species', 'dog') | ||
* } | ||
* }) | ||
* .then(function (persons) { | ||
* console.log(persons[1].children.pets[0]); | ||
* }); | ||
* ``` | ||
* | ||
* @param {Array.<Model|Object>} $models | ||
* @param {string|RelationExpression} expression | ||
* @param {Object.<string, function(QueryBuilder)>=} filters | ||
* @returns {Promise} | ||
*/ | ||
if (_.isUndefined(traverser)) { | ||
traverser = models; | ||
models = filterConstructor; | ||
filterConstructor = null; | ||
} | ||
}, { | ||
key: 'loadRelated', | ||
value: function loadRelated($models, expression, filters) { | ||
if (!(expression instanceof _RelationExpression2.default)) { | ||
expression = _RelationExpression2.default.parse(expression); | ||
} | ||
if (!_.isFunction(traverser)) { | ||
throw new Error('traverser must be a function'); | ||
} | ||
return new _EagerFetcher2.default({ | ||
modelClass: this, | ||
models: this.ensureModelArray($models), | ||
eager: expression, | ||
filters: filters | ||
}).fetch().then(function (models) { | ||
return _lodash2.default.isArray($models) ? models : models[0]; | ||
}); | ||
} | ||
traverse(models, null, null, filterConstructor, traverser); | ||
return this; | ||
}; | ||
/** | ||
* Traverses the relation tree of a list of models. | ||
* | ||
* Calls the callback for each related model recursively. The callback is called | ||
* also for the input models themselves. | ||
* | ||
* There are two ways to call this method: | ||
* | ||
* ```js | ||
* Model.traverse(models, function (model, parentModel, relationName) { | ||
* doSomething(model); | ||
* }); | ||
* ``` | ||
* | ||
* and | ||
* | ||
* ```js | ||
* Model.traverse(Person, models, function (person, parentModel, relationName) { | ||
* doSomethingForPerson(person); | ||
* }); | ||
* ``` | ||
* | ||
* In the second example the traverser function is only called for `Person` instances. | ||
* | ||
* @param {function=} filterConstructor | ||
* If this optional constructor is given, the `traverser` is only called for | ||
* models for which `model instanceof filterConstructor` returns true. | ||
* | ||
* @param {Model|Array.<Model>} models | ||
* The model(s) whose relation trees to traverse. | ||
* | ||
* @param {function(Model, Model, string)} traverser | ||
* The traverser function that is called for each model. The first argument | ||
* is the model itself. If the model is in a relation of some other model | ||
* the second argument is the parent model and the third argument is the | ||
* name of the relation. | ||
* | ||
* @return {Model} | ||
*/ | ||
/** | ||
* @protected | ||
* @returns {Array.<String>} | ||
*/ | ||
Model.$$getJsonAttributes = function () { | ||
var self = this; | ||
}, { | ||
key: 'traverse', | ||
value: function traverse(filterConstructor, models, traverser) { | ||
filterConstructor = filterConstructor || null; | ||
// If the jsonAttributes property is not set, try to create it based | ||
// on the jsonSchema. All properties that are objects or arrays must | ||
// be converted to JSON. | ||
if (!this.jsonAttributes && this.jsonSchema) { | ||
this.jsonAttributes = []; | ||
if (_lodash2.default.isUndefined(traverser)) { | ||
traverser = models; | ||
models = filterConstructor; | ||
filterConstructor = null; | ||
} | ||
_.each(this.jsonSchema.properties, function (prop, propName) { | ||
var types = _.compact(ensureArray(prop.type)); | ||
if (types.length === 0 && _.isArray(prop.anyOf)) { | ||
types = _.flattenDeep(_.pluck(prop.anyOf, 'type')); | ||
if (!_lodash2.default.isFunction(traverser)) { | ||
throw new Error('traverser must be a function'); | ||
} | ||
if (types.length === 0 && _.isArray(prop.oneOf)) { | ||
types = _.flattenDeep(_.pluck(prop.oneOf, 'type')); | ||
_traverse(models, null, null, filterConstructor, traverser); | ||
return this; | ||
} | ||
/** | ||
* @protected | ||
* @returns {Array.<string>} | ||
*/ | ||
}, { | ||
key: '$$getJsonAttributes', | ||
value: function $$getJsonAttributes() { | ||
var _this4 = this; | ||
// If the jsonAttributes property is not set, try to create it based | ||
// on the jsonSchema. All properties that are objects or arrays must | ||
// be converted to JSON. | ||
if (!this.jsonAttributes && this.jsonSchema) { | ||
this.jsonAttributes = []; | ||
_lodash2.default.each(this.jsonSchema.properties, function (prop, propName) { | ||
var types = _lodash2.default.compact(ensureArray(prop.type)); | ||
if (types.length === 0 && _lodash2.default.isArray(prop.anyOf)) { | ||
types = _lodash2.default.flattenDeep(_lodash2.default.pluck(prop.anyOf, 'type')); | ||
} | ||
if (types.length === 0 && _lodash2.default.isArray(prop.oneOf)) { | ||
types = _lodash2.default.flattenDeep(_lodash2.default.pluck(prop.oneOf, 'type')); | ||
} | ||
if (_lodash2.default.contains(types, 'object') || _lodash2.default.contains(types, 'array')) { | ||
_this4.jsonAttributes.push(propName); | ||
} | ||
}); | ||
} | ||
if (_.contains(types, 'object') || _.contains(types, 'array')) { | ||
self.jsonAttributes.push(propName); | ||
if (!_lodash2.default.isArray(this.jsonAttributes)) { | ||
this.jsonAttributes = []; | ||
} | ||
}); | ||
} | ||
if (!_.isArray(this.jsonAttributes)) { | ||
this.jsonAttributes = []; | ||
} | ||
return this.jsonAttributes; | ||
} | ||
}]); | ||
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); | ||
return this.jsonAttributes; | ||
}; | ||
/** | ||
* @private | ||
*/ | ||
exports.default = Model; | ||
function ensureArray(obj) { | ||
if (_.isArray(obj)) { | ||
if (_lodash2.default.isArray(obj)) { | ||
return obj; | ||
@@ -1408,8 +1545,8 @@ } else { | ||
*/ | ||
function traverse(models, parent, relationName, modelClass, callback) { | ||
if (!_.isObject(models)) { | ||
function _traverse(models, parent, relationName, modelClass, callback) { | ||
if (!_lodash2.default.isObject(models)) { | ||
return; | ||
} | ||
if (_.isArray(models)) { | ||
if (_lodash2.default.isArray(models)) { | ||
for (var i = 0, l = models.length; i < l; ++i) { | ||
@@ -1419,3 +1556,3 @@ traverseOne(models[i], parent, relationName, modelClass, callback); | ||
} else { | ||
traverseOne(models, parent, relationName, modelClass, callback) | ||
traverseOne(models, parent, relationName, modelClass, callback); | ||
} | ||
@@ -1437,8 +1574,6 @@ } | ||
for (var relName in model.constructor.getRelations()) { | ||
if (_.has(model, relName)) { | ||
traverse(model[relName], model, relName, modelClass, callback); | ||
if (_lodash2.default.has(model, relName)) { | ||
_traverse(model[relName], model, relName, modelClass, callback); | ||
} | ||
} | ||
} | ||
module.exports = Model; | ||
} |
@@ -1,17 +0,48 @@ | ||
"use strict"; | ||
'use strict'; | ||
var _ = require('lodash') | ||
, tv4 = require('tv4') | ||
, tv4Formats = require('tv4-formats') | ||
, utils = require('./../utils') | ||
, ValidationError = require('./../ValidationError'); | ||
var _class, _temp; | ||
// Add validation formats, so that for example the following schema validation works: | ||
// createTime: {type: 'string', format: 'date-time'} | ||
tv4.addFormat(tv4Formats); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _tv = require('tv4'); | ||
var _tv2 = _interopRequireDefault(_tv); | ||
var _tv4Formats = require('tv4-formats'); | ||
var _tv4Formats2 = _interopRequireDefault(_tv4Formats); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _ValidationError = require('../ValidationError'); | ||
var _ValidationError2 = _interopRequireDefault(_ValidationError); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* @typedef {Object} ModelOptions | ||
* | ||
* @property {Boolean} [patch] | ||
* @property {boolean} [patch] | ||
* If true the json is treated as a patch and the `required` field of the json schema is | ||
@@ -21,3 +52,3 @@ * ignored in the validation. This allows us to create models with a subset of required | ||
* | ||
* @property {Boolean} [skipValidation] | ||
* @property {boolean} [skipValidation] | ||
* If true the json schema validation is skipped. | ||
@@ -104,589 +135,638 @@ */ | ||
*/ | ||
function ModelBase() { | ||
// Nothing to do here. | ||
} | ||
var ModelBase = (_temp = _class = (function () { | ||
function ModelBase() { | ||
(0, _classCallCheck3.default)(this, ModelBase); | ||
} | ||
/** | ||
* This is called before validation. | ||
* | ||
* Here you can dynamically edit the jsonSchema if needed. | ||
* | ||
* @param {Object} jsonSchema | ||
* A deep clone of this class's jsonSchema. | ||
* | ||
* @param {Object} json | ||
* The JSON object to be validated. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The (possibly) modified jsonSchema. | ||
*/ | ||
ModelBase.prototype.$beforeValidate = function (jsonSchema, json, options) { | ||
/* istanbul ignore next */ | ||
return jsonSchema; | ||
}; | ||
(0, _createClass3.default)(ModelBase, [{ | ||
key: '$beforeValidate', | ||
/** | ||
* Validates the given JSON object. | ||
* | ||
* Calls `$beforeValidation` and `$afterValidation` methods. This method is called | ||
* automatically from `fromJson` and `$setJson` methods. This method can also be | ||
* called explicitly when needed. | ||
* | ||
* @throws {ValidationError} | ||
* If validation fails. | ||
* | ||
* @param {Object=} json | ||
* If not given ==> this. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The input json | ||
*/ | ||
ModelBase.prototype.$validate = function (json, options) { | ||
var ModelClass = this.constructor; | ||
var jsonSchema = ModelClass.jsonSchema; | ||
var required; | ||
/** | ||
* This is called before validation. | ||
* | ||
* Here you can dynamically edit the jsonSchema if needed. | ||
* | ||
* @param {Object} jsonSchema | ||
* A deep clone of this class's jsonSchema. | ||
* | ||
* @param {Object} json | ||
* The JSON object to be validated. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The (possibly) modified jsonSchema. | ||
*/ | ||
options = options || {}; | ||
json = json || this; | ||
/** | ||
* @private | ||
*/ | ||
value: function $beforeValidate(jsonSchema, json, options) { | ||
/* istanbul ignore next */ | ||
return jsonSchema; | ||
} | ||
if (!jsonSchema || options.skipValidation) { | ||
return json; | ||
} | ||
/** | ||
* Validates the given JSON object. | ||
* | ||
* Calls `$beforeValidation` and `$afterValidation` methods. This method is called | ||
* automatically from `fromJson` and `$setJson` methods. This method can also be | ||
* called explicitly when needed. | ||
* | ||
* @throws {ValidationError} | ||
* If validation fails. | ||
* | ||
* @param {Object=} json | ||
* If not given ==> this. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The input json | ||
*/ | ||
// No need to call $beforeValidate (and clone the jsonSchema) if $beforeValidate has not been overwritten. | ||
if (this.$beforeValidate !== ModelBase.prototype.$beforeValidate) { | ||
jsonSchema = _.cloneDeep(jsonSchema); | ||
jsonSchema = this.$beforeValidate(jsonSchema, json, options); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
var report = tryValidate(jsonSchema, json, options); | ||
var validationError = parseValidationError(report); | ||
/** | ||
* The optional schema against which the JSON is validated. | ||
* | ||
* The jsonSchema can be dynamically modified in the `$beforeValidate` method. | ||
* | ||
* Must follow http://json-schema.org specification. If null no validation is done. | ||
* | ||
* @see $beforeValidate() | ||
* @see $validate() | ||
* @see $afterValidate() | ||
* | ||
* @type {Object} | ||
*/ | ||
if (validationError) { | ||
throw validationError; | ||
} | ||
}, { | ||
key: '$validate', | ||
value: function $validate() { | ||
var json = arguments.length <= 0 || arguments[0] === undefined ? this : arguments[0]; | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
this.$afterValidate(json, options); | ||
return json; | ||
}; | ||
var ModelClass = this.constructor; | ||
var jsonSchema = ModelClass.jsonSchema; | ||
/** | ||
* This is called after successful validation. | ||
* | ||
* You can do further validation here and throw a ValidationError if something goes wrong. | ||
* | ||
* @param {Object=} json | ||
* The JSON object to validate. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
*/ | ||
ModelBase.prototype.$afterValidate = function (json, options) { | ||
// Do nothing by default. | ||
}; | ||
if (!jsonSchema || options.skipValidation) { | ||
return json; | ||
} | ||
/** | ||
* This is called when a ModelBase is created from a database JSON object. | ||
* | ||
* Converts the JSON object from the database format to the internal format. | ||
* | ||
* @note This function must handle the case where any subset of the columns comes | ||
* in the `json` argument. You cannot assume that all columns are present as it | ||
* depends on the select statement. There can also be additional columns because | ||
* of join clauses, aliases etc. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in database format. | ||
* | ||
* @return {Object} | ||
* The JSON object in internal format. | ||
*/ | ||
ModelBase.prototype.$parseDatabaseJson = function (json) { | ||
return json; | ||
}; | ||
// No need to call $beforeValidate (and clone the jsonSchema) if $beforeValidate has not been overwritten. | ||
if (this.$beforeValidate !== ModelBase.prototype.$beforeValidate) { | ||
jsonSchema = _lodash2.default.cloneDeep(jsonSchema); | ||
jsonSchema = this.$beforeValidate(jsonSchema, json, options); | ||
} | ||
/** | ||
* This is called when a ModelBase is converted to database format. | ||
* | ||
* Converts the JSON object from the internal format to the database format. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in internal format. | ||
* | ||
* @return {Object} | ||
* The JSON object in database format. | ||
*/ | ||
ModelBase.prototype.$formatDatabaseJson = function (json) { | ||
return json; | ||
}; | ||
var report = tryValidate(jsonSchema, json, options); | ||
var validationError = parseValidationError(report); | ||
/** | ||
* This is called when a ModelBase is created from a JSON object. | ||
* | ||
* Converts the JSON object to the internal format. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in external format. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The JSON object in internal format. | ||
*/ | ||
ModelBase.prototype.$parseJson = function (json, options) { | ||
return json; | ||
}; | ||
if (validationError) { | ||
throw validationError; | ||
} | ||
/** | ||
* This is called when a ModelBase is converted to JSON. | ||
* | ||
* @note Remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in internal format | ||
* | ||
* @return {Object} | ||
* The JSON object in external format. | ||
*/ | ||
ModelBase.prototype.$formatJson = function (json) { | ||
return json; | ||
}; | ||
this.$afterValidate(json, options); | ||
return json; | ||
} | ||
/** | ||
* Exports this model as a database JSON object. | ||
* | ||
* Calls `$formatDatabaseJson()`. | ||
* | ||
* @return {Object} | ||
* This model as a JSON object in database format. | ||
*/ | ||
ModelBase.prototype.$toDatabaseJson = function () { | ||
return this.$$toJson(true, null, null); | ||
}; | ||
/** | ||
* This is called after successful validation. | ||
* | ||
* You can do further validation here and throw a ValidationError if something goes wrong. | ||
* | ||
* @param {Object=} json | ||
* The JSON object to validate. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
*/ | ||
/** | ||
* Exports this model as a JSON object. | ||
* | ||
* Calls `$formatJson()`. | ||
* | ||
* @return {Object} | ||
* This model as a JSON object. | ||
*/ | ||
ModelBase.prototype.$toJson = function () { | ||
return this.$$toJson(false, null, null); | ||
}; | ||
}, { | ||
key: '$afterValidate', | ||
value: function $afterValidate(json, options) {} | ||
// Do nothing by default. | ||
/** | ||
* Alias for `this.$toJson()`. | ||
* | ||
* For JSON.stringify compatibility. | ||
*/ | ||
ModelBase.prototype.toJSON = function () { | ||
return this.$toJson(); | ||
}; | ||
/** | ||
* This is called when a ModelBase is created from a database JSON object. | ||
* | ||
* Converts the JSON object from the database format to the internal format. | ||
* | ||
* @note This function must handle the case where any subset of the columns comes | ||
* in the `json` argument. You cannot assume that all columns are present as it | ||
* depends on the select statement. There can also be additional columns because | ||
* of join clauses, aliases etc. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in database format. | ||
* | ||
* @return {Object} | ||
* The JSON object in internal format. | ||
*/ | ||
/** | ||
* Sets the values from a JSON object. | ||
* | ||
* Validates the JSON before setting values. Calls `this.$parseJson()`. | ||
* | ||
* @param {Object} json | ||
* The JSON object to set. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @throws ValidationError | ||
* If validation fails. | ||
*/ | ||
ModelBase.prototype.$setJson = function (json, options) { | ||
json = json || {}; | ||
options = options || {}; | ||
}, { | ||
key: '$parseDatabaseJson', | ||
value: function $parseDatabaseJson(json) { | ||
return json; | ||
} | ||
if (!_.isObject(json) | ||
|| _.isString(json) | ||
|| _.isNumber(json) | ||
|| _.isDate(json) | ||
|| _.isArray(json) | ||
|| _.isFunction(json) | ||
|| _.isTypedArray(json) | ||
|| _.isRegExp(json)) { | ||
/** | ||
* This is called when a ModelBase is converted to database format. | ||
* | ||
* Converts the JSON object from the internal format to the database format. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in internal format. | ||
* | ||
* @return {Object} | ||
* The JSON object in database format. | ||
*/ | ||
throw new Error('You should only pass objects to $setJson method. ' | ||
+ '$setJson method was given an invalid value ' | ||
+ json); | ||
} | ||
}, { | ||
key: '$formatDatabaseJson', | ||
value: function $formatDatabaseJson(json) { | ||
return json; | ||
} | ||
if (!options.patch) { | ||
json = mergeWithDefaults(this.constructor.jsonSchema, json); | ||
} | ||
/** | ||
* This is called when a ModelBase is created from a JSON object. | ||
* | ||
* Converts the JSON object to the internal format. | ||
* | ||
* @note If you override this remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in external format. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @return {Object} | ||
* The JSON object in internal format. | ||
*/ | ||
json = this.$parseJson(json, options); | ||
json = this.$validate(json, options); | ||
}, { | ||
key: '$parseJson', | ||
value: function $parseJson(json, options) { | ||
return json; | ||
} | ||
this.$set(json); | ||
}; | ||
/** | ||
* This is called when a ModelBase is converted to JSON. | ||
* | ||
* @note Remember to call the super class's implementation. | ||
* | ||
* @param {Object} json | ||
* The JSON object in internal format | ||
* | ||
* @return {Object} | ||
* The JSON object in external format. | ||
*/ | ||
/** | ||
* Sets the values from a JSON object in database format. | ||
* | ||
* Calls `this.$parseDatabaseJson()`. | ||
* | ||
* @param {Object} json | ||
* The JSON object in database format. | ||
*/ | ||
ModelBase.prototype.$setDatabaseJson = function (json) { | ||
json = this.$parseDatabaseJson(json || {}); | ||
}, { | ||
key: '$formatJson', | ||
value: function $formatJson(json) { | ||
return json; | ||
} | ||
for (var key in json) { | ||
this[key] = json[key]; | ||
} | ||
}; | ||
/** | ||
* Exports this model as a database JSON object. | ||
* | ||
* Calls `$formatDatabaseJson()`. | ||
* | ||
* @return {Object} | ||
* This model as a JSON object in database format. | ||
*/ | ||
/** | ||
* Sets the values from another model or object. | ||
* | ||
* Unlike $setJson, this doesn't call any `$parseJson` methods or validate the input. | ||
* This simply sets each value in the object to this object. | ||
* | ||
* @param {Object} obj | ||
*/ | ||
ModelBase.prototype.$set = function (obj) { | ||
var self = this; | ||
}, { | ||
key: '$toDatabaseJson', | ||
value: function $toDatabaseJson() { | ||
return this.$$toJson(true, null, null); | ||
} | ||
_.each(obj, function $setLooper(value, key) { | ||
if (key.charAt(0) !== '$' && !_.isFunction(value)) { | ||
self[key] = value; | ||
/** | ||
* Exports this model as a JSON object. | ||
* | ||
* Calls `$formatJson()`. | ||
* | ||
* @return {Object} | ||
* This model as a JSON object. | ||
*/ | ||
}, { | ||
key: '$toJson', | ||
value: function $toJson() { | ||
return this.$$toJson(false, null, null); | ||
} | ||
}); | ||
return this; | ||
}; | ||
/** | ||
* Alias for `this.$toJson()`. | ||
* | ||
* For JSON.stringify compatibility. | ||
*/ | ||
/** | ||
* Omits a set of properties. | ||
* | ||
* The selected properties are set to `undefined`. Note that this is done in-place. | ||
* Properties are set to undefined instead of deleting them for performance reasons | ||
* (V8 doesn't like delete). | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit('lastName') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit('lastName') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit('lastName') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* @param {Array.<String>|Object.<String, Boolean>} keys | ||
*/ | ||
ModelBase.prototype.$omit = function () { | ||
if (arguments.length === 1 && _.isObject(arguments[0])) { | ||
var keys = arguments[0]; | ||
}, { | ||
key: 'toJSON', | ||
value: function toJSON() { | ||
return this.$toJson(); | ||
} | ||
if (_.isArray(keys)) { | ||
omitArray(this, keys); | ||
} else { | ||
omitObject(this, keys); | ||
/** | ||
* Sets the values from a JSON object. | ||
* | ||
* Validates the JSON before setting values. Calls `this.$parseJson()`. | ||
* | ||
* @param {Object} json | ||
* The JSON object to set. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @returns {ModelBase} `this` for chaining. | ||
* | ||
* @throws ValidationError | ||
* If validation fails. | ||
*/ | ||
}, { | ||
key: '$setJson', | ||
value: function $setJson(json) { | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
json = json || {}; | ||
if (!_lodash2.default.isObject(json) || _lodash2.default.isString(json) || _lodash2.default.isNumber(json) || _lodash2.default.isDate(json) || _lodash2.default.isArray(json) || _lodash2.default.isFunction(json) || _lodash2.default.isTypedArray(json) || _lodash2.default.isRegExp(json)) { | ||
throw new Error('You should only pass objects to $setJson method. ' + '$setJson method was given an invalid value ' + json); | ||
} | ||
if (!options.patch) { | ||
json = mergeWithDefaults(this.constructor.jsonSchema, json); | ||
} | ||
json = this.$parseJson(json, options); | ||
json = this.$validate(json, options); | ||
return this.$set(json); | ||
} | ||
} else { | ||
omitArray(this, _.toArray(arguments)); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Sets the values from a JSON object in database format. | ||
* | ||
* Calls `this.$parseDatabaseJson()`. | ||
* | ||
* @param {Object} json | ||
* The JSON object in database format. | ||
* | ||
* @returns {ModelBase} `this` for chaining. | ||
*/ | ||
/** | ||
* Picks a set of properties. | ||
* | ||
* All other properties but the selected ones are set to `undefined`. Note that | ||
* this is done in-place. Properties are set to undefined instead of deleting | ||
* them for performance reasons (V8 doesn't like delete). | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick('firstName', 'age') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick(['firstName', 'age']) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick({firstName: true, age: true}) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* @param {Array.<String>|Object.<String, Boolean>} keys | ||
*/ | ||
ModelBase.prototype.$pick = function () { | ||
if (arguments.length === 1 && _.isObject(arguments[0])) { | ||
var keys = arguments[0]; | ||
}, { | ||
key: '$setDatabaseJson', | ||
value: function $setDatabaseJson() { | ||
var json = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
if (_.isArray(keys)) { | ||
pickArray(this, keys); | ||
} else { | ||
pickObject(this, keys); | ||
json = this.$parseDatabaseJson(json); | ||
for (var key in json) { | ||
this[key] = json[key]; | ||
} | ||
return this; | ||
} | ||
} else { | ||
pickArray(this, _.toArray(arguments)); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Sets the values from another model or object. | ||
* | ||
* Unlike $setJson, this doesn't call any `$parseJson` methods or validate the input. | ||
* This simply sets each value in the object to this object. | ||
* | ||
* @param {Object} obj | ||
* @returns {ModelBase} `this` for chaining. | ||
*/ | ||
/** | ||
* Returns a deep copy of this model. | ||
* | ||
* If this object has instances of ModelBase as properties (or arrays of them) | ||
* they are cloned using their `.$clone()` method. | ||
* | ||
* @return {ModelBase} | ||
*/ | ||
ModelBase.prototype.$clone = function () { | ||
var clone = new this.constructor(); | ||
}, { | ||
key: '$set', | ||
value: function $set(obj) { | ||
var self = this; | ||
_.each(this, function cloneLooper(value, key) { | ||
if (_.isObject(value)) { | ||
clone[key] = cloneObject(value); | ||
} else { | ||
clone[key] = value; | ||
_lodash2.default.each(obj, function (value, key) { | ||
if (key.charAt(0) !== '$' && !_lodash2.default.isFunction(value)) { | ||
self[key] = value; | ||
} | ||
}); | ||
return this; | ||
} | ||
}); | ||
return clone; | ||
}; | ||
/** | ||
* Omits a set of properties. | ||
* | ||
* The selected properties are set to `undefined`. Note that this is done in-place. | ||
* Properties are set to undefined instead of deleting them for performance reasons | ||
* (V8 doesn't like delete). | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit('lastName') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit(['lastName']) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$omit({lastName: true}) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* @param {string|Array.<string>|Object.<string, boolean>} keys | ||
* @returns {ModelBase} `this` for chaining. | ||
*/ | ||
/** | ||
* The optional schema against which the JSON is validated. | ||
* | ||
* The jsonSchema can be dynamically modified in the `$beforeValidate` method. | ||
* | ||
* Must follow http://json-schema.org specification. If null no validation is done. | ||
* | ||
* @see $beforeValidate() | ||
* @see $validate() | ||
* @see $afterValidate() | ||
* | ||
* @type {Object} | ||
*/ | ||
ModelBase.jsonSchema = null; | ||
}, { | ||
key: '$omit', | ||
value: function $omit() { | ||
if (arguments.length === 1 && _lodash2.default.isObject(arguments[0])) { | ||
var keys = arguments[0]; | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$colToProp = null; | ||
if (_lodash2.default.isArray(keys)) { | ||
omitArray(this, keys); | ||
} else { | ||
omitObject(this, keys); | ||
} | ||
} else { | ||
omitArray(this, _lodash2.default.toArray(arguments)); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$propToCol = null; | ||
return this; | ||
} | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
ModelBase.extend = function (subclassConstructor) { | ||
if (_.isEmpty(subclassConstructor.name)) { | ||
throw new Error('Each ModelBase subclass constructor must have a name'); | ||
} | ||
/** | ||
* Picks a set of properties. | ||
* | ||
* All other properties but the selected ones are set to `undefined`. Note that | ||
* this is done in-place. Properties are set to undefined instead of deleting | ||
* them for performance reasons (V8 doesn't like delete). | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick('firstName', 'age') | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick(['firstName', 'age']) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* ```js | ||
* var json = person | ||
* .fromJson({firstName: 'Jennifer', lastName: 'Lawrence', age: 24}) | ||
* .$pick({firstName: true, age: true}) | ||
* .toJSON(); | ||
* | ||
* console.log(_.has(json, 'lastName')); // --> false | ||
* ``` | ||
* | ||
* @param {string|Array.<string>|Object.<string, boolean>} keys | ||
* @returns {ModelBase} `this` for chaining. | ||
*/ | ||
utils.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
}; | ||
}, { | ||
key: '$pick', | ||
value: function $pick() { | ||
if (arguments.length === 1 && _lodash2.default.isObject(arguments[0])) { | ||
var keys = arguments[0]; | ||
/** | ||
* Creates a model instance from a JSON object. | ||
* | ||
* The object is checked against `jsonSchema` and an exception is thrown on failure. | ||
* | ||
* @param {Object=} json | ||
* The JSON from which to create the model. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @throws ValidationError | ||
* If validation fails. | ||
*/ | ||
ModelBase.fromJson = function (json, options) { | ||
var model = new this(); | ||
model.$setJson(json || {}, options); | ||
return model; | ||
}; | ||
if (_lodash2.default.isArray(keys)) { | ||
pickArray(this, keys); | ||
} else { | ||
pickObject(this, keys); | ||
} | ||
} else { | ||
pickArray(this, _lodash2.default.toArray(arguments)); | ||
} | ||
/** | ||
* Creates a model instance from a JSON object in database format. | ||
* | ||
* @param {Object=} json | ||
* The JSON from which to create the model. | ||
*/ | ||
ModelBase.fromDatabaseJson = function (json) { | ||
var model = new this(); | ||
model.$setDatabaseJson(json || {}); | ||
return model; | ||
}; | ||
return this; | ||
} | ||
/** | ||
* Omit implementation to use. | ||
* | ||
* The default just sets the property to undefined for performance reasons. | ||
* If the slight performance drop is not an issue for you, you can override | ||
* this method to delete the property instead. | ||
* | ||
* @param {Object} obj | ||
* @param {String} prop | ||
*/ | ||
ModelBase.omitImpl = function (obj, prop) { | ||
obj[prop] = undefined; | ||
}; | ||
/** | ||
* Returns a deep copy of this model. | ||
* | ||
* If this object has instances of ModelBase as properties (or arrays of them) | ||
* they are cloned using their `.$clone()` method. | ||
* | ||
* @return {ModelBase} | ||
*/ | ||
/** | ||
* @ignore | ||
* @param {string} columnName | ||
* @returns {string} | ||
*/ | ||
ModelBase.columnNameToPropertyName = function (columnName) { | ||
this.$$ensurePropNameConversionCache(); | ||
}, { | ||
key: '$clone', | ||
value: function $clone() { | ||
var clone = new this.constructor(); | ||
if (!this.$$colToProp[columnName]) { | ||
this.$$cachePropNameConversion(this.$$columnNameToPropertyName(columnName), columnName); | ||
} | ||
_lodash2.default.each(this, function (value, key) { | ||
if (_lodash2.default.isObject(value)) { | ||
clone[key] = cloneObject(value); | ||
} else { | ||
clone[key] = value; | ||
} | ||
}); | ||
return this.$$colToProp[columnName]; | ||
}; | ||
return clone; | ||
} | ||
/** | ||
* @ignore | ||
* @param {string} propertyName | ||
* @returns {string} | ||
*/ | ||
ModelBase.propertyNameToColumnName = function (propertyName) { | ||
this.$$ensurePropNameConversionCache(); | ||
/** | ||
* @protected | ||
*/ | ||
if (!this.$$propToCol[propertyName]) { | ||
this.$$cachePropNameConversion(propertyName, this.$$propertyNameToColumnName(propertyName)); | ||
} | ||
}, { | ||
key: '$$toJson', | ||
value: function $$toJson(createDbJson, omit, pick) { | ||
var json = toJsonImpl(this, createDbJson, omit, pick); | ||
return this.$$propToCol[propertyName]; | ||
}; | ||
if (createDbJson) { | ||
return this.$formatDatabaseJson(json); | ||
} else { | ||
return this.$formatJson(json); | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
ModelBase.prototype.$$toJson = function (createDbJson, omit, pick) { | ||
var json = toJsonImpl(this, createDbJson, omit, pick); | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
if (createDbJson) { | ||
return this.$formatDatabaseJson(json); | ||
} else { | ||
return this.$formatJson(json); | ||
} | ||
}; | ||
}], [{ | ||
key: 'extend', | ||
value: function extend(subclassConstructor) { | ||
if (_lodash2.default.isEmpty(subclassConstructor.name)) { | ||
throw new Error('Each ModelBase subclass constructor must have a name'); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$ensurePropNameConversionCache = function () { | ||
if (!this.$$propToCol) { | ||
this.$$propToCol = Object.create(null) | ||
} | ||
_utils2.default.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
} | ||
if (!this.$$colToProp) { | ||
this.$$colToProp = Object.create(null); | ||
} | ||
}; | ||
/** | ||
* Creates a model instance from a JSON object. | ||
* | ||
* The object is checked against `jsonSchema` and an exception is thrown on failure. | ||
* | ||
* @param {Object=} json | ||
* The JSON from which to create the model. | ||
* | ||
* @param {ModelOptions=} options | ||
* Optional options. | ||
* | ||
* @returns {Model} | ||
* | ||
* @throws ValidationError | ||
* If validation fails. | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$cachePropNameConversion = function (propertyName, columnName) { | ||
this.$$propToCol[propertyName] = columnName; | ||
this.$$colToProp[columnName] = propertyName; | ||
}; | ||
}, { | ||
key: 'fromJson', | ||
value: function fromJson(json, options) { | ||
var model = new this(); | ||
model.$setJson(json || {}, options); | ||
return model; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$columnNameToPropertyName = function (columnName) { | ||
var ModelClass = this; | ||
var model = new ModelClass(); | ||
var addedProps = _.keys(model.$parseDatabaseJson({})); | ||
/** | ||
* Creates a model instance from a JSON object in database format. | ||
* | ||
* @param {Object=} json | ||
* The JSON from which to create the model. | ||
* | ||
* @returns {Model} | ||
*/ | ||
var row = {}; | ||
row[columnName] = null; | ||
}, { | ||
key: 'fromDatabaseJson', | ||
value: function fromDatabaseJson(json) { | ||
var model = new this(); | ||
model.$setDatabaseJson(json || {}); | ||
return model; | ||
} | ||
var props = _.keys(_.omit(model.$parseDatabaseJson(row), addedProps)); | ||
var propertyName = _.first(props); | ||
/** | ||
* Omit implementation to use. | ||
* | ||
* The default just sets the property to undefined for performance reasons. | ||
* If the slight performance drop is not an issue for you, you can override | ||
* this method to delete the property instead. | ||
* | ||
* @param {Object} obj | ||
* @param {string} prop | ||
*/ | ||
return propertyName || null; | ||
}; | ||
}, { | ||
key: 'omitImpl', | ||
value: function omitImpl(obj, prop) { | ||
obj[prop] = undefined; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
ModelBase.$$propertyNameToColumnName = function (propertyName) { | ||
var ModelClass = this; | ||
var model = new ModelClass(); | ||
var addedCols = _.keys(model.$formatDatabaseJson({})); | ||
/** | ||
* @ignore | ||
* @param {string} columnName | ||
* @returns {string} | ||
*/ | ||
var obj = {}; | ||
obj[propertyName] = null; | ||
}, { | ||
key: 'columnNameToPropertyName', | ||
value: function columnNameToPropertyName(columnName) { | ||
this.$$ensurePropNameConversionCache(); | ||
var cols = _.keys(_.omit(model.$formatDatabaseJson(obj), addedCols)); | ||
var columnName = _.first(cols); | ||
if (!this.$$colToProp[columnName]) { | ||
this.$$cachePropNameConversion(_columnNameToPropertyName(this, columnName), columnName); | ||
} | ||
return columnName || null; | ||
}; | ||
return this.$$colToProp[columnName]; | ||
} | ||
/** | ||
* @ignore | ||
* @param {string} propertyName | ||
* @returns {string} | ||
*/ | ||
}, { | ||
key: 'propertyNameToColumnName', | ||
value: function propertyNameToColumnName(propertyName) { | ||
this.$$ensurePropNameConversionCache(); | ||
if (!this.$$propToCol[propertyName]) { | ||
this.$$cachePropNameConversion(propertyName, _propertyNameToColumnName(this, propertyName)); | ||
} | ||
return this.$$propToCol[propertyName]; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '$$ensurePropNameConversionCache', | ||
value: function $$ensurePropNameConversionCache() { | ||
if (!this.$$propToCol) { | ||
this.$$propToCol = (0, _create2.default)(null); | ||
} | ||
if (!this.$$colToProp) { | ||
this.$$colToProp = (0, _create2.default)(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); | ||
/** | ||
* @private | ||
*/ | ||
exports.default = ModelBase; | ||
function mergeWithDefaults(jsonSchema, json) { | ||
@@ -703,10 +783,10 @@ var merged = null; | ||
if (!_.has(json, key) && _.has(prop, 'default')) { | ||
if (!_lodash2.default.has(json, key) && _lodash2.default.has(prop, 'default')) { | ||
if (merged === null) { | ||
// Only take expensive clone if needed. | ||
merged = _.cloneDeep(json); | ||
merged = _lodash2.default.cloneDeep(json); | ||
} | ||
if (_.isObject(prop.default)) { | ||
merged[key] = _.cloneDeep(prop.default); | ||
if (_lodash2.default.isObject(prop.default)) { | ||
merged[key] = _lodash2.default.cloneDeep(prop.default); | ||
} else { | ||
@@ -729,3 +809,3 @@ merged[key] = prop.default; | ||
function tryValidate(jsonSchema, json, options) { | ||
var required; | ||
var required = undefined; | ||
@@ -738,3 +818,3 @@ try { | ||
return tv4.validateMultiple(json, jsonSchema); | ||
return _tv2.default.validateMultiple(json, jsonSchema); | ||
} finally { | ||
@@ -777,3 +857,3 @@ if (options.patch) { | ||
return new ValidationError(errorHash); | ||
return new _ValidationError2.default(errorHash); | ||
} | ||
@@ -787,10 +867,6 @@ | ||
_.each(self, function toJsonImplLooper(value, key) { | ||
if (key.charAt(0) !== '$' | ||
&& !_.isFunction(value) | ||
&& !_.isUndefined(value) | ||
&& (!omit || !omit[key]) | ||
&& (!pick || pick[key])) { | ||
_lodash2.default.each(self, function (value, key) { | ||
if (key.charAt(0) !== '$' && !_lodash2.default.isFunction(value) && !_lodash2.default.isUndefined(value) && (!omit || !omit[key]) && (!pick || pick[key])) { | ||
if (_.isObject(value)) { | ||
if (_lodash2.default.isObject(value)) { | ||
json[key] = toJsonObject(value, createDbJson); | ||
@@ -810,3 +886,3 @@ } else { | ||
function toJsonObject(value, createDbJson) { | ||
if (_.isArray(value)) { | ||
if (_lodash2.default.isArray(value)) { | ||
return toJsonArray(value, createDbJson); | ||
@@ -820,3 +896,3 @@ } else if (value instanceof ModelBase) { | ||
} else { | ||
return _.cloneDeep(value); | ||
return _lodash2.default.cloneDeep(value); | ||
} | ||
@@ -829,3 +905,3 @@ } | ||
function toJsonArray(value, createDbJson) { | ||
return _.map(value, function toJsonArrayLooper(value) { | ||
return _lodash2.default.map(value, function (value) { | ||
return toJsonObject(value, createDbJson); | ||
@@ -839,3 +915,3 @@ }); | ||
function cloneObject(value) { | ||
if (_.isArray(value)) { | ||
if (_lodash2.default.isArray(value)) { | ||
return cloneArray(value); | ||
@@ -845,3 +921,3 @@ } else if (value instanceof ModelBase) { | ||
} else { | ||
return _.cloneDeep(value); | ||
return _lodash2.default.cloneDeep(value); | ||
} | ||
@@ -854,5 +930,3 @@ } | ||
function cloneArray(value) { | ||
return _.map(value, function cloneArrayLooper(value) { | ||
return cloneObject(value); | ||
}); | ||
return _lodash2.default.map(value, cloneObject); | ||
} | ||
@@ -866,4 +940,4 @@ | ||
_.each(keyObj, function (value, key) { | ||
if (value && key.charAt(0) !== '$' && _.has(model, key)) { | ||
_lodash2.default.each(keyObj, function (value, key) { | ||
if (value && key.charAt(0) !== '$' && _lodash2.default.has(model, key)) { | ||
ModelClass.omitImpl(model, key); | ||
@@ -880,4 +954,4 @@ } | ||
_.each(keys, function (key) { | ||
if (key.charAt(0) !== '$' && _.has(model, key)) { | ||
_lodash2.default.each(keys, function (key) { | ||
if (key.charAt(0) !== '$' && _lodash2.default.has(model, key)) { | ||
ModelClass.omitImpl(model, key); | ||
@@ -894,3 +968,3 @@ } | ||
_.each(model, function (value, key) { | ||
_lodash2.default.each(model, function (value, key) { | ||
if (key.charAt(0) !== '$' && !keyObj[key]) { | ||
@@ -908,3 +982,3 @@ ModelClass.omitImpl(model, key); | ||
_.each(model, function (value, key) { | ||
_lodash2.default.each(model, function (value, key) { | ||
if (key.charAt(0) !== '$' && !contains(keys, key)) { | ||
@@ -928,2 +1002,36 @@ ModelClass.omitImpl(model, key); | ||
module.exports = ModelBase; | ||
/** | ||
* @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); |
'use strict'; | ||
var _ = require('lodash') | ||
, Promise = require('bluebird') | ||
, QueryBuilder = require('./QueryBuilder') | ||
, ValidationError = require('./../ValidationError') | ||
, RelationExpression = require('./RelationExpression'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _bluebird = require('bluebird'); | ||
var _bluebird2 = _interopRequireDefault(_bluebird); | ||
var _ValidationError = require('../ValidationError'); | ||
var _ValidationError2 = _interopRequireDefault(_ValidationError); | ||
var _RelationExpression = require('./RelationExpression'); | ||
var _RelationExpression2 = _interopRequireDefault(_RelationExpression); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* @constructor | ||
* @ignore | ||
*/ | ||
function EagerFetcher(opt) { | ||
this.modelClass = opt.modelClass; | ||
this.models = opt.models; | ||
this.eager = opt.eager; | ||
this.filters = opt.filters || {}; | ||
this.parent = opt.parent || null; | ||
this.children = Object.create(null); | ||
this.promise = null; | ||
} | ||
EagerFetcher.prototype.fetch = function () { | ||
if (this.promise) { | ||
return this.promise; | ||
} | ||
var EagerFetcher = (function () { | ||
function EagerFetcher(_ref) { | ||
var modelClass = _ref.modelClass; | ||
var models = _ref.models; | ||
var eager = _ref.eager; | ||
var filters = _ref.filters; | ||
var parent = _ref.parent; | ||
var rootQuery = _ref.rootQuery; | ||
(0, _classCallCheck3.default)(this, EagerFetcher); | ||
if (_.isEmpty(this.models)) { | ||
this.promise = Promise.resolve([]); | ||
return this.promise; | ||
this.modelClass = modelClass; | ||
this.models = models; | ||
this.eager = eager; | ||
this.filters = filters || {}; | ||
this.parent = parent || null; | ||
this.rootQuery = rootQuery || null; | ||
this.children = (0, _create2.default)(null); | ||
this.promise = null; | ||
} | ||
var self = this; | ||
var promises = []; | ||
(0, _createClass3.default)(EagerFetcher, [{ | ||
key: 'fetch', | ||
value: function fetch() { | ||
var _this = this; | ||
this.eager.forEachChild(function (child) { | ||
var relation = self.modelClass.getRelations()[child.name]; | ||
if (this.promise) { | ||
return this.promise; | ||
} | ||
if (!relation) { | ||
throw new ValidationError({eager: 'unknown relation "' + child.name + '" in an eager expression'}); | ||
} | ||
}); | ||
if (_lodash2.default.isEmpty(this.models)) { | ||
this.promise = _bluebird2.default.resolve([]); | ||
return this.promise; | ||
} | ||
_.each(this.modelClass.getRelations(), function (relation) { | ||
var nextEager = self.eager.childExpression(relation.name); | ||
var promises = []; | ||
if (nextEager) { | ||
promises.push(self._fetchRelation(relation, nextEager)); | ||
} | ||
}); | ||
this.eager.forEachChild(function (child) { | ||
var relation = _this.modelClass.getRelations()[child.name]; | ||
this.promise = Promise.all(promises).return(this.models); | ||
return this.promise; | ||
}; | ||
if (!relation) { | ||
throw new _ValidationError2.default({ eager: 'unknown relation "' + child.name + '" in an eager expression' }); | ||
} | ||
}); | ||
EagerFetcher.prototype._fetchRelation = function (relation, nextEager) { | ||
var self = this; | ||
var ModelClass = relation.relatedModelClass; | ||
var queryBuilder = ModelClass.RelatedQueryBuilder.forClass(ModelClass); | ||
_lodash2.default.each(this.modelClass.getRelations(), function (relation) { | ||
var nextEager = _this.eager.childExpression(relation.name); | ||
relation.find(queryBuilder, this.models); | ||
if (nextEager) { | ||
promises.push(_this._fetchRelation(relation, nextEager)); | ||
} | ||
}); | ||
_.each(nextEager.args, function (filterName) { | ||
var filter = self.filters[filterName]; | ||
if (!_.isFunction(filter)) { | ||
throw new ValidationError({eager: 'could not find filter "' + filterName + '" for relation "' + relation.name + '"'}); | ||
this.promise = _bluebird2.default.all(promises).return(this.models); | ||
return this.promise; | ||
} | ||
}, { | ||
key: '_fetchRelation', | ||
value: function _fetchRelation(relation, nextEager) { | ||
var _this2 = this; | ||
filter(queryBuilder); | ||
}); | ||
var ModelClass = relation.relatedModelClass; | ||
var queryBuilder = ModelClass.RelatedQueryBuilder.forClass(ModelClass).childQueryOf(this.rootQuery); | ||
return queryBuilder.then(function (related) { | ||
return self._fetchNextEager(relation, related, nextEager); | ||
}); | ||
}; | ||
relation.find(queryBuilder, this.models); | ||
EagerFetcher.prototype._fetchNextEager = function (relation, related, eager) { | ||
this.children[relation.name] = new EagerFetcher({ | ||
modelClass: relation.relatedModelClass, | ||
models: related, | ||
eager: eager, | ||
filters: this.filters, | ||
parent: this | ||
}); | ||
_lodash2.default.each(nextEager.args, function (filterName) { | ||
var filter = _this2.filters[filterName]; | ||
return this.children[relation.name].fetch(); | ||
}; | ||
if (!_lodash2.default.isFunction(filter)) { | ||
throw new _ValidationError2.default({ eager: 'could not find filter "' + filterName + '" for relation "' + relation.name + '"' }); | ||
} | ||
module.exports = EagerFetcher; | ||
filter(queryBuilder); | ||
}); | ||
return queryBuilder.then(function (related) { | ||
return _this2._fetchNextEager(relation, related, nextEager); | ||
}); | ||
} | ||
}, { | ||
key: '_fetchNextEager', | ||
value: function _fetchNextEager(relation, related, eager) { | ||
this.children[relation.name] = new EagerFetcher({ | ||
modelClass: relation.relatedModelClass, | ||
models: related, | ||
eager: eager, | ||
filters: this.filters, | ||
parent: this, | ||
rootQuery: this.rootQuery | ||
}); | ||
return this.children[relation.name].fetch(); | ||
} | ||
}]); | ||
return EagerFetcher; | ||
})(); | ||
exports.default = EagerFetcher; |
'use strict'; | ||
var _ = require('lodash'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _QueryBuilderBase = require('./QueryBuilderBase'); | ||
var _QueryBuilderBase2 = _interopRequireDefault(_QueryBuilderBase); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
@@ -29,111 +50,128 @@ * Internal representation of insert and update data. | ||
* @ignore | ||
* @constructor | ||
*/ | ||
function InsertionOrUpdate(QueryBuilder, ModelClass) { | ||
this.QueryBuilder = QueryBuilder; | ||
this.ModelClass = ModelClass; | ||
this._models = []; | ||
this._rawOrQuery = []; | ||
this._arrayInput = false; | ||
} | ||
var InsertionOrUpdate = (function () { | ||
function InsertionOrUpdate(_ref) { | ||
var ModelClass = _ref.ModelClass; | ||
var modelsOrObjects = _ref.modelsOrObjects; | ||
var modelOptions = _ref.modelOptions; | ||
(0, _classCallCheck3.default)(this, InsertionOrUpdate); | ||
InsertionOrUpdate.prototype.model = function () { | ||
return this._models[0]; | ||
}; | ||
this.ModelClass = ModelClass; | ||
InsertionOrUpdate.prototype.models = function () { | ||
return this._models; | ||
}; | ||
this._models = []; | ||
this._rawOrQuery = []; | ||
this._arrayInput = false; | ||
/** | ||
* Returns true if the input to `setData` method was an array. | ||
* | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
InsertionOrUpdate.prototype.isArray = function () { | ||
return this._arrayInput; | ||
}; | ||
this.setData(modelsOrObjects, modelOptions); | ||
} | ||
/** | ||
* Sets the actual insert/update data. | ||
* | ||
* @ignore | ||
* @param {Object|Array.<Object>} data | ||
* @param {ModelOptions} modelOptions | ||
*/ | ||
InsertionOrUpdate.prototype.setData = function (data, modelOptions) { | ||
var self = this; | ||
var knex = this.ModelClass.knex(); | ||
var KnexQueryBuilder = knex.client.QueryBuilder; | ||
var Raw = knex.client.Raw; | ||
(0, _createClass3.default)(InsertionOrUpdate, [{ | ||
key: 'model', | ||
value: function model() { | ||
return this._models[0]; | ||
} | ||
}, { | ||
key: 'models', | ||
value: function models() { | ||
return this._models; | ||
} | ||
// knex.QueryBuilder and knex.Raw are not documented properties. | ||
// We make sure here that things break if knex changes things. | ||
if (!_.isFunction(KnexQueryBuilder) || !_.isFunction(Raw)) { | ||
throw new Error('knex API has changed: knex.QueryBuilder or knex.Raw constructor missing.'); | ||
} | ||
/** | ||
* Returns true if the input to `setData` method was an array. | ||
* | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
this._models = []; | ||
this._rawOrQuery = []; | ||
this._arrayInput = _.isArray(data); | ||
}, { | ||
key: 'isArray', | ||
value: function isArray() { | ||
return this._arrayInput; | ||
} | ||
if (!this._arrayInput) { | ||
data = _.isObject(data) ? [data] : []; | ||
} | ||
/** | ||
* Sets the actual insert/update data. | ||
* | ||
* @ignore | ||
* @param {(Object|Array.<Object>)} data | ||
* @param {ModelOptions} modelOptions | ||
*/ | ||
// Separate raw queries and query builders from javascript primitives. | ||
// The javascript primitives are converted into a Model instance and the | ||
// "query" properties are stored separately. | ||
_.each(data, function (obj) { | ||
if (obj instanceof self.ModelClass) { | ||
self._models.push(obj); | ||
self._rawOrQuery.push({}); | ||
} else { | ||
var modelJson = {}; | ||
var rawOrSubquery = {}; | ||
}, { | ||
key: 'setData', | ||
value: function setData(data, modelOptions) { | ||
var _this = this; | ||
_.each(obj, function (value, key) { | ||
if (value instanceof KnexQueryBuilder|| value instanceof Raw) { | ||
rawOrSubquery[key] = value; | ||
} else if (value instanceof self.QueryBuilder) { | ||
rawOrSubquery[key] = value.build(); | ||
var knex = this.ModelClass.knex(); | ||
var KnexQueryBuilder = knex.client.QueryBuilder; | ||
var Raw = knex.client.Raw; | ||
// knex.QueryBuilder and knex.Raw are not documented properties. | ||
// We make sure here that things break if knex changes things. | ||
if (!_lodash2.default.isFunction(KnexQueryBuilder) || !_lodash2.default.isFunction(Raw)) { | ||
throw new Error('knex API has changed: knex.QueryBuilder or knex.Raw constructor missing.'); | ||
} | ||
this._models = []; | ||
this._rawOrQuery = []; | ||
this._arrayInput = _lodash2.default.isArray(data); | ||
if (!this._arrayInput) { | ||
data = _lodash2.default.isObject(data) ? [data] : []; | ||
} | ||
// Separate raw queries and query builders from javascript primitives. | ||
// The javascript primitives are converted into a Model instance and the | ||
// "query" properties are stored separately. | ||
_lodash2.default.forEach(data, function (obj) { | ||
if (obj instanceof _this.ModelClass) { | ||
_this._models.push(obj); | ||
_this._rawOrQuery.push({}); | ||
} else { | ||
modelJson[key] = value; | ||
(function () { | ||
var modelJson = {}; | ||
var rawOrSubquery = {}; | ||
_lodash2.default.forEach(obj, function (value, key) { | ||
if (value instanceof KnexQueryBuilder || value instanceof Raw) { | ||
rawOrSubquery[key] = value; | ||
} else if (value instanceof _QueryBuilderBase2.default) { | ||
rawOrSubquery[key] = value.build(); | ||
} else { | ||
modelJson[key] = value; | ||
} | ||
}); | ||
_this._models.push(_this.ModelClass.fromJson(modelJson, modelOptions)); | ||
_this._rawOrQuery.push(rawOrSubquery); | ||
})(); | ||
} | ||
}); | ||
self._models.push(self.ModelClass.fromJson(modelJson, modelOptions)); | ||
self._rawOrQuery.push(rawOrSubquery); | ||
} | ||
}); | ||
}; | ||
/** | ||
* Create an object that can be given for the knex update or insert method. | ||
* | ||
* @ignore | ||
* @returns {Object|Array.<Object>} | ||
*/ | ||
InsertionOrUpdate.prototype.toKnexInput = function () { | ||
var self = this; | ||
/** | ||
* Create an object that can be given for the knex update or insert method. | ||
* | ||
* @ignore | ||
* @returns {Object|Array.<Object>} | ||
*/ | ||
var knexInput = _.map(this._models, function (model, i) { | ||
var modelJson = model.$toDatabaseJson(); | ||
}, { | ||
key: 'toKnexInput', | ||
value: function toKnexInput() { | ||
var _this2 = this; | ||
var rawOrQuery = _.mapKeys(self._rawOrQuery[i], function (value, key) { | ||
return model.constructor.propertyNameToColumnName(key); | ||
}); | ||
var knexInput = _lodash2.default.map(this._models, function (model, i) { | ||
return _lodash2.default.merge(model.$toDatabaseJson(), _lodash2.default.mapKeys(_this2._rawOrQuery[i], function (value, key) { | ||
return model.constructor.propertyNameToColumnName(key); | ||
})); | ||
}); | ||
return _.merge(modelJson, rawOrQuery); | ||
}); | ||
return knexInput.length === 1 ? knexInput[0] : knexInput; | ||
} | ||
}]); | ||
return InsertionOrUpdate; | ||
})(); | ||
if (knexInput.length === 1) { | ||
return knexInput[0]; | ||
} else { | ||
return knexInput; | ||
} | ||
}; | ||
module.exports = InsertionOrUpdate; | ||
exports.default = InsertionOrUpdate; |
'use strict'; | ||
var _ = require('lodash') | ||
, Promise = require('bluebird') | ||
, RelationExpression = require('./RelationExpression') | ||
, ManyToManyRelation = require('./../relations/ManyToManyRelation') | ||
, OneToManyRelation = require('./../relations/OneToManyRelation') | ||
, OneToOneRelation = require('./../relations/OneToOneRelation') | ||
, ValidationError = require('./../ValidationError') | ||
, Model; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _bluebird = require('bluebird'); | ||
var _bluebird2 = _interopRequireDefault(_bluebird); | ||
var _RelationExpression = require('./RelationExpression'); | ||
var _RelationExpression2 = _interopRequireDefault(_RelationExpression); | ||
var _ManyToManyRelation = require('../relations/ManyToManyRelation'); | ||
var _ManyToManyRelation2 = _interopRequireDefault(_ManyToManyRelation); | ||
var _OneToManyRelation = require('../relations/OneToManyRelation'); | ||
var _OneToManyRelation2 = _interopRequireDefault(_OneToManyRelation); | ||
var _OneToOneRelation = require('../relations/OneToOneRelation'); | ||
var _OneToOneRelation2 = _interopRequireDefault(_OneToOneRelation); | ||
var _ValidationError = require('../ValidationError'); | ||
var _ValidationError2 = _interopRequireDefault(_ValidationError); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var Model = undefined; | ||
/** | ||
@@ -16,201 +56,298 @@ * Given an model with nested relations, finds a fast way to insert the models into | ||
* | ||
* This class assumes that all foreign key references have a not-null constraint. | ||
* | ||
* By the way, the code in this module is ugly as hell because of stupid micro-optimizations :| | ||
* | ||
* @constructor | ||
* @ignore | ||
*/ | ||
function InsertWithRelated(opt) { | ||
// Lazy-load Model. | ||
Model = Model || require('./../model/Model'); | ||
this.modelClass = opt.modelClass; | ||
this.models = opt.models; | ||
this.allowedRelations = opt.allowedRelations || null; | ||
this.graph = this._buildDependencyGraph(); | ||
this.done = false; | ||
} | ||
var InsertWithRelated = (function () { | ||
function InsertWithRelated(_ref) { | ||
var modelClass = _ref.modelClass; | ||
var models = _ref.models; | ||
var allowedRelations = _ref.allowedRelations; | ||
(0, _classCallCheck3.default)(this, InsertWithRelated); | ||
/** | ||
* @returns {DependencyGraph} | ||
* @private | ||
*/ | ||
InsertWithRelated.prototype._buildDependencyGraph = function () { | ||
var graph = new DependencyGraph(this.allowedRelations); | ||
graph.build(this.modelClass, this.models); | ||
return graph; | ||
}; | ||
// Lazy-load Model. | ||
Model = Model || require('./../model/Model').default; | ||
InsertWithRelated.prototype.execute = function (inserter) { | ||
return this.executeNextBatch(inserter); | ||
}; | ||
this.modelClass = modelClass; | ||
this.models = models; | ||
this.allowedRelations = allowedRelations || null; | ||
this.done = false; | ||
this.graph = this._buildDependencyGraph(); | ||
} | ||
InsertWithRelated.prototype.executeNextBatch = function (inserter) { | ||
var self = this; | ||
var batch = this.nextBatch(); | ||
/** | ||
* @param {function(TableInsertion)} inserter | ||
* @return {Promise} | ||
*/ | ||
if (!batch) { | ||
// TODO turn this into a function. | ||
for (var n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
var refNode = this.graph.nodes[n]; | ||
var ref = getUidRef(refNode.model); | ||
(0, _createClass3.default)(InsertWithRelated, [{ | ||
key: 'execute', | ||
value: function execute(inserter) { | ||
return this._executeNextBatch(inserter); | ||
} | ||
if (ref) { | ||
// Copy all the properties to the reference nodes. | ||
var actualNode = getNode(this.graph.nodesById, ref); | ||
var relations = actualNode.modelClass.getRelations(); | ||
/** | ||
* @returns {DependencyGraph} | ||
* @private | ||
*/ | ||
_.each(actualNode.model, function (value, key) { | ||
if (!getRelation(relations, key) && !_.isFunction(value)) { | ||
refNode.model[key] = value; | ||
}, { | ||
key: '_buildDependencyGraph', | ||
value: function _buildDependencyGraph() { | ||
var graph = new DependencyGraph(this.allowedRelations); | ||
graph.build(this.modelClass, this.models); | ||
return graph; | ||
} | ||
/** | ||
* @param {function(TableInsertion)} inserter | ||
* @returns {Promise} | ||
* @private | ||
*/ | ||
}, { | ||
key: '_executeNextBatch', | ||
value: function _executeNextBatch(inserter) { | ||
var _this = this; | ||
var batch = this._nextBatch(); | ||
if (!batch) { | ||
// If we get here, we are done. All we need to do now is to finalize the object graph | ||
// and return it as the final output. | ||
return this._finalize(); | ||
} | ||
// Insert the batch using the `inserter` function. | ||
return _bluebird2.default.all(_lodash2.default.map(batch, function (tableInsertion) { | ||
var uids = undefined; | ||
if (!tableInsertion.isJoinTableInsertion) { | ||
// We need to omit the uid properties so that they don't get inserted | ||
// into the database. Join table insertions never have uids. | ||
uids = _this._omitUids(tableInsertion); | ||
} | ||
return inserter(tableInsertion).then(function () { | ||
if (!tableInsertion.isJoinTableInsertion) { | ||
// Resolve dependencies to the inserted objects. | ||
_this._resolveDepsForInsertion(tableInsertion, uids); | ||
} | ||
}); | ||
})).then(function () { | ||
return _this._executeNextBatch(inserter); | ||
}); | ||
} | ||
refNode.model.$omit(refNode.modelClass.uidProp, refNode.modelClass.uidRefProp); | ||
/** | ||
* @private | ||
* @returns {Object.<string, TableInsertion>} | ||
*/ | ||
}, { | ||
key: '_nextBatch', | ||
value: function _nextBatch() { | ||
if (this.done) { | ||
return null; | ||
} | ||
var batch = this._createBatch(); | ||
if (_lodash2.default.isEmpty(batch)) { | ||
this.done = true; | ||
return this._createManyToManyRelationJoinRowBatch(); | ||
} else { | ||
this._markBatchHandled(batch); | ||
return batch; | ||
} | ||
} | ||
return Promise.resolve(this.models); | ||
} | ||
/** | ||
* @private | ||
* @returns {Object.<string, TableInsertion>} | ||
*/ | ||
return Promise.all(_.map(batch, function (tableInsertion) { | ||
var ids; | ||
}, { | ||
key: '_createBatch', | ||
value: function _createBatch() { | ||
var batch = (0, _create2.default)(null); | ||
var nodes = this.graph.nodes; | ||
// If the insertion is a model insertion instead of join row insertion, | ||
// we need to delete the uid properties so that they don't get inserted | ||
// into the database. | ||
if (tableInsertion.modelClass) { | ||
ids = _.pluck(tableInsertion.models, tableInsertion.modelClass.uidProp); | ||
for (var n = 0, ln = nodes.length; n < ln; ++n) { | ||
var node = nodes[n]; | ||
for (var m = 0, lm = tableInsertion.models.length; m < lm; ++m) { | ||
tableInsertion.models[m].$omit(tableInsertion.modelClass.uidProp); | ||
if (!node.handled && node.needs.length === node.numHandledNeeds) { | ||
var tableInsertion = getTableInsertion(batch, node.modelClass.tableName); | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(node.modelClass, false); | ||
setTableInsertion(batch, node.modelClass.tableName, tableInsertion); | ||
} | ||
tableInsertion.models.push(node.model); | ||
tableInsertion.isInputModel.push(isInputNode(this.graph.inputNodesById, node)); | ||
} | ||
} | ||
return batch; | ||
} | ||
return inserter(tableInsertion).then(function () { | ||
if (!tableInsertion.modelClass) { | ||
// The Many to many join row table insertions don't have a modelClass. | ||
return; | ||
} | ||
/** | ||
* @private | ||
* @param {Object.<string, TableInsertion>} batch | ||
*/ | ||
for (var m = 0, lm = tableInsertion.models.length; m < lm; ++m) { | ||
var node = getNode(self.graph.nodesById, ids[m]); | ||
var model = tableInsertion.models[m]; | ||
}, { | ||
key: '_markBatchHandled', | ||
value: function _markBatchHandled(batch) { | ||
var models = _lodash2.default.flatten(_lodash2.default.pluck(batch, 'models')); | ||
var nodes = this.graph.nodesById; | ||
for (var d = 0, ld = node.isNeededBy.length; d < ld; ++d) { | ||
var dep = node.isNeededBy[d]; | ||
dep.resolve(model); | ||
for (var m = 0, lm = models.length; m < lm; ++m) { | ||
var id = getUid(models[m]); | ||
var node = getNode(nodes, id); | ||
for (var nb = 0, lnb = node.isNeededBy.length; nb < lnb; ++nb) { | ||
var dep = node.isNeededBy[nb]; | ||
dep.node.numHandledNeeds++; | ||
} | ||
node.handled = true; | ||
} | ||
}); | ||
})).then(function () { | ||
return self.executeNextBatch(inserter); | ||
}) | ||
}; | ||
} | ||
InsertWithRelated.prototype.nextBatch = function () { | ||
if (this.done) { | ||
return null; | ||
} | ||
/** | ||
* @private | ||
* @returns {Object.<string, TableInsertion>} | ||
*/ | ||
var batch = this.createBatch(); | ||
}, { | ||
key: '_createManyToManyRelationJoinRowBatch', | ||
value: function _createManyToManyRelationJoinRowBatch() { | ||
var batch = (0, _create2.default)(null); | ||
var notUnique = (0, _create2.default)(null); | ||
if (_.isEmpty(batch)) { | ||
this.done = true; | ||
return this.createManyToManyRelationJoinRowBatch(); | ||
} else { | ||
this.markBatchHandled(batch); | ||
return batch; | ||
} | ||
}; | ||
for (var n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
var node = this.graph.nodes[n]; | ||
InsertWithRelated.prototype.createBatch = function () { | ||
var batch = Object.create(null); | ||
var nodes = this.graph.nodes; | ||
for (var m = 0, lm = node.manyToManyConnections.length; m < lm; ++m) { | ||
var conn = node.manyToManyConnections[m]; | ||
var tableInsertion = getTableInsertion(batch, conn.relation.joinTable); | ||
for (var n = 0, ln = nodes.length; n < ln; ++n) { | ||
var node = nodes[n]; | ||
var sourceVal = node.model[conn.relation.ownerProp]; | ||
var targetVal = conn.node.model[conn.relation.relatedProp]; | ||
if (!node.handled && node.needs.length === node.numHandledNeeds) { | ||
var tableInsertion = getTableInsertion(batch, node.modelClass.tableName); | ||
var uniqueKey = undefined; | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(node.modelClass, node.modelClass.tableName); | ||
setTableInsertion(batch, node.modelClass.tableName, tableInsertion) | ||
if (conn.relation.joinTableOwnerCol < conn.relation.joinTableRelatedCol) { | ||
uniqueKey = conn.relation.joinTable + '_' + sourceVal + '_' + targetVal; | ||
} else { | ||
uniqueKey = conn.relation.joinTable + '_' + targetVal + '_' + sourceVal; | ||
} | ||
if (notUnique[uniqueKey]) { | ||
continue; | ||
} | ||
notUnique[uniqueKey] = true; | ||
var joinModel = {}; | ||
joinModel[conn.relation.joinTableOwnerProp] = sourceVal; | ||
joinModel[conn.relation.joinTableRelatedProp] = targetVal; | ||
joinModel = conn.relation.joinTableModelClass.fromJson(joinModel); | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(conn.relation.joinTableModelClass, true); | ||
setTableInsertion(batch, conn.relation.joinTable, tableInsertion); | ||
} | ||
tableInsertion.models.push(joinModel); | ||
tableInsertion.isInputModel.push(false); | ||
} | ||
} | ||
tableInsertion.models.push(node.model); | ||
tableInsertion.isInputModel.push(isInputNode(this.graph.inputNodesById, node)); | ||
return batch; | ||
} | ||
} | ||
return batch; | ||
}; | ||
/** | ||
* @private | ||
*/ | ||
InsertWithRelated.prototype.markBatchHandled = function (batch) { | ||
var models = _.flatten(_.pluck(batch, 'models')); | ||
var nodes = this.graph.nodesById; | ||
}, { | ||
key: '_omitUids', | ||
value: function _omitUids(tableInsertion) { | ||
var ids = _lodash2.default.pluck(tableInsertion.models, tableInsertion.modelClass.uidProp); | ||
for (var m = 0, lm = models.length; m < lm; ++m) { | ||
var id = getUid(models[m]); | ||
var node = getNode(nodes, id); | ||
for (var m = 0, lm = tableInsertion.models.length; m < lm; ++m) { | ||
tableInsertion.models[m].$omit(tableInsertion.modelClass.uidProp); | ||
} | ||
for (var nb = 0, lnb = node.isNeededBy.length; nb < lnb; ++nb) { | ||
var dep = node.isNeededBy[nb]; | ||
dep.node.numHandledNeeds++; | ||
return ids; | ||
} | ||
node.handled = true; | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {TableInsertion} tableInsertion | ||
* @param {Array.<string>} uids | ||
*/ | ||
InsertWithRelated.prototype.createManyToManyRelationJoinRowBatch = function () { | ||
var batch = Object.create(null); | ||
var notUnique = Object.create(null); | ||
}, { | ||
key: '_resolveDepsForInsertion', | ||
value: function _resolveDepsForInsertion(tableInsertion, uids) { | ||
for (var m = 0, lm = tableInsertion.models.length; m < lm; ++m) { | ||
var node = getNode(this.graph.nodesById, uids[m]); | ||
var model = tableInsertion.models[m]; | ||
for (var n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
var node = this.graph.nodes[n]; | ||
for (var d = 0, ld = node.isNeededBy.length; d < ld; ++d) { | ||
var dep = node.isNeededBy[d]; | ||
dep.resolve(model); | ||
} | ||
} | ||
} | ||
for (var m = 0, lm = node.manyToManyConnections.length; m < lm; ++m) { | ||
var conn = node.manyToManyConnections[m]; | ||
var tableInsertion = getTableInsertion(batch, conn.relation.joinTable); | ||
/** | ||
* @private | ||
* @return {Promise} | ||
*/ | ||
var sourceVal = node.model[conn.relation.ownerProp]; | ||
var targetVal = conn.node.model[conn.relation.relatedProp]; | ||
}, { | ||
key: '_finalize', | ||
value: function _finalize() { | ||
var _this2 = this; | ||
var uniqueKey; | ||
if (conn.relation.joinTableOwnerCol < conn.relation.joinTableRelatedCol) { | ||
uniqueKey = conn.relation.joinTable + '_' + sourceVal + '_' + targetVal; | ||
} else { | ||
uniqueKey = conn.relation.joinTable + '_' + targetVal + '_' + sourceVal; | ||
} | ||
var _loop = function _loop(n, ln) { | ||
var refNode = _this2.graph.nodes[n]; | ||
var ref = getUidRef(refNode.model); | ||
if (notUnique[uniqueKey]) { | ||
continue; | ||
} | ||
if (ref) { | ||
(function () { | ||
// Copy all the properties to the reference nodes. | ||
var actualNode = getNode(_this2.graph.nodesById, ref); | ||
var relations = actualNode.modelClass.getRelations(); | ||
var joinRow = {}; | ||
_lodash2.default.each(actualNode.model, function (value, key) { | ||
if (!getRelation(relations, key) && !_lodash2.default.isFunction(value)) { | ||
refNode.model[key] = value; | ||
} | ||
}); | ||
notUnique[uniqueKey] = true; | ||
joinRow[conn.relation.joinTableOwnerCol] = sourceVal; | ||
joinRow[conn.relation.joinTableRelatedCol] = targetVal; | ||
refNode.model.$omit(refNode.modelClass.uidProp, refNode.modelClass.uidRefProp); | ||
})(); | ||
} | ||
}; | ||
if (!tableInsertion) { | ||
tableInsertion = new TableInsertion(null, conn.relation.joinTable); | ||
setTableInsertion(batch, conn.relation.joinTable, tableInsertion) | ||
for (var n = 0, ln = this.graph.nodes.length; n < ln; ++n) { | ||
_loop(n, ln); | ||
} | ||
tableInsertion.models.push(joinRow); | ||
tableInsertion.isInputModel.push(false); | ||
return _bluebird2.default.resolve(this.models); | ||
} | ||
} | ||
}]); | ||
return InsertWithRelated; | ||
})(); | ||
return batch; | ||
}; | ||
exports.default = InsertWithRelated; | ||
function TableInsertion(modelClass, tableName) { | ||
function TableInsertion(modelClass, isJoinTableInsertion) { | ||
this.modelClass = modelClass; | ||
this.tableName = tableName; | ||
this.isJoinTableInsertion = isJoinTableInsertion; | ||
this.models = []; | ||
@@ -247,4 +384,4 @@ this.isInputModel = []; | ||
this.allowedRelations = allowedRelations; | ||
this.nodesById = Object.create(null); | ||
this.inputNodesById = Object.create(null); | ||
this.nodesById = (0, _create2.default)(null); | ||
this.inputNodesById = (0, _create2.default)(null); | ||
this.nodes = []; | ||
@@ -257,7 +394,7 @@ this.uid = 0; | ||
this.nodesById = Object.create(null); | ||
this.nodesById = (0, _create2.default)(null); | ||
this.nodes = []; | ||
if (_.isArray(models)) { | ||
_.each(models, function (model) { | ||
if (_lodash2.default.isArray(models)) { | ||
_lodash2.default.each(models, function (model) { | ||
self.buildForModel(modelClass, model, null, null, self.allowedRelations); | ||
@@ -273,3 +410,3 @@ }); | ||
if (this.isCyclic(this.nodes)) { | ||
throw new ValidationError({cyclic: 'the object graph contains cyclic references'}); | ||
throw new _ValidationError2.default({ cyclic: 'the object graph contains cyclic references' }); | ||
} | ||
@@ -282,7 +419,7 @@ | ||
if (!(model instanceof Model)) { | ||
throw new ValidationError({notModel: 'the object graph contains cyclic references'}); | ||
throw new _ValidationError2.default({ notModel: 'the object graph contains cyclic references' }); | ||
} | ||
if (!getUid(model)) { | ||
setUid(model, '__objection_uid(' + (++this.uid) + ')__'); | ||
setUid(model, '__objection_uid(' + ++this.uid + ')__'); | ||
} | ||
@@ -299,3 +436,3 @@ | ||
if (rel instanceof OneToManyRelation) { | ||
if (rel instanceof _OneToManyRelation2.default) { | ||
@@ -309,5 +446,4 @@ node.needs.push(new Dependency(parentNode, function (model) { | ||
})); | ||
} else if (rel instanceof _OneToOneRelation2.default) { | ||
} else if (rel instanceof OneToOneRelation) { | ||
node.isNeededBy.push(new Dependency(parentNode, function (model) { | ||
@@ -320,4 +456,3 @@ this.node.model[rel.ownerProp] = model[rel.relatedProp]; | ||
})); | ||
} else if (rel instanceof ManyToManyRelation) { | ||
} else if (rel instanceof _ManyToManyRelation2.default) { | ||
// ManyToManyRelations create no dependencies since we can create the | ||
@@ -341,11 +476,11 @@ // join table rows after everything else has been inserted. | ||
if (relModels && allowedRelations instanceof RelationExpression) { | ||
if (relModels && allowedRelations instanceof _RelationExpression2.default) { | ||
nextAllowed = allowedRelations.childExpression(relName); | ||
if (!nextAllowed) { | ||
throw new ValidationError({allowedRelations: 'trying to insert an unallowed relation'}); | ||
throw new _ValidationError2.default({ allowedRelations: 'trying to insert an unallowed relation' }); | ||
} | ||
} | ||
if (_.isArray(relModels)) { | ||
if (_lodash2.default.isArray(relModels)) { | ||
for (var i = 0, l = relModels.length; i < l; ++i) { | ||
@@ -361,3 +496,3 @@ this.buildForModel(rel.relatedModelClass, relModels[i], node, rel, nextAllowed); | ||
DependencyGraph.prototype.solveReferences = function () { | ||
var refMap = Object.create(null); | ||
var refMap = (0, _create2.default)(null); | ||
@@ -373,34 +508,35 @@ // First merge all reference nodes into the actual node. | ||
for (var n = 0, ln = this.nodes.length; n < ln; ++n) { | ||
var refNode = this.nodes[n]; | ||
var _refNode = this.nodes[n]; | ||
if (refNode.handled) { | ||
if (_refNode.handled) { | ||
continue; | ||
} | ||
var ref = getUidRef(refNode.model); | ||
var _ref2 = getUidRef(_refNode.model); | ||
if (ref) { | ||
var actualNode = getNode(this.nodesById, ref); | ||
if (_ref2) { | ||
var actualNode = getNode(this.nodesById, _ref2); | ||
if (!actualNode) { | ||
throw new ValidationError({ref: 'could not resolve reference "' + ref + '"'}); | ||
throw new _ValidationError2.default({ ref: 'could not resolve reference "' + _ref2 + '"' }); | ||
} | ||
var d, ld; | ||
var d = undefined, | ||
ld = undefined; | ||
for (d = 0, ld = refNode.needs.length; d < ld; ++d) { | ||
actualNode.needs.push(refNode.needs[d]); | ||
for (d = 0, ld = _refNode.needs.length; d < ld; ++d) { | ||
actualNode.needs.push(_refNode.needs[d]); | ||
} | ||
for (d = 0, ld = refNode.isNeededBy.length; d < ld; ++d) { | ||
actualNode.isNeededBy.push(refNode.isNeededBy[d]); | ||
for (d = 0, ld = _refNode.isNeededBy.length; d < ld; ++d) { | ||
actualNode.isNeededBy.push(_refNode.isNeededBy[d]); | ||
} | ||
for (var m = 0, lm = refNode.manyToManyConnections.length; m < lm; ++m) { | ||
actualNode.manyToManyConnections.push(refNode.manyToManyConnections[m]); | ||
for (var m = 0, lm = _refNode.manyToManyConnections.length; m < lm; ++m) { | ||
actualNode.manyToManyConnections.push(_refNode.manyToManyConnections[m]); | ||
} | ||
setRefMap(refMap, refNode.id, actualNode); | ||
setRefMap(refMap, _refNode.id, actualNode); | ||
refNode.handled = true; | ||
_refNode.handled = true; | ||
} | ||
@@ -413,3 +549,6 @@ } | ||
var node = this.nodes[n]; | ||
var d, ld, dep, actualNode; | ||
var d = undefined, | ||
ld = undefined, | ||
dep = undefined, | ||
actualNode = undefined; | ||
@@ -458,6 +597,7 @@ for (d = 0, ld = node.needs.length; d < ld; ++d) { | ||
var relations = node.modelClass.getRelations(); | ||
var isModel = obj instanceof Model; | ||
var self = this; | ||
_.each(obj, function (value, key) { | ||
if (obj instanceof Model && getRelation(relations, key)) { | ||
_lodash2.default.each(obj, function (value, key) { | ||
if (isModel && getRelation(relations, key)) { | ||
// Don't traverse the relations of model instances. | ||
@@ -469,3 +609,3 @@ return; | ||
if (_.isString(value)) { | ||
if (_lodash2.default.isString(value)) { | ||
allMatches(propRefRegex, value, function (matchResult) { | ||
@@ -479,3 +619,3 @@ var match = matchResult[0]; | ||
if (!refNode) { | ||
throw new ValidationError({ref: 'could not resolve reference "' + value + '"'}); | ||
throw new _ValidationError2.default({ ref: 'could not resolve reference "' + value + '"' }); | ||
} | ||
@@ -488,6 +628,6 @@ | ||
node.needs.push(new Dependency(refNode, function (model) { | ||
_.set(model, pathClone, this.node.model[refProp]); | ||
_lodash2.default.set(model, pathClone, this.node.model[refProp]); | ||
})); | ||
refNode.isNeededBy.push(new Dependency(node, function (model) { | ||
_.set(this.node.model, pathClone, model[refProp]); | ||
_lodash2.default.set(this.node.model, pathClone, model[refProp]); | ||
})); | ||
@@ -499,11 +639,11 @@ } else { | ||
value = value.replace(match, this.node.model[refProp]); | ||
_.set(model, pathClone, value); | ||
_lodash2.default.set(model, pathClone, value); | ||
})); | ||
refNode.isNeededBy.push(new Dependency(node, function (model) { | ||
value = value.replace(match, model[refProp]); | ||
_.set(this.node.model, pathClone, value); | ||
_lodash2.default.set(this.node.model, pathClone, value); | ||
})); | ||
} | ||
}); | ||
} else if (_.isObject(value)) { | ||
} else if (_lodash2.default.isObject(value)) { | ||
self.createNonRelationDepsForObject(value, node, path); | ||
@@ -616,4 +756,2 @@ } | ||
} | ||
} | ||
module.exports = InsertWithRelated; | ||
} |
@@ -1,2 +0,4 @@ | ||
module.exports = (function() { | ||
"use strict"; | ||
module.exports = (function () { | ||
/* | ||
@@ -9,3 +11,5 @@ * Generated by PEG.js 0.8.0. | ||
function peg$subclass(child, parent) { | ||
function ctor() { this.constructor = child; } | ||
function ctor() { | ||
this.constructor = child; | ||
} | ||
ctor.prototype = parent.prototype; | ||
@@ -16,10 +20,10 @@ child.prototype = new ctor(); | ||
function SyntaxError(message, expected, found, offset, line, column) { | ||
this.message = message; | ||
this.message = message; | ||
this.expected = expected; | ||
this.found = found; | ||
this.offset = offset; | ||
this.line = line; | ||
this.column = column; | ||
this.found = found; | ||
this.offset = offset; | ||
this.line = line; | ||
this.column = column; | ||
this.name = "SyntaxError"; | ||
this.name = "SyntaxError"; | ||
} | ||
@@ -31,8 +35,5 @@ | ||
var options = arguments.length > 1 ? arguments[1] : {}, | ||
peg$FAILED = {}, | ||
peg$startRuleFunctions = { start: peg$parsestart }, | ||
peg$startRuleFunction = peg$parsestart, | ||
peg$startRuleFunction = peg$parsestart, | ||
peg$c0 = peg$FAILED, | ||
@@ -43,11 +44,11 @@ peg$c1 = null, | ||
peg$c4 = [], | ||
peg$c5 = function(column, refs) { | ||
var access = []; | ||
if (refs) { | ||
var firstAccess = refs[1]; | ||
access = refs[2]; | ||
access.unshift(firstAccess); | ||
} | ||
return { columnName: column, access: access }; | ||
}, | ||
peg$c5 = function peg$c5(column, refs) { | ||
var access = []; | ||
if (refs) { | ||
var firstAccess = refs[1]; | ||
access = refs[2]; | ||
access.unshift(firstAccess); | ||
} | ||
return { columnName: column, access: access }; | ||
}, | ||
peg$c6 = "[", | ||
@@ -61,5 +62,11 @@ peg$c7 = { type: "literal", value: "[", description: "\"[\"" }, | ||
peg$c13 = { type: "literal", value: "]", description: "\"]\"" }, | ||
peg$c14 = function(key) { return { type: 'object', ref: Array.isArray(key) ? key[1] : key }; }, | ||
peg$c15 = function(index) { return { type: 'array', ref: parseInt(index, 10) }; }, | ||
peg$c16 = function(key) { return { type: 'object', ref: key }; }, | ||
peg$c14 = function peg$c14(key) { | ||
return { type: 'object', ref: Array.isArray(key) ? key[1] : key }; | ||
}, | ||
peg$c15 = function peg$c15(index) { | ||
return { type: 'array', ref: parseInt(index, 10) }; | ||
}, | ||
peg$c16 = function peg$c16(key) { | ||
return { type: 'object', ref: key }; | ||
}, | ||
peg$c17 = ".", | ||
@@ -69,3 +76,5 @@ peg$c18 = { type: "literal", value: ".", description: "\".\"" }, | ||
peg$c20 = { type: "class", value: "[^\\][]", description: "[^\\][]" }, | ||
peg$c21 = function(chars) { return chars.join(""); }, | ||
peg$c21 = function peg$c21(chars) { | ||
return chars.join(""); | ||
}, | ||
peg$c22 = /^[^:]/, | ||
@@ -81,12 +90,12 @@ peg$c23 = { type: "class", value: "[^:]", description: "[^:]" }, | ||
peg$c31 = { type: "class", value: "[0-9]", description: "[0-9]" }, | ||
peg$c32 = function(digits) { return digits.join(""); }, | ||
peg$currPos = 0, | ||
peg$reportedPos = 0, | ||
peg$cachedPos = 0, | ||
peg$c32 = function peg$c32(digits) { | ||
return digits.join(""); | ||
}, | ||
peg$currPos = 0, | ||
peg$reportedPos = 0, | ||
peg$cachedPos = 0, | ||
peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, | ||
peg$maxFailPos = 0, | ||
peg$maxFailExpected = [], | ||
peg$silentFails = 0, | ||
peg$maxFailPos = 0, | ||
peg$maxFailExpected = [], | ||
peg$silentFails = 0, | ||
peg$result; | ||
@@ -119,7 +128,3 @@ | ||
function expected(description) { | ||
throw peg$buildException( | ||
null, | ||
[{ type: "other", description: description }], | ||
peg$reportedPos | ||
); | ||
throw peg$buildException(null, [{ type: "other", description: description }], peg$reportedPos); | ||
} | ||
@@ -138,3 +143,5 @@ | ||
if (ch === "\n") { | ||
if (!details.seenCR) { details.line++; } | ||
if (!details.seenCR) { | ||
details.line++; | ||
} | ||
details.column = 1; | ||
@@ -166,3 +173,5 @@ details.seenCR = false; | ||
function peg$fail(expected) { | ||
if (peg$currPos < peg$maxFailPos) { return; } | ||
if (peg$currPos < peg$maxFailPos) { | ||
return; | ||
} | ||
@@ -181,3 +190,3 @@ if (peg$currPos > peg$maxFailPos) { | ||
expected.sort(function(a, b) { | ||
expected.sort(function (a, b) { | ||
if (a.description < b.description) { | ||
@@ -203,20 +212,21 @@ return -1; | ||
function stringEscape(s) { | ||
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } | ||
function hex(ch) { | ||
return ch.charCodeAt(0).toString(16).toUpperCase(); | ||
} | ||
return s | ||
.replace(/\\/g, '\\\\') | ||
.replace(/"/g, '\\"') | ||
.replace(/\x08/g, '\\b') | ||
.replace(/\t/g, '\\t') | ||
.replace(/\n/g, '\\n') | ||
.replace(/\f/g, '\\f') | ||
.replace(/\r/g, '\\r') | ||
.replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) | ||
.replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) | ||
.replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) | ||
.replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); | ||
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\x08/g, '\\b').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\f/g, '\\f').replace(/\r/g, '\\r').replace(/[\x00-\x07\x0B\x0E\x0F]/g, function (ch) { | ||
return '\\x0' + hex(ch); | ||
}).replace(/[\x10-\x1F\x80-\xFF]/g, function (ch) { | ||
return '\\x' + hex(ch); | ||
}).replace(/[\u0180-\u0FFF]/g, function (ch) { | ||
return "\\u0" + hex(ch); | ||
}).replace(/[\u1080-\uFFFF]/g, function (ch) { | ||
return "\\u" + hex(ch); | ||
}); | ||
} | ||
var expectedDescs = new Array(expected.length), | ||
expectedDesc, foundDesc, i; | ||
expectedDesc, | ||
foundDesc, | ||
i; | ||
@@ -227,7 +237,3 @@ for (i = 0; i < expected.length; i++) { | ||
expectedDesc = expected.length > 1 | ||
? expectedDescs.slice(0, -1).join(", ") | ||
+ " or " | ||
+ expectedDescs[expected.length - 1] | ||
: expectedDescs[0]; | ||
expectedDesc = expected.length > 1 ? expectedDescs.slice(0, -1).join(", ") + " or " + expectedDescs[expected.length - 1] : expectedDescs[0]; | ||
@@ -240,3 +246,3 @@ foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; | ||
var posDetails = peg$computePosDetails(pos), | ||
found = pos < input.length ? input.charAt(pos) : null; | ||
found = pos < input.length ? input.charAt(pos) : null; | ||
@@ -247,10 +253,3 @@ if (expected !== null) { | ||
return new SyntaxError( | ||
message !== null ? message : buildMessage(expected, found), | ||
expected, | ||
found, | ||
pos, | ||
posDetails.line, | ||
posDetails.column | ||
); | ||
return new SyntaxError(message !== null ? message : buildMessage(expected, found), expected, found, pos, posDetails.line, posDetails.column); | ||
} | ||
@@ -270,3 +269,5 @@ | ||
s3 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c3); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c3); | ||
} | ||
} | ||
@@ -343,3 +344,5 @@ if (s3 !== peg$FAILED) { | ||
s1 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c7); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c7); | ||
} | ||
} | ||
@@ -353,3 +356,5 @@ if (s1 !== peg$FAILED) { | ||
s3 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c9); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c9); | ||
} | ||
} | ||
@@ -364,3 +369,5 @@ if (s3 !== peg$FAILED) { | ||
s5 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c9); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c9); | ||
} | ||
} | ||
@@ -389,3 +396,5 @@ if (s5 !== peg$FAILED) { | ||
s3 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c11); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c11); | ||
} | ||
} | ||
@@ -400,3 +409,5 @@ if (s3 !== peg$FAILED) { | ||
s5 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c11); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c11); | ||
} | ||
} | ||
@@ -428,3 +439,5 @@ if (s5 !== peg$FAILED) { | ||
s3 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c13); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c13); | ||
} | ||
} | ||
@@ -460,3 +473,5 @@ if (s3 !== peg$FAILED) { | ||
s1 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c7); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c7); | ||
} | ||
} | ||
@@ -471,3 +486,5 @@ if (s1 !== peg$FAILED) { | ||
s3 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c13); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c13); | ||
} | ||
} | ||
@@ -517,3 +534,5 @@ if (s3 !== peg$FAILED) { | ||
s1 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c18); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c18); | ||
} | ||
} | ||
@@ -548,3 +567,5 @@ if (s1 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c20); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c20); | ||
} | ||
} | ||
@@ -559,3 +580,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c20); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c20); | ||
} | ||
} | ||
@@ -585,3 +608,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c23); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c23); | ||
} | ||
} | ||
@@ -596,3 +621,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c23); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c23); | ||
} | ||
} | ||
@@ -622,3 +649,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c25); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c25); | ||
} | ||
} | ||
@@ -633,3 +662,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c25); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c25); | ||
} | ||
} | ||
@@ -659,3 +690,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c27); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c27); | ||
} | ||
} | ||
@@ -670,3 +703,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c27); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c27); | ||
} | ||
} | ||
@@ -696,3 +731,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c29); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c29); | ||
} | ||
} | ||
@@ -707,3 +744,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c29); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c29); | ||
} | ||
} | ||
@@ -733,3 +772,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c31); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c31); | ||
} | ||
} | ||
@@ -744,3 +785,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c31); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c31); | ||
} | ||
} | ||
@@ -775,4 +818,4 @@ } | ||
SyntaxError: SyntaxError, | ||
parse: parse | ||
parse: parse | ||
}; | ||
})(); | ||
})(); |
@@ -1,2 +0,4 @@ | ||
module.exports = (function() { | ||
"use strict"; | ||
module.exports = (function () { | ||
"use strict"; | ||
@@ -11,3 +13,5 @@ | ||
function peg$subclass(child, parent) { | ||
function ctor() { this.constructor = child; } | ||
function ctor() { | ||
this.constructor = child; | ||
} | ||
ctor.prototype = parent.prototype; | ||
@@ -18,7 +22,7 @@ child.prototype = new ctor(); | ||
function peg$SyntaxError(message, expected, found, location) { | ||
this.message = message; | ||
this.message = message; | ||
this.expected = expected; | ||
this.found = found; | ||
this.found = found; | ||
this.location = location; | ||
this.name = "SyntaxError"; | ||
this.name = "SyntaxError"; | ||
@@ -34,31 +38,28 @@ if (typeof Error.captureStackTrace === "function") { | ||
var options = arguments.length > 1 ? arguments[1] : {}, | ||
parser = this, | ||
parser = this, | ||
peg$FAILED = {}, | ||
peg$startRuleFunctions = { start: peg$parsestart }, | ||
peg$startRuleFunction = peg$parsestart, | ||
peg$c0 = function(expr) { | ||
var node = {name: null, args: [], numChildren: 1, children: {}}; | ||
node.children[expr.name] = expr; | ||
return node; | ||
}, | ||
peg$c1 = function(list) { | ||
return {name: null, args: [], numChildren: list.list.length, children: list.byName}; | ||
}, | ||
peg$c2 = function(rel, args, list) { | ||
return {name: rel, args: args || [], numChildren: list.list.length, children: list.byName}; | ||
}, | ||
peg$c3 = function(rel, args, sub) { | ||
var node = {name: rel, args: args || [], numChildren: 0, children: {}}; | ||
if (sub) { | ||
node.numChildren = 1; | ||
node.children[sub.name] = sub; | ||
} | ||
return node; | ||
}, | ||
peg$c4 = function(rel) { | ||
return rel.join('') | ||
}, | ||
peg$startRuleFunction = peg$parsestart, | ||
peg$c0 = function peg$c0(expr) { | ||
var node = { name: null, args: [], numChildren: 1, children: {} }; | ||
node.children[expr.name] = expr; | ||
return node; | ||
}, | ||
peg$c1 = function peg$c1(list) { | ||
return { name: null, args: [], numChildren: list.list.length, children: list.byName }; | ||
}, | ||
peg$c2 = function peg$c2(rel, args, list) { | ||
return { name: rel, args: args || [], numChildren: list.list.length, children: list.byName }; | ||
}, | ||
peg$c3 = function peg$c3(rel, args, sub) { | ||
var node = { name: rel, args: args || [], numChildren: 0, children: {} }; | ||
if (sub) { | ||
node.numChildren = 1; | ||
node.children[sub.name] = sub; | ||
} | ||
return node; | ||
}, | ||
peg$c4 = function peg$c4(rel) { | ||
return rel.join(''); | ||
}, | ||
peg$c5 = /^[^[\](),. \t\r\n]/, | ||
@@ -70,10 +71,10 @@ peg$c6 = { type: "class", value: "[^\\[\\]\\(\\),\\. \\t\\r\\n]", description: "[^\\[\\]\\(\\),\\. \\t\\r\\n]" }, | ||
peg$c10 = { type: "literal", value: ")", description: "\")\"" }, | ||
peg$c11 = function(args) { | ||
return args | ||
}, | ||
peg$c11 = function peg$c11(args) { | ||
return args; | ||
}, | ||
peg$c12 = ",", | ||
peg$c13 = { type: "literal", value: ",", description: "\",\"" }, | ||
peg$c14 = function(arg) { | ||
return arg | ||
}, | ||
peg$c14 = function peg$c14(arg) { | ||
return arg; | ||
}, | ||
peg$c15 = /^[ \t\r\n]/, | ||
@@ -83,5 +84,5 @@ peg$c16 = { type: "class", value: "[ \\t\\r\\n]", description: "[ \\t\\r\\n]" }, | ||
peg$c18 = { type: "literal", value: ".", description: "\".\"" }, | ||
peg$c19 = function(list) { | ||
return list | ||
}, | ||
peg$c19 = function peg$c19(list) { | ||
return list; | ||
}, | ||
peg$c20 = "[", | ||
@@ -91,28 +92,26 @@ peg$c21 = { type: "literal", value: "[", description: "\"[\"" }, | ||
peg$c23 = { type: "literal", value: "]", description: "\"]\"" }, | ||
peg$c24 = function(items) { | ||
var itemsByName = {}; | ||
peg$c24 = function peg$c24(items) { | ||
var itemsByName = {}; | ||
for (var i = 0; i < items.length; ++i) { | ||
itemsByName[items[i].name] = items[i]; | ||
} | ||
for (var i = 0; i < items.length; ++i) { | ||
itemsByName[items[i].name] = items[i]; | ||
} | ||
return { | ||
list: items, | ||
byName: itemsByName | ||
}; | ||
}, | ||
peg$c25 = function(expr) { | ||
return expr | ||
}, | ||
peg$c26 = function(sub) { | ||
return sub | ||
}, | ||
peg$currPos = 0, | ||
peg$savedPos = 0, | ||
peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }], | ||
peg$maxFailPos = 0, | ||
peg$maxFailExpected = [], | ||
peg$silentFails = 0, | ||
return { | ||
list: items, | ||
byName: itemsByName | ||
}; | ||
}, | ||
peg$c25 = function peg$c25(expr) { | ||
return expr; | ||
}, | ||
peg$c26 = function peg$c26(sub) { | ||
return sub; | ||
}, | ||
peg$currPos = 0, | ||
peg$savedPos = 0, | ||
peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }], | ||
peg$maxFailPos = 0, | ||
peg$maxFailExpected = [], | ||
peg$silentFails = 0, | ||
peg$result; | ||
@@ -137,17 +136,7 @@ | ||
function expected(description) { | ||
throw peg$buildException( | ||
null, | ||
[{ type: "other", description: description }], | ||
input.substring(peg$savedPos, peg$currPos), | ||
peg$computeLocation(peg$savedPos, peg$currPos) | ||
); | ||
throw peg$buildException(null, [{ type: "other", description: description }], input.substring(peg$savedPos, peg$currPos), peg$computeLocation(peg$savedPos, peg$currPos)); | ||
} | ||
function error(message) { | ||
throw peg$buildException( | ||
message, | ||
null, | ||
input.substring(peg$savedPos, peg$currPos), | ||
peg$computeLocation(peg$savedPos, peg$currPos) | ||
); | ||
throw peg$buildException(message, null, input.substring(peg$savedPos, peg$currPos), peg$computeLocation(peg$savedPos, peg$currPos)); | ||
} | ||
@@ -157,3 +146,4 @@ | ||
var details = peg$posDetailsCache[pos], | ||
p, ch; | ||
p, | ||
ch; | ||
@@ -170,3 +160,3 @@ if (details) { | ||
details = { | ||
line: details.line, | ||
line: details.line, | ||
column: details.column, | ||
@@ -179,3 +169,5 @@ seenCR: details.seenCR | ||
if (ch === "\n") { | ||
if (!details.seenCR) { details.line++; } | ||
if (!details.seenCR) { | ||
details.line++; | ||
} | ||
details.column = 1; | ||
@@ -202,3 +194,3 @@ details.seenCR = false; | ||
var startPosDetails = peg$computePosDetails(startPos), | ||
endPosDetails = peg$computePosDetails(endPos); | ||
endPosDetails = peg$computePosDetails(endPos); | ||
@@ -208,3 +200,3 @@ return { | ||
offset: startPos, | ||
line: startPosDetails.line, | ||
line: startPosDetails.line, | ||
column: startPosDetails.column | ||
@@ -214,3 +206,3 @@ }, | ||
offset: endPos, | ||
line: endPosDetails.line, | ||
line: endPosDetails.line, | ||
column: endPosDetails.column | ||
@@ -222,3 +214,5 @@ } | ||
function peg$fail(expected) { | ||
if (peg$currPos < peg$maxFailPos) { return; } | ||
if (peg$currPos < peg$maxFailPos) { | ||
return; | ||
} | ||
@@ -237,3 +231,3 @@ if (peg$currPos > peg$maxFailPos) { | ||
expected.sort(function(a, b) { | ||
expected.sort(function (a, b) { | ||
if (a.description < b.description) { | ||
@@ -259,20 +253,21 @@ return -1; | ||
function stringEscape(s) { | ||
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } | ||
function hex(ch) { | ||
return ch.charCodeAt(0).toString(16).toUpperCase(); | ||
} | ||
return s | ||
.replace(/\\/g, '\\\\') | ||
.replace(/"/g, '\\"') | ||
.replace(/\x08/g, '\\b') | ||
.replace(/\t/g, '\\t') | ||
.replace(/\n/g, '\\n') | ||
.replace(/\f/g, '\\f') | ||
.replace(/\r/g, '\\r') | ||
.replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) | ||
.replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) | ||
.replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) | ||
.replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); | ||
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\x08/g, '\\b').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\f/g, '\\f').replace(/\r/g, '\\r').replace(/[\x00-\x07\x0B\x0E\x0F]/g, function (ch) { | ||
return '\\x0' + hex(ch); | ||
}).replace(/[\x10-\x1F\x80-\xFF]/g, function (ch) { | ||
return '\\x' + hex(ch); | ||
}).replace(/[\u0100-\u0FFF]/g, function (ch) { | ||
return "\\u0" + hex(ch); | ||
}).replace(/[\u1000-\uFFFF]/g, function (ch) { | ||
return "\\u" + hex(ch); | ||
}); | ||
} | ||
var expectedDescs = new Array(expected.length), | ||
expectedDesc, foundDesc, i; | ||
expectedDesc, | ||
foundDesc, | ||
i; | ||
@@ -283,7 +278,3 @@ for (i = 0; i < expected.length; i++) { | ||
expectedDesc = expected.length > 1 | ||
? expectedDescs.slice(0, -1).join(", ") | ||
+ " or " | ||
+ expectedDescs[expected.length - 1] | ||
: expectedDescs[0]; | ||
expectedDesc = expected.length > 1 ? expectedDescs.slice(0, -1).join(", ") + " or " + expectedDescs[expected.length - 1] : expectedDescs[0]; | ||
@@ -299,8 +290,3 @@ foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; | ||
return new peg$SyntaxError( | ||
message !== null ? message : buildMessage(expected, found), | ||
expected, | ||
found, | ||
location | ||
); | ||
return new peg$SyntaxError(message !== null ? message : buildMessage(expected, found), expected, found, location); | ||
} | ||
@@ -424,3 +410,5 @@ | ||
s0 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c6); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c6); | ||
} | ||
} | ||
@@ -442,3 +430,5 @@ | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c8); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c8); | ||
} | ||
} | ||
@@ -458,3 +448,5 @@ if (s2 !== peg$FAILED) { | ||
s4 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c10); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c10); | ||
} | ||
} | ||
@@ -506,3 +498,5 @@ if (s4 !== peg$FAILED) { | ||
s4 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c13); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c13); | ||
} | ||
} | ||
@@ -551,3 +545,5 @@ if (s4 === peg$FAILED) { | ||
s1 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c16); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c16); | ||
} | ||
} | ||
@@ -561,3 +557,5 @@ while (s1 !== peg$FAILED) { | ||
s1 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c16); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c16); | ||
} | ||
} | ||
@@ -603,3 +601,5 @@ } | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c18); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c18); | ||
} | ||
} | ||
@@ -651,3 +651,5 @@ if (s2 !== peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c21); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c21); | ||
} | ||
} | ||
@@ -667,3 +669,5 @@ if (s2 !== peg$FAILED) { | ||
s4 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c23); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c23); | ||
} | ||
} | ||
@@ -715,3 +719,5 @@ if (s4 !== peg$FAILED) { | ||
s4 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c13); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c13); | ||
} | ||
} | ||
@@ -762,3 +768,5 @@ if (s4 === peg$FAILED) { | ||
s2 = peg$FAILED; | ||
if (peg$silentFails === 0) { peg$fail(peg$c18); } | ||
if (peg$silentFails === 0) { | ||
peg$fail(peg$c18); | ||
} | ||
} | ||
@@ -808,10 +816,3 @@ if (s2 !== peg$FAILED) { | ||
throw peg$buildException( | ||
null, | ||
peg$maxFailExpected, | ||
peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, | ||
peg$maxFailPos < input.length | ||
? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) | ||
: peg$computeLocation(peg$maxFailPos, peg$maxFailPos) | ||
); | ||
throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)); | ||
} | ||
@@ -822,4 +823,4 @@ } | ||
SyntaxError: peg$SyntaxError, | ||
parse: peg$parse | ||
parse: peg$parse | ||
}; | ||
})(); | ||
})(); |
'use strict'; | ||
var _ = require('lodash') | ||
, jsonFieldExpressionParser = require('./parsers/jsonFieldExpressionParser') | ||
, InsertionOrUpdate = require('./InsertionOrUpdate') | ||
, utils = require('./../utils'); | ||
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _dec34, _dec35, _dec36, _dec37, _dec38, _dec39, _dec40, _dec41, _dec42, _dec43, _dec44, _dec45, _dec46, _dec47, _dec48, _dec49, _dec50, _dec51, _dec52, _dec53, _dec54, _dec55, _dec56, _dec57, _dec58, _dec59, _dec60, _dec61, _dec62, _dec63, _dec64, _dec65, _dec66, _dec67, _dec68, _dec69, _dec70, _dec71, _dec72, _desc, _value, _class; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _stringify = require('babel-runtime/core-js/json/stringify'); | ||
var _stringify2 = _interopRequireDefault(_stringify); | ||
var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); | ||
var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _jsonFieldExpressionParser = require('./parsers/jsonFieldExpressionParser'); | ||
var _jsonFieldExpressionParser2 = _interopRequireDefault(_jsonFieldExpressionParser); | ||
var _InsertionOrUpdate = require('./InsertionOrUpdate'); | ||
var _InsertionOrUpdate2 = _interopRequireDefault(_InsertionOrUpdate); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
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; | ||
} | ||
/** | ||
@@ -17,1105 +82,1409 @@ * Knex query builder wrapper. | ||
*/ | ||
function QueryBuilderBase(knex) { | ||
this._knex = knex; | ||
this._knexMethodCalls = []; | ||
} | ||
var QueryBuilderBase = (_dec = knexQueryMethod(), _dec2 = knexQueryMethod(), _dec3 = knexQueryMethod(), _dec4 = knexQueryMethod('delete'), _dec5 = knexQueryMethod(), _dec6 = knexQueryMethod(), _dec7 = knexQueryMethod(), _dec8 = knexQueryMethod(), _dec9 = knexQueryMethod(), _dec10 = knexQueryMethod(), _dec11 = knexQueryMethod(), _dec12 = knexQueryMethod(), _dec13 = knexQueryMethod(), _dec14 = knexQueryMethod(), _dec15 = knexQueryMethod(), _dec16 = knexQueryMethod(), _dec17 = knexQueryMethod(), _dec18 = knexQueryMethod(), _dec19 = knexQueryMethod(), _dec20 = knexQueryMethod(), _dec21 = knexQueryMethod(), _dec22 = knexQueryMethod(), _dec23 = knexQueryMethod(), _dec24 = knexQueryMethod(), _dec25 = knexQueryMethod(), _dec26 = knexQueryMethod(), _dec27 = knexQueryMethod(), _dec28 = knexQueryMethod(), _dec29 = knexQueryMethod(), _dec30 = knexQueryMethod(), _dec31 = knexQueryMethod(), _dec32 = knexQueryMethod(), _dec33 = knexQueryMethod(), _dec34 = knexQueryMethod(), _dec35 = knexQueryMethod(), _dec36 = knexQueryMethod(), _dec37 = knexQueryMethod(), _dec38 = knexQueryMethod(), _dec39 = knexQueryMethod(), _dec40 = knexQueryMethod(), _dec41 = knexQueryMethod(), _dec42 = knexQueryMethod(), _dec43 = knexQueryMethod(), _dec44 = knexQueryMethod(), _dec45 = knexQueryMethod(), _dec46 = knexQueryMethod(), _dec47 = knexQueryMethod(), _dec48 = knexQueryMethod(), _dec49 = knexQueryMethod(), _dec50 = knexQueryMethod(), _dec51 = knexQueryMethod(), _dec52 = knexQueryMethod(), _dec53 = knexQueryMethod(), _dec54 = knexQueryMethod(), _dec55 = knexQueryMethod(), _dec56 = knexQueryMethod(), _dec57 = knexQueryMethod(), _dec58 = knexQueryMethod(), _dec59 = knexQueryMethod(), _dec60 = knexQueryMethod(), _dec61 = knexQueryMethod(), _dec62 = knexQueryMethod(), _dec63 = knexQueryMethod(), _dec64 = knexQueryMethod(), _dec65 = knexQueryMethod(), _dec66 = knexQueryMethod(), _dec67 = knexQueryMethod(), _dec68 = knexQueryMethod(), _dec69 = knexQueryMethod(), _dec70 = knexQueryMethod(), _dec71 = knexQueryMethod(), _dec72 = knexQueryMethod(), (_class = (function () { | ||
function QueryBuilderBase(knex) { | ||
(0, _classCallCheck3.default)(this, QueryBuilderBase); | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
QueryBuilderBase.extend = function (subclassConstructor) { | ||
utils.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
}; | ||
this._knex = knex; | ||
this._knexMethodCalls = []; | ||
this._context = {}; | ||
} | ||
/** | ||
* Calls the given function immediately and passes `this` as an argument. | ||
* | ||
* Handy for chaining conditional stuff: | ||
* | ||
* ```js | ||
* new QueryBuilderBase().call(function (builder) { | ||
* if (someCondition) { | ||
* builder.where('something', someValue); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param {function} func | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.call = function (func) { | ||
func.call(this, this); | ||
return this; | ||
}; | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {QueryBuilderBase} | ||
*/ | ||
/** | ||
* Returns the SQL string. | ||
* | ||
* @returns {String} | ||
*/ | ||
QueryBuilderBase.prototype.toString = function () { | ||
return this.build().toString(); | ||
}; | ||
(0, _createClass3.default)(QueryBuilderBase, [{ | ||
key: 'context', | ||
/** | ||
* Returns the SQL string. | ||
* | ||
* @returns {String} | ||
*/ | ||
QueryBuilderBase.prototype.toSql = function () { | ||
return this.toString(); | ||
}; | ||
/** | ||
* Sets/gets the query context. | ||
*/ | ||
value: function context() { | ||
if (arguments.length === 0) { | ||
return this._context.userContext; | ||
} else { | ||
this._context.userContext = arguments[0]; | ||
return this; | ||
} | ||
} | ||
/** | ||
* Create a clone of this builder. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.clone = function () { | ||
var clone = new this.constructor(this._knex); | ||
this.cloneInto(clone); | ||
return clone; | ||
}; | ||
/** | ||
* Sets/gets the query full internal context. | ||
* | ||
* For internal use only. | ||
* | ||
* @ignore | ||
*/ | ||
/** | ||
* @protected | ||
*/ | ||
QueryBuilderBase.prototype.cloneInto = function (builder) { | ||
builder._knex = this._knex; | ||
builder._knexMethodCalls = this._knexMethodCalls.slice(); | ||
}; | ||
}, { | ||
key: 'internalContext', | ||
value: function internalContext() { | ||
if (arguments.length === 0) { | ||
return this._context; | ||
} else { | ||
this._context = arguments[0]; | ||
return this; | ||
} | ||
} | ||
/** | ||
* Removes query builder method calls. | ||
* | ||
* @param {RegExp=} methodNameRegex | ||
* Optional patter to that must match the method names to remove. | ||
* If not given, all calls are removed. | ||
* | ||
* @ignore | ||
*/ | ||
QueryBuilderBase.prototype.clear = function (methodNameRegex) { | ||
if (methodNameRegex) { | ||
// Reject all query method calls that don't pass the filter. | ||
this._knexMethodCalls = _.reject(this._knexMethodCalls, function (call) { | ||
return methodNameRegex.test(call.method); | ||
}); | ||
} else { | ||
// If no arguments are given, clear all query method calls. | ||
this._knexMethodCalls = []; | ||
} | ||
/** | ||
* Calls the given function immediately and passes `this` as an argument. | ||
* | ||
* Handy for chaining conditional stuff: | ||
* | ||
* ```js | ||
* new QueryBuilderBase().call(function (builder) { | ||
* if (someCondition) { | ||
* builder.where('something', someValue); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param {function} func | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
return this; | ||
}; | ||
}, { | ||
key: 'call', | ||
value: function call(func) { | ||
func.call(this, this); | ||
return this; | ||
} | ||
/** | ||
* Copy query builder method calls from another query builder. | ||
* | ||
* @param {QueryBuilderBase} queryBuilder | ||
* The builder to copy from. | ||
* | ||
* @param {RegExp} methodNameRegex | ||
* Optional regular expression to filter which method calls are copied. | ||
* | ||
* @ignore | ||
*/ | ||
QueryBuilderBase.prototype.copyFrom = function (queryBuilder, methodNameRegex) { | ||
var self = this; | ||
/** | ||
* Returns the SQL string. | ||
* | ||
* @returns {string} | ||
*/ | ||
_.each(queryBuilder._knexMethodCalls, function (call) { | ||
if (!methodNameRegex || methodNameRegex.test(call.method)) { | ||
self._knexMethodCalls.push(call); | ||
}, { | ||
key: 'toString', | ||
value: function toString() { | ||
return this.build().toString(); | ||
} | ||
}); | ||
return this; | ||
}; | ||
/** | ||
* Returns the SQL string. | ||
* | ||
* @returns {string} | ||
*/ | ||
/** | ||
* Returns true if the builder has a call to a method whose name matches the `methodNameRegex`. | ||
* | ||
* @param {RegExp} methodNameRegex | ||
* | ||
* @ignore | ||
*/ | ||
QueryBuilderBase.prototype.has = function (methodNameRegex) { | ||
return _.any(this._knexMethodCalls, function (call) { | ||
return methodNameRegex.test(call.method); | ||
}); | ||
}; | ||
}, { | ||
key: 'toSql', | ||
value: function toSql() { | ||
return this.toString(); | ||
} | ||
/** | ||
* Builds the query into a knex query builder. | ||
* | ||
* @returns {knex.QueryBuilder} | ||
* The built knex query builder. | ||
* | ||
* @protected | ||
*/ | ||
QueryBuilderBase.prototype.build = function () { | ||
return this.buildInto(this._knex.queryBuilder()); | ||
}; | ||
/** | ||
* Create a clone of this builder. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
QueryBuilderBase.prototype.buildInto = function (knexBuilder) { | ||
_.each(this._knexMethodCalls, function (call) { | ||
if (_.isFunction(knexBuilder[call.method])) { | ||
knexBuilder[call.method].apply(knexBuilder, call.args); | ||
}, { | ||
key: 'clone', | ||
value: function clone() { | ||
var clone = new this.constructor(this._knex); | ||
this.cloneInto(clone); | ||
return clone; | ||
} | ||
}); | ||
return knexBuilder; | ||
}; | ||
/** | ||
* @protected | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.insert = knexQueryMethod('insert'); | ||
}, { | ||
key: 'cloneInto', | ||
value: function cloneInto(builder) { | ||
builder._knex = this._knex; | ||
builder._knexMethodCalls = this._knexMethodCalls.slice(); | ||
builder._context = this._context; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.update = knexQueryMethod('update'); | ||
/** | ||
* Removes query builder method calls. | ||
* | ||
* @param {RegExp=} methodNameRegex | ||
* Optional patter to that must match the method names to remove. | ||
* If not given, all calls are removed. | ||
* | ||
* @ignore | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.delete = knexQueryMethod('delete'); | ||
}, { | ||
key: 'clear', | ||
value: function clear(methodNameRegex) { | ||
if (methodNameRegex) { | ||
// Reject all query method calls that don't pass the filter. | ||
this._knexMethodCalls = _lodash2.default.reject(this._knexMethodCalls, function (call) { | ||
return methodNameRegex.test(call.method); | ||
}); | ||
} else { | ||
// If no arguments are given, clear all query method calls. | ||
this._knexMethodCalls = []; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.del = knexQueryMethod('delete'); | ||
return this; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.select = knexQueryMethod('select'); | ||
/** | ||
* Copy query builder method calls from another query builder. | ||
* | ||
* @param {QueryBuilderBase} queryBuilder | ||
* The builder to copy from. | ||
* | ||
* @param {RegExp} methodNameRegex | ||
* Optional regular expression to filter which method calls are copied. | ||
* | ||
* @ignore | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.as = knexQueryMethod('as'); | ||
}, { | ||
key: 'copyFrom', | ||
value: function copyFrom(queryBuilder, methodNameRegex) { | ||
var self = this; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.columns = knexQueryMethod('columns'); | ||
_lodash2.default.forEach(queryBuilder._knexMethodCalls, function (call) { | ||
if (!methodNameRegex || methodNameRegex.test(call.method)) { | ||
self._knexMethodCalls.push(call); | ||
} | ||
}); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.column = knexQueryMethod('column'); | ||
return this; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.from = knexQueryMethod('from'); | ||
/** | ||
* Returns true if the builder has a call to a method whose name matches the `methodNameRegex`. | ||
* | ||
* @param {RegExp} methodNameRegex | ||
* | ||
* @ignore | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.fromJS = knexQueryMethod('fromJS'); | ||
}, { | ||
key: 'has', | ||
value: function has(methodNameRegex) { | ||
return _lodash2.default.any(this._knexMethodCalls, function (call) { | ||
return methodNameRegex.test(call.method); | ||
}); | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.into = knexQueryMethod('into'); | ||
/** | ||
* Builds the query into a knex query builder. | ||
* | ||
* @returns {knex.QueryBuilder} | ||
* The built knex query builder. | ||
* | ||
* @protected | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.withSchema = knexQueryMethod('withSchema'); | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
return this.buildInto(this._knex.queryBuilder()); | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.table = knexQueryMethod('table'); | ||
/** | ||
* @private | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.distinct = knexQueryMethod('distinct'); | ||
}, { | ||
key: 'buildInto', | ||
value: function buildInto(knexBuilder) { | ||
_lodash2.default.forEach(this._knexMethodCalls, function (call) { | ||
if (_lodash2.default.isFunction(knexBuilder[call.method])) { | ||
knexBuilder[call.method].apply(knexBuilder, call.args); | ||
} | ||
}); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.join = knexQueryMethod('join'); | ||
return knexBuilder; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.joinRaw = knexQueryMethod('joinRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.innerJoin = knexQueryMethod('innerJoin'); | ||
}, { | ||
key: 'insert', | ||
value: function insert() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.leftJoin = knexQueryMethod('leftJoin'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.leftOuterJoin = knexQueryMethod('leftOuterJoin'); | ||
}, { | ||
key: 'update', | ||
value: function update() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.rightJoin = knexQueryMethod('rightJoin'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.rightOuterJoin = knexQueryMethod('rightOuterJoin'); | ||
}, { | ||
key: 'delete', | ||
value: function _delete() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.outerJoin = knexQueryMethod('outerJoin'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.fullOuterJoin = knexQueryMethod('fullOuterJoin'); | ||
}, { | ||
key: 'del', | ||
value: function del() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.crossJoin = knexQueryMethod('crossJoin'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.where = knexQueryMethod('where'); | ||
}, { | ||
key: 'select', | ||
value: function select() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.andWhere = knexQueryMethod('andWhere'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhere = knexQueryMethod('orWhere'); | ||
}, { | ||
key: 'as', | ||
value: function as() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNot = knexQueryMethod('whereNot'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNot = knexQueryMethod('orWhereNot'); | ||
}, { | ||
key: 'columns', | ||
value: function columns() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereRaw = knexQueryMethod('whereRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereWrapped = knexQueryMethod('whereWrapped'); | ||
}, { | ||
key: 'column', | ||
value: function column() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.havingWrapped = knexQueryMethod('havingWrapped'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'from', | ||
value: function from() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereRaw = knexQueryMethod('orWhereRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereExists = knexQueryMethod('whereExists'); | ||
}, { | ||
key: 'fromJS', | ||
value: function fromJS() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereExists = knexQueryMethod('orWhereExists'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNotExists = knexQueryMethod('whereNotExists'); | ||
}, { | ||
key: 'into', | ||
value: function into() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNotExists = knexQueryMethod('orWhereNotExists'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereIn = knexQueryMethod('whereIn'); | ||
}, { | ||
key: 'withSchema', | ||
value: function withSchema() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereIn = knexQueryMethod('orWhereIn'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNotIn = knexQueryMethod('whereNotIn'); | ||
}, { | ||
key: 'table', | ||
value: function table() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNotIn = knexQueryMethod('orWhereNotIn'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNull = knexQueryMethod('whereNull'); | ||
}, { | ||
key: 'distinct', | ||
value: function distinct() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNull = knexQueryMethod('orWhereNull'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNotNull = knexQueryMethod('whereNotNull'); | ||
}, { | ||
key: 'join', | ||
value: function join() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNotNull = knexQueryMethod('orWhereNotNull'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereBetween = knexQueryMethod('whereBetween'); | ||
}, { | ||
key: 'joinRaw', | ||
value: function joinRaw() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereNotBetween = knexQueryMethod('whereNotBetween'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereBetween = knexQueryMethod('orWhereBetween'); | ||
}, { | ||
key: 'innerJoin', | ||
value: function innerJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereNotBetween = knexQueryMethod('orWhereNotBetween'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.groupBy = knexQueryMethod('groupBy'); | ||
}, { | ||
key: 'leftJoin', | ||
value: function leftJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.groupByRaw = knexQueryMethod('groupByRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orderBy = knexQueryMethod('orderBy'); | ||
}, { | ||
key: 'leftOuterJoin', | ||
value: function leftOuterJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orderByRaw = knexQueryMethod('orderByRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.union = knexQueryMethod('union'); | ||
}, { | ||
key: 'rightJoin', | ||
value: function rightJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.unionAll = knexQueryMethod('unionAll'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.having = knexQueryMethod('having'); | ||
}, { | ||
key: 'rightOuterJoin', | ||
value: function rightOuterJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.havingRaw = knexQueryMethod('havingRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orHaving = knexQueryMethod('orHaving'); | ||
}, { | ||
key: 'outerJoin', | ||
value: function outerJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.orHavingRaw = knexQueryMethod('orHavingRaw'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.offset = knexQueryMethod('offset'); | ||
}, { | ||
key: 'fullOuterJoin', | ||
value: function fullOuterJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.limit = knexQueryMethod('limit'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.count = knexQueryMethod('count'); | ||
}, { | ||
key: 'crossJoin', | ||
value: function crossJoin() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.countDistinct = knexQueryMethod('countDistinct'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.min = knexQueryMethod('min'); | ||
}, { | ||
key: 'where', | ||
value: function where() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.max = knexQueryMethod('max'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.sum = knexQueryMethod('sum'); | ||
}, { | ||
key: 'andWhere', | ||
value: function andWhere() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.avg = knexQueryMethod('avg'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.avgDistinct = knexQueryMethod('avgDistinct'); | ||
}, { | ||
key: 'orWhere', | ||
value: function orWhere() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.debug = knexQueryMethod('debug'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.returning = knexQueryMethod('returning'); | ||
}, { | ||
key: 'whereNot', | ||
value: function whereNot() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.truncate = knexQueryMethod('truncate'); | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @method | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.connection = knexQueryMethod('connection'); | ||
}, { | ||
key: 'orWhereNot', | ||
value: function orWhereNot() {} | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.whereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
QueryBuilderBase.prototype.whereRef = function (lhs, op, rhs) { | ||
return this._whereRef('and', lhs, op, rhs); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.orWhereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
QueryBuilderBase.prototype.orWhereRef = function (lhs, op, rhs) { | ||
return this._whereRef('or', lhs, op, rhs); | ||
}; | ||
}, { | ||
key: 'whereRaw', | ||
value: function whereRaw() {} | ||
/** | ||
* Json query APIs | ||
*/ | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @typedef {String} FieldExpression | ||
* | ||
* Json field expression to refer to jsonb columns or keys / objects inside columns. | ||
* | ||
* e.g. `Person.jsonColumnName:details.names[1]` would refer to column | ||
* `Person.jsonColumnName` which has `{ details: { names: ['First', 'Second', 'Last'] } }` | ||
* object stored in it. | ||
* | ||
*/ | ||
}, { | ||
key: 'whereWrapped', | ||
value: function whereWrapped() {} | ||
/** | ||
* Where jsonb field reference equals jsonb object or other field reference. | ||
* | ||
* Also supports having field expression in both sides of equality. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .whereJsonEquals('additionalData:myDogs', 'additionalData:dogsAtHome') | ||
* .then(function (people) { | ||
* // oh joy! these people have all their dogs at home! | ||
* }); | ||
* | ||
* Person | ||
* .query() | ||
* .whereJsonEquals('additionalData:myDogs[0]', { name: "peter"}) | ||
* .then(function (people) { | ||
* // these people's first dog name is "peter" and the dog has no other | ||
* // attributes, but its name | ||
* }); | ||
* ``` | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* Reference to column / json field. | ||
* | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* Reference to column / json field or json object. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonEquals = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "=", jsonObjectOrFieldExpression); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonEquals = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "=", jsonObjectOrFieldExpression); | ||
}; | ||
}, { | ||
key: 'havingWrapped', | ||
value: function havingWrapped() {} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonNotEquals = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "!=", jsonObjectOrFieldExpression); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonNotEquals = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "!=", jsonObjectOrFieldExpression); | ||
}; | ||
}, { | ||
key: 'orWhereRaw', | ||
value: function orWhereRaw() {} | ||
/** | ||
* Where left hand json field reference is a superset of the right hand json value or reference. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .whereJsonSupersetOf('additionalData:myDogs', 'additionalData:dogsAtHome') | ||
* .then(function (people) { | ||
* // These people have all or some of their dogs at home. Person might have some | ||
* // additional dogs in their custody since myDogs is supreset of dogsAtHome. | ||
* }); | ||
* | ||
* Person | ||
* .query() | ||
* .whereJsonSupersetOf('additionalData:myDogs[0]', { name: "peter"}) | ||
* .then(function (people) { | ||
* // These people's first dog name is "peter", but the dog might have | ||
* // additional attributes as well. | ||
* }); | ||
* ``` | ||
* | ||
* Object and array are always their own supersets. | ||
* | ||
* For arrays this means that left side matches if it has all the elements | ||
* listed in the right hand side. e.g. | ||
* | ||
* ``` | ||
* [1,2,3] isSuperSetOf [2] => true | ||
* [1,2,3] isSuperSetOf [2,1,3] => true | ||
* [1,2,3] isSuperSetOf [2,null] => false | ||
* [1,2,3] isSuperSetOf [] => true | ||
* ``` | ||
* | ||
* The `not` variants with jsonb operators behave in a way that they won't match rows, which don't have | ||
* the referred json key referred in field expression. e.g. for table | ||
* | ||
* ``` | ||
* id | jsonObject | ||
* ----+-------------------------- | ||
* 1 | {} | ||
* 2 | NULL | ||
* 3 | {"a": 1} | ||
* 4 | {"a": 1, "b": 2} | ||
* 5 | {"a": ['3'], "b": ['3']} | ||
* ``` | ||
* | ||
* query: | ||
* | ||
* ```js | ||
* builder.whereJsonNotEquals("jsonObject:a", "jsonObject:b") | ||
* ``` | ||
* | ||
* Returns only the row `4` which has keys `a` and `b` and `a` != `b`, but it won't return any rows which | ||
* does not have `jsonObject.a` or `jsonObject.b`. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* Reference to column / json field, which is tested for being a superset. | ||
* | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* To which to compare. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonSupersetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonSupersetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression); | ||
}; | ||
}, { | ||
key: 'whereExists', | ||
value: function whereExists() {} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonNotSupersetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression, 'not'); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonNotSupersetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression, 'not'); | ||
}; | ||
}, { | ||
key: 'orWhereExists', | ||
value: function orWhereExists() {} | ||
/** | ||
* Where left hand json field reference is a subset of the right hand json value or reference. | ||
* | ||
* Object and array are always their own subsets. | ||
* | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonSubsetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonSubsetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression); | ||
}; | ||
}, { | ||
key: 'whereNotExists', | ||
value: function whereNotExists() {} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonNotSubsetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression, 'not'); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonNotSubsetOf = function (fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression, 'not'); | ||
}; | ||
}, { | ||
key: 'orWhereNotExists', | ||
value: function orWhereNotExists() {} | ||
/** | ||
* Where json field reference is an array. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonIsArray = function (fieldExpression) { | ||
return this.whereJsonSupersetOf(fieldExpression, []); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonIsArray = function (fieldExpression) { | ||
return this.orWhereJsonSupersetOf(fieldExpression, []); | ||
}; | ||
}, { | ||
key: 'whereIn', | ||
value: function whereIn() {} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonNotArray = function (fieldExpression) { | ||
var knex = this._knex; | ||
// uhh... ugly. own subquery builder could help... now this refers to plain knex subquery builder | ||
return this.where(function () { | ||
// not array | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", [], 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonNotArray = function (fieldExpression) { | ||
var knex = this._knex; | ||
return this.orWhere(function () { | ||
// not array | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", [], 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
}; | ||
}, { | ||
key: 'orWhereIn', | ||
value: function orWhereIn() {} | ||
/** | ||
* Where json field reference is an object. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonIsObject = function (fieldExpression) { | ||
return this.whereJsonSupersetOf(fieldExpression, {}); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonIsObject = function (fieldExpression) { | ||
return this.orWhereJsonSupersetOf(fieldExpression, {}); | ||
}; | ||
}, { | ||
key: 'whereNotIn', | ||
value: function whereNotIn() {} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonNotObject = function (fieldExpression) { | ||
var knex = this._knex; | ||
return this.where(function () { | ||
// not object | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", {}, 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonNotObject = function (fieldExpression) { | ||
var knex = this._knex; | ||
return this.orWhere(function () { | ||
// not object | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", {}, 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
}; | ||
}, { | ||
key: 'orWhereNotIn', | ||
value: function orWhereNotIn() {} | ||
/** | ||
* Where any of given strings is found from json object key(s) or array items. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {String|Array.<String>} keys Strings that are looked from object or array. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonHasAny = function (fieldExpression, keys) { | ||
return whereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?|', keys); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonHasAny} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonHasAny = function (fieldExpression, keys) { | ||
return orWhereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?|', keys); | ||
}; | ||
}, { | ||
key: 'whereNull', | ||
value: function whereNull() {} | ||
/** | ||
* Where all of given strings are found from json object key(s) or array items. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {String|Array.<String>} keys Strings that are looked from object or array. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonHasAll = function (fieldExpression, keys) { | ||
return whereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?&', keys); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonHasAll} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonHasAll = function (fieldExpression, keys) { | ||
return orWhereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?&', keys); | ||
}; | ||
}, { | ||
key: 'orWhereNull', | ||
value: function orWhereNull() {} | ||
/** | ||
* Where referred json field value casted to same type with value fulfill given operand. | ||
* | ||
* Value may be number, string, null, boolean and referred json field is converted | ||
* to TEXT, NUMERIC or BOOLEAN sql type for comparison. | ||
* | ||
* If left hand field does not exist rows appear IS null so if one needs to get only | ||
* rows, which has key and it's value is null one may use e.g. | ||
* `.whereJsonSupersetOf("column", { field: null })` or check is key exist and | ||
* then `.whereJsonField('column:field', 'IS', null)` | ||
* | ||
* For testing against objects or arrays one should see tested with whereJsonEqual, | ||
* whereJsonSupersetOf and whereJsonSubsetOf methods. | ||
* | ||
* @param {FieldExpression} fieldExpression Expression pointing to certain value. | ||
* @param {String} operator SQL comparator usually `<`, `>`, `<>`, `=` or `!=` | ||
* @param {Boolean|Number|String|null} value Value to which field is compared to. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
QueryBuilderBase.prototype.whereJsonField = function (fieldExpression, operator, value) { | ||
var query = whereJsonFieldQuery(this._knex, fieldExpression, operator, value); | ||
return this.whereRaw(query); | ||
}; | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonField} | ||
*/ | ||
QueryBuilderBase.prototype.orWhereJsonField = function (fieldExpression, operator, value) { | ||
var query = whereJsonFieldQuery(this._knex, fieldExpression, operator, value); | ||
return this.orWhereRaw(query); | ||
}; | ||
}, { | ||
key: 'whereNotNull', | ||
value: function whereNotNull() {} | ||
/** | ||
* @private | ||
*/ | ||
QueryBuilderBase.prototype._whereRef = function (bool, lhs, op, rhs) { | ||
if (!rhs) { | ||
rhs = op; | ||
op = '='; | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
var formatter = this._knex.client.formatter(); | ||
op = formatter.operator(op); | ||
}, { | ||
key: 'orWhereNotNull', | ||
value: function orWhereNotNull() {} | ||
if (!_.isString(lhs) || !_.isString(rhs) || !_.isString(op)) { | ||
throw new Error('whereRef: invalid operands or operator'); | ||
} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
var sql = formatter.wrap(lhs) + ' ' + op + ' ' + formatter.wrap(rhs); | ||
if (bool === 'or') { | ||
return this.orWhereRaw(sql); | ||
} else { | ||
return this.whereRaw(sql); | ||
} | ||
}; | ||
}, { | ||
key: 'whereBetween', | ||
value: function whereBetween() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereNotBetween', | ||
value: function whereNotBetween() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orWhereBetween', | ||
value: function orWhereBetween() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orWhereNotBetween', | ||
value: function orWhereNotBetween() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'groupBy', | ||
value: function groupBy() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'groupByRaw', | ||
value: function groupByRaw() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orderBy', | ||
value: function orderBy() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orderByRaw', | ||
value: function orderByRaw() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'union', | ||
value: function union() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'unionAll', | ||
value: function unionAll() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'having', | ||
value: function having() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'havingRaw', | ||
value: function havingRaw() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orHaving', | ||
value: function orHaving() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'orHavingRaw', | ||
value: function orHavingRaw() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'offset', | ||
value: function offset() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'limit', | ||
value: function limit() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'count', | ||
value: function count() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'countDistinct', | ||
value: function countDistinct() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'min', | ||
value: function min() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'max', | ||
value: function max() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'sum', | ||
value: function sum() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'avg', | ||
value: function avg() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'avgDistinct', | ||
value: function avgDistinct() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'debug', | ||
value: function debug() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'returning', | ||
value: function returning() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'truncate', | ||
value: function truncate() {} | ||
/** | ||
* See <a href="http://knexjs.org">knex documentation</a> | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'connection', | ||
value: function connection() {} | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.whereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
}, { | ||
key: 'whereRef', | ||
value: function whereRef(lhs, op, rhs) { | ||
return this._whereRef('and', lhs, op, rhs); | ||
} | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.orWhereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
}, { | ||
key: 'orWhereRef', | ||
value: function orWhereRef(lhs, op, rhs) { | ||
return this._whereRef('or', lhs, op, rhs); | ||
} | ||
/** | ||
* Json query APIs | ||
*/ | ||
/** | ||
* @typedef {string} FieldExpression | ||
* | ||
* Json field expression to refer to jsonb columns or keys / objects inside columns. | ||
* | ||
* e.g. `Person.jsonColumnName:details.names[1]` would refer to column | ||
* `Person.jsonColumnName` which has `{ details: { names: ['First', 'Second', 'Last'] } }` | ||
* object stored in it. | ||
* | ||
*/ | ||
/** | ||
* Where jsonb field reference equals jsonb object or other field reference. | ||
* | ||
* Also supports having field expression in both sides of equality. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .whereJsonEquals('additionalData:myDogs', 'additionalData:dogsAtHome') | ||
* .then(function (people) { | ||
* // oh joy! these people have all their dogs at home! | ||
* }); | ||
* | ||
* Person | ||
* .query() | ||
* .whereJsonEquals('additionalData:myDogs[0]', { name: "peter"}) | ||
* .then(function (people) { | ||
* // these people's first dog name is "peter" and the dog has no other | ||
* // attributes, but its name | ||
* }); | ||
* ``` | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* Reference to column / json field. | ||
* | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* Reference to column / json field or json object. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonEquals', | ||
value: function whereJsonEquals(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "=", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonEquals', | ||
value: function orWhereJsonEquals(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "=", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
}, { | ||
key: 'whereJsonNotEquals', | ||
value: function whereJsonNotEquals(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "!=", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonEquals} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonNotEquals', | ||
value: function orWhereJsonNotEquals(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "!=", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* Where left hand json field reference is a superset of the right hand json value or reference. | ||
* | ||
* ```js | ||
* Person | ||
* .query() | ||
* .whereJsonSupersetOf('additionalData:myDogs', 'additionalData:dogsAtHome') | ||
* .then(function (people) { | ||
* // These people have all or some of their dogs at home. Person might have some | ||
* // additional dogs in their custody since myDogs is supreset of dogsAtHome. | ||
* }); | ||
* | ||
* Person | ||
* .query() | ||
* .whereJsonSupersetOf('additionalData:myDogs[0]', { name: "peter"}) | ||
* .then(function (people) { | ||
* // These people's first dog name is "peter", but the dog might have | ||
* // additional attributes as well. | ||
* }); | ||
* ``` | ||
* | ||
* Object and array are always their own supersets. | ||
* | ||
* For arrays this means that left side matches if it has all the elements | ||
* listed in the right hand side. e.g. | ||
* | ||
* ``` | ||
* [1,2,3] isSuperSetOf [2] => true | ||
* [1,2,3] isSuperSetOf [2,1,3] => true | ||
* [1,2,3] isSuperSetOf [2,null] => false | ||
* [1,2,3] isSuperSetOf [] => true | ||
* ``` | ||
* | ||
* The `not` variants with jsonb operators behave in a way that they won't match rows, which don't have | ||
* the referred json key referred in field expression. e.g. for table | ||
* | ||
* ``` | ||
* id | jsonObject | ||
* ----+-------------------------- | ||
* 1 | {} | ||
* 2 | NULL | ||
* 3 | {"a": 1} | ||
* 4 | {"a": 1, "b": 2} | ||
* 5 | {"a": ['3'], "b": ['3']} | ||
* ``` | ||
* | ||
* query: | ||
* | ||
* ```js | ||
* builder.whereJsonNotEquals("jsonObject:a", "jsonObject:b") | ||
* ``` | ||
* | ||
* Returns only the row `4` which has keys `a` and `b` and `a` != `b`, but it won't return any rows which | ||
* does not have `jsonObject.a` or `jsonObject.b`. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* Reference to column / json field, which is tested for being a superset. | ||
* | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* To which to compare. | ||
* | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonSupersetOf', | ||
value: function whereJsonSupersetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonSupersetOf', | ||
value: function orWhereJsonSupersetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
}, { | ||
key: 'whereJsonNotSupersetOf', | ||
value: function whereJsonNotSupersetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression, 'not'); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonNotSupersetOf', | ||
value: function orWhereJsonNotSupersetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", jsonObjectOrFieldExpression, 'not'); | ||
} | ||
/** | ||
* Where left hand json field reference is a subset of the right hand json value or reference. | ||
* | ||
* Object and array are always their own subsets. | ||
* | ||
* @see {@link QueryBuilderBase#whereJsonSupersetOf} | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonSubsetOf', | ||
value: function whereJsonSubsetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonSubsetOf', | ||
value: function orWhereJsonSubsetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
}, { | ||
key: 'whereJsonNotSubsetOf', | ||
value: function whereJsonNotSubsetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression, 'not'); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonSubsetOf} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonNotSubsetOf', | ||
value: function orWhereJsonNotSubsetOf(fieldExpression, jsonObjectOrFieldExpression) { | ||
return orWhereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "<@", jsonObjectOrFieldExpression, 'not'); | ||
} | ||
/** | ||
* Where json field reference is an array. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonIsArray', | ||
value: function whereJsonIsArray(fieldExpression) { | ||
return this.whereJsonSupersetOf(fieldExpression, []); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonIsArray', | ||
value: function orWhereJsonIsArray(fieldExpression) { | ||
return this.orWhereJsonSupersetOf(fieldExpression, []); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
}, { | ||
key: 'whereJsonNotArray', | ||
value: function whereJsonNotArray(fieldExpression) { | ||
var knex = this._knex; | ||
// uhh... ugly. own subquery builder could help... now this refers to plain knex subquery builder | ||
return this.where(function () { | ||
// not array | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", [], 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsArray} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
}, { | ||
key: 'orWhereJsonNotArray', | ||
value: function orWhereJsonNotArray(fieldExpression) { | ||
var knex = this._knex; | ||
return this.orWhere(function () { | ||
// not array | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", [], 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
} | ||
/** | ||
* Where json field reference is an object. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonIsObject', | ||
value: function whereJsonIsObject(fieldExpression) { | ||
return this.whereJsonSupersetOf(fieldExpression, {}); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonIsObject', | ||
value: function orWhereJsonIsObject(fieldExpression) { | ||
return this.orWhereJsonSupersetOf(fieldExpression, {}); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
}, { | ||
key: 'whereJsonNotObject', | ||
value: function whereJsonNotObject(fieldExpression) { | ||
var knex = this._knex; | ||
return this.where(function () { | ||
// not object | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", {}, 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonIsObject} | ||
* @note Also returns rows where `fieldExpression` does not exist. | ||
*/ | ||
}, { | ||
key: 'orWhereJsonNotObject', | ||
value: function orWhereJsonNotObject(fieldExpression) { | ||
var knex = this._knex; | ||
return this.orWhere(function () { | ||
// not object | ||
var builder = whereJsonbRefOnLeftJsonbValOrRefOnRight(this, fieldExpression, "@>", {}, 'not'); | ||
var ifRefNotExistQuery = whereJsonFieldQuery(knex, fieldExpression, "IS", null); | ||
// or not exist | ||
builder.orWhereRaw(ifRefNotExistQuery); | ||
}); | ||
} | ||
/** | ||
* Where any of given strings is found from json object key(s) or array items. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {string|Array.<string>} keys Strings that are looked from object or array. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonHasAny', | ||
value: function whereJsonHasAny(fieldExpression, keys) { | ||
return whereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?|', keys); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonHasAny} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonHasAny', | ||
value: function orWhereJsonHasAny(fieldExpression, keys) { | ||
return orWhereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?|', keys); | ||
} | ||
/** | ||
* Where all of given strings are found from json object key(s) or array items. | ||
* | ||
* @param {FieldExpression} fieldExpression | ||
* @param {string|Array.<string>} keys Strings that are looked from object or array. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonHasAll', | ||
value: function whereJsonHasAll(fieldExpression, keys) { | ||
return whereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?&', keys); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonHasAll} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonHasAll', | ||
value: function orWhereJsonHasAll(fieldExpression, keys) { | ||
return orWhereJsonFieldRightStringArrayOnLeft(this, fieldExpression, '?&', keys); | ||
} | ||
/** | ||
* Where referred json field value casted to same type with value fulfill given operand. | ||
* | ||
* Value may be number, string, null, boolean and referred json field is converted | ||
* to TEXT, NUMERIC or BOOLEAN sql type for comparison. | ||
* | ||
* If left hand field does not exist rows appear IS null so if one needs to get only | ||
* rows, which has key and it's value is null one may use e.g. | ||
* `.whereJsonSupersetOf("column", { field: null })` or check is key exist and | ||
* then `.whereJsonField('column:field', 'IS', null)` | ||
* | ||
* For testing against objects or arrays one should see tested with whereJsonEqual, | ||
* whereJsonSupersetOf and whereJsonSubsetOf methods. | ||
* | ||
* @param {FieldExpression} fieldExpression Expression pointing to certain value. | ||
* @param {string} operator SQL comparator usually `<`, `>`, `<>`, `=` or `!=` | ||
* @param {boolean|Number|string|null} value Value to which field is compared to. | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
}, { | ||
key: 'whereJsonField', | ||
value: function whereJsonField(fieldExpression, operator, value) { | ||
var query = whereJsonFieldQuery(this._knex, fieldExpression, operator, value); | ||
return this.whereRaw(query); | ||
} | ||
/** | ||
* @see {@link QueryBuilderBase#whereJsonField} | ||
*/ | ||
}, { | ||
key: 'orWhereJsonField', | ||
value: function orWhereJsonField(fieldExpression, operator, value) { | ||
var query = whereJsonFieldQuery(this._knex, fieldExpression, operator, value); | ||
return this.orWhereRaw(query); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '_whereRef', | ||
value: function _whereRef(bool, lhs, op, rhs) { | ||
if (!rhs) { | ||
rhs = op; | ||
op = '='; | ||
} | ||
var formatter = this._knex.client.formatter(); | ||
op = formatter.operator(op); | ||
if (!_lodash2.default.isString(lhs) || !_lodash2.default.isString(rhs) || !_lodash2.default.isString(op)) { | ||
throw new Error('whereRef: invalid operands or operator'); | ||
} | ||
var sql = formatter.wrap(lhs) + ' ' + op + ' ' + formatter.wrap(rhs); | ||
if (bool === 'or') { | ||
return this.orWhereRaw(sql); | ||
} else { | ||
return this.whereRaw(sql); | ||
} | ||
} | ||
}], [{ | ||
key: 'extend', | ||
value: function extend(subclassConstructor) { | ||
_utils2.default.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
} | ||
}]); | ||
return QueryBuilderBase; | ||
})(), (_applyDecoratedDescriptor(_class.prototype, 'insert', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'insert'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'delete', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'delete'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'del', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'del'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'select', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'select'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'as', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'as'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'columns', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'columns'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'column', [_dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'column'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'from', [_dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'from'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'fromJS', [_dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'fromJS'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'into', [_dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'into'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'withSchema', [_dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'withSchema'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'table', [_dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'table'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'distinct', [_dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'distinct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'join', [_dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'join'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'joinRaw', [_dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'joinRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'innerJoin', [_dec17], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'innerJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'leftJoin', [_dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'leftJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'leftOuterJoin', [_dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'leftOuterJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'rightJoin', [_dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'rightJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'rightOuterJoin', [_dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'rightOuterJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'outerJoin', [_dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'outerJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'fullOuterJoin', [_dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'fullOuterJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'crossJoin', [_dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'crossJoin'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'where', [_dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'where'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'andWhere', [_dec26], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'andWhere'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhere', [_dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhere'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNot', [_dec28], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNot'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNot', [_dec29], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNot'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereRaw', [_dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereWrapped', [_dec31], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereWrapped'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'havingWrapped', [_dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'havingWrapped'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereRaw', [_dec33], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereExists', [_dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereExists'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereExists', [_dec35], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereExists'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNotExists', [_dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNotExists'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNotExists', [_dec37], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNotExists'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereIn', [_dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereIn'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereIn', [_dec39], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereIn'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNotIn', [_dec40], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNotIn'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNotIn', [_dec41], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNotIn'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNull', [_dec42], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNull'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNull', [_dec43], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNull'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNotNull', [_dec44], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNotNull'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNotNull', [_dec45], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNotNull'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereBetween', [_dec46], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereBetween'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'whereNotBetween', [_dec47], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'whereNotBetween'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereBetween', [_dec48], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereBetween'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orWhereNotBetween', [_dec49], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orWhereNotBetween'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'groupBy', [_dec50], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'groupBy'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'groupByRaw', [_dec51], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'groupByRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orderBy', [_dec52], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orderBy'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orderByRaw', [_dec53], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orderByRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'union', [_dec54], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'union'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unionAll', [_dec55], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unionAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'having', [_dec56], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'having'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'havingRaw', [_dec57], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'havingRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orHaving', [_dec58], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orHaving'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'orHavingRaw', [_dec59], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'orHavingRaw'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'offset', [_dec60], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'offset'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'limit', [_dec61], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'limit'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'count', [_dec62], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'count'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'countDistinct', [_dec63], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'countDistinct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'min', [_dec64], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'min'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'max', [_dec65], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'max'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'sum', [_dec66], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'sum'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'avg', [_dec67], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'avg'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'avgDistinct', [_dec68], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'avgDistinct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'debug', [_dec69], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'debug'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'returning', [_dec70], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'returning'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'truncate', [_dec71], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'truncate'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'connection', [_dec72], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'connection'), _class.prototype)), _class)); | ||
/** | ||
* @returns {Function} | ||
* @private | ||
*/ | ||
function knexQueryMethod(methodName) { | ||
/** | ||
* @returns {QueryBuilderBase} | ||
*/ | ||
return function () { | ||
var args = new Array(arguments.length); | ||
var knex = this._knex; | ||
for (var i = 0, l = arguments.length; i < l; ++i) { | ||
if (arguments[i] === undefined) { | ||
// None of the query builder methods should accept undefined. Do nothing if | ||
// one of the arguments is undefined. This enables us to do things like | ||
// `.where('name', req.query.name)` without checking if req.query has the | ||
// property `name`. | ||
return this; | ||
} else if (arguments[i] instanceof QueryBuilderBase) { | ||
// Convert QueryBuilderBase instances into knex query builders. | ||
args[i] = arguments[i].build(); | ||
} else if (_.isFunction(arguments[i])) { | ||
// If an argument is a function, knex calls it with a query builder as | ||
// `this` context. We call the function with a QueryBuilderBase as | ||
// `this` context instead. | ||
args[i] = wrapFunctionArg(knex, arguments[i]); | ||
} else { | ||
args[i] = arguments[i]; | ||
exports.default = QueryBuilderBase; | ||
function knexQueryMethod(overrideMethodName) { | ||
return function (target, methodName, descriptor) { | ||
descriptor.value = function () { | ||
var args = new Array(arguments.length); | ||
var context = this.internalContext(); | ||
for (var i = 0, l = arguments.length; i < l; ++i) { | ||
if (arguments[i] === undefined) { | ||
// None of the query builder methods should accept undefined. Do nothing if | ||
// one of the arguments is undefined. This enables us to do things like | ||
// `.where('name', req.query.name)` without checking if req.query has the | ||
// property `name`. | ||
return this; | ||
} else if (arguments[i] instanceof QueryBuilderBase) { | ||
// Convert QueryBuilderBase instances into knex query builders. | ||
args[i] = arguments[i].internalContext(context).build(); | ||
} else if (_lodash2.default.isFunction(arguments[i])) { | ||
// If an argument is a function, knex calls it with a query builder as | ||
// `this` context. We call the function with a QueryBuilderBase as | ||
// `this` context instead. | ||
args[i] = wrapFunctionArg(arguments[i], this); | ||
} else { | ||
args[i] = arguments[i]; | ||
} | ||
} | ||
} | ||
this._knexMethodCalls.push({ | ||
method: methodName, | ||
args: args | ||
}); | ||
this._knexMethodCalls.push({ | ||
method: overrideMethodName || methodName, | ||
args: args | ||
}); | ||
return this; | ||
return this; | ||
}; | ||
}; | ||
@@ -1127,9 +1496,8 @@ } | ||
*/ | ||
function wrapFunctionArg(knex, func) { | ||
function wrapFunctionArg(func, query) { | ||
return function () { | ||
var builder = new QueryBuilderBase(knex); | ||
var knexBuilder = this; | ||
var context = query.internalContext(); | ||
var builder = new QueryBuilderBase(query._knex).internalContext(context); | ||
func.call(builder, builder); | ||
builder.buildInto(knexBuilder); | ||
builder.buildInto(this); | ||
}; | ||
@@ -1139,8 +1507,17 @@ } | ||
/** | ||
* Field expression how to refer certain nested field inside jsonb column. | ||
* Parses a objection.js json field expression into a postgres jsonb field reference. | ||
* | ||
* Table.jsonColumn:obj[key.with.dots][0].key.0.me[0] refers to | ||
* obj = { "key.with.dots" : [{"key" : { "0": { me : [ "I was referred" ] }}}] | ||
* For example, assume we have this object stored in the column `jsonColumn` of a table `Table`: | ||
* | ||
* Since PostgreSql #>{field,0,field2,...} operator does not make difference if | ||
* ``` | ||
* {obj: { "key.with.dots": [{"key": { "0": { me : [ "I was referred" ] } } } ] } } | ||
* ``` | ||
* | ||
* We can refer to the value "I was referred" using the following field expression: | ||
* | ||
* ``` | ||
* Table.jsonColumn:obj[key.with.dots][0].key.0.me[0] | ||
* ``` | ||
* | ||
* Since Postgresql #>{field,0,field2,...} operator does not make difference if | ||
* reference is string or a number, one can actually use also jsonArray.0 notation | ||
@@ -1150,14 +1527,13 @@ * to refer index of an array. Like wise one can use object[123] notation to refer | ||
* | ||
* @param {String} expression | ||
* @param {Boolean} extractAsText Return text instead of jsonb object (useful for type casting). | ||
* @private | ||
* @returns {Array} Array of referred path, where first item is table/column. | ||
* @param {string} expression | ||
* @param {boolean} extractAsText Return text instead of jsonb object (useful for type casting). | ||
* @returns {string} postgres json reference. | ||
*/ | ||
function parseFieldExpression(expression, extractAsText) { | ||
var parsed = jsonFieldExpressionParser.parse(expression); | ||
var jsonRefs = _(parsed.access).pluck('ref').value().join(","); | ||
var parsed = _jsonFieldExpressionParser2.default.parse(expression); | ||
var jsonRefs = (0, _lodash2.default)(parsed.access).pluck('ref').value().join(","); | ||
var extractor = extractAsText ? '#>>' : '#>'; | ||
// TODO: knex.raw('??', parsed.columnName) could work with latest knex | ||
var middleQuotedColumnName = parsed.columnName.split('.').join('"."'); | ||
return ['"', middleQuotedColumnName, '"', extractor, "'{", jsonRefs, "}'"].join(""); | ||
return '"' + middleQuotedColumnName + '"' + extractor + '\'{' + jsonRefs + '}\''; | ||
} | ||
@@ -1179,8 +1555,8 @@ | ||
* | ||
* @private | ||
* @param {QueryBuilderBase} builder | ||
* @param {FieldExpression} fieldExpression Reference to column / jsonField. | ||
* @param {String} operator operator to apply. | ||
* @param {string} operator operator to apply. | ||
* @param {Object|Array|FieldExpression} jsonObjectOrFieldExpression Reference to column / jsonField or json object. | ||
* @param {String=} queryPrefix String prepended to query e.g. 'not'. Space after string added implicitly. | ||
* @private | ||
* @param {string=} queryPrefix string prepended to query e.g. 'not'. Space after string added implicitly. | ||
* @returns {QueryBuilderBase} | ||
@@ -1210,3 +1586,3 @@ */ | ||
if (_.isString(jsonObjectOrFieldExpression)) { | ||
if (_lodash2.default.isString(jsonObjectOrFieldExpression)) { | ||
var rightHandReference = parseFieldExpression(jsonObjectOrFieldExpression); | ||
@@ -1218,3 +1594,3 @@ var refRefQuery = ["(", fieldReference, ")::jsonb", operator, "(", rightHandReference, ")::jsonb"]; | ||
return [refRefQuery.join(" ")]; | ||
} else if (_.isObject(jsonObjectOrFieldExpression)) { | ||
} else if (_lodash2.default.isObject(jsonObjectOrFieldExpression)) { | ||
var refValQuery = ["(", fieldReference, ")::jsonb", operator, "?::jsonb"]; | ||
@@ -1224,3 +1600,3 @@ if (queryPrefix) { | ||
} | ||
return [refValQuery.join(" "), JSON.stringify(jsonObjectOrFieldExpression)]; | ||
return [refValQuery.join(" "), (0, _stringify2.default)(jsonObjectOrFieldExpression)]; | ||
} | ||
@@ -1242,6 +1618,7 @@ | ||
* | ||
* @private | ||
* @param {QueryBuilderBase} builder | ||
* @param {FieldExpression} fieldExpression | ||
* @param {String} operator | ||
* @param {Array.<String>} keys | ||
* @param {string} operator | ||
* @param {Array.<string>} keys | ||
* @returns {QueryBuilderBase} | ||
@@ -1268,6 +1645,6 @@ */ | ||
var fieldReference = parseFieldExpression(fieldExpression); | ||
keys = _.isArray(keys) ? keys : [keys]; | ||
keys = _lodash2.default.isArray(keys) ? keys : [keys]; | ||
var questionMarksArray = _.map(keys, function (key) { | ||
if (!_.isString(key)) { | ||
var questionMarksArray = _lodash2.default.map(keys, function (key) { | ||
if (!_lodash2.default.isString(key)) { | ||
throw new Error("All keys to find must be strings."); | ||
@@ -1281,3 +1658,3 @@ } | ||
return [fieldReference, " ", operator.replace('?', '\\?'), " ", rightHandExpression].join(""); | ||
return fieldReference + ' ' + operator.replace('?', '\\?') + ' ' + rightHandExpression; | ||
} | ||
@@ -1294,11 +1671,11 @@ | ||
// json type comparison takes json type in string format | ||
var cast; | ||
var cast = undefined; | ||
var escapedValue = knex.raw(" ?", [value]); | ||
if (_.isNumber(value)) { | ||
if (_lodash2.default.isNumber(value)) { | ||
cast = "::NUMERIC"; | ||
} else if (_.isBoolean(value)) { | ||
} else if (_lodash2.default.isBoolean(value)) { | ||
cast = "::BOOLEAN"; | ||
} else if (_.isString(value)) { | ||
} else if (_lodash2.default.isString(value)) { | ||
cast = "::TEXT"; | ||
} else if (_.isNull(value)) { | ||
} else if (_lodash2.default.isNull(value)) { | ||
cast = "::TEXT"; | ||
@@ -1309,3 +1686,4 @@ escapedValue = 'NULL'; | ||
} | ||
return ["(", fieldReference, ")", cast, " ", normalizedOperator," ", escapedValue].join("") | ||
return '(' + fieldReference + ')' + cast + ' ' + normalizedOperator + ' ' + escapedValue; | ||
} | ||
@@ -1316,4 +1694,4 @@ | ||
* @param knex | ||
* @param {String} operator | ||
* @returns {String} | ||
* @param {string} operator | ||
* @returns {string} | ||
*/ | ||
@@ -1330,4 +1708,2 @@ function normalizeOperator(knex, operator) { | ||
} | ||
} | ||
module.exports = QueryBuilderBase; | ||
} |
'use strict'; | ||
var _ = require('lodash') | ||
, parser = require('./parsers/relationExpressionParser') | ||
, ValidationError = require('./../ValidationError'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _keys = require('babel-runtime/core-js/object/keys'); | ||
var _keys2 = _interopRequireDefault(_keys); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _relationExpressionParser = require('./parsers/relationExpressionParser'); | ||
var _relationExpressionParser2 = _interopRequireDefault(_relationExpressionParser); | ||
var _ValidationError = require('./../ValidationError'); | ||
var _ValidationError2 = _interopRequireDefault(_ValidationError); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
@@ -64,128 +91,150 @@ * Relation expression is a simple DSL for expressing relation trees. | ||
* the argument `arg3`. | ||
* | ||
* @constructor | ||
*/ | ||
function RelationExpression(node) { | ||
node = node || {}; | ||
this.name = node.name || null; | ||
this.args = node.args || []; | ||
this.numChildren = node.numChildren || 0; | ||
this.children = node.children || {}; | ||
} | ||
/** | ||
* Parses an expression string into a {@link RelationExpression} object. | ||
* | ||
* @param {String|RelationExpression} expr | ||
* @returns {RelationExpression} | ||
*/ | ||
RelationExpression.parse = function (expr) { | ||
if (expr instanceof RelationExpression) { | ||
return expr; | ||
} else if (!_.isString(expr) || _.isEmpty(expr.trim())) { | ||
return new RelationExpression(); | ||
} else { | ||
try { | ||
return new RelationExpression(parser.parse(expr)); | ||
} catch (err) { | ||
throw new ValidationError({ | ||
message: 'Invalid relation expression "' + expr + '"', | ||
cause: err.message | ||
}); | ||
} | ||
var RelationExpression = (function () { | ||
function RelationExpression(node) { | ||
(0, _classCallCheck3.default)(this, RelationExpression); | ||
node = node || {}; | ||
this.name = node.name || null; | ||
this.args = node.args || []; | ||
this.numChildren = node.numChildren || 0; | ||
this.children = node.children || {}; | ||
} | ||
}; | ||
/** | ||
* Tests if another expression is a sub expression of this one. | ||
* | ||
* Expression B is a sub expression of expression A if: | ||
* | ||
* - A and B have the same root | ||
* - And each path from root to a leaf in B can be found in A | ||
* | ||
* For example sub expressions of `children.[movies.actors, pets]` are: | ||
* | ||
* - `children` | ||
* - `children.movies` | ||
* - `children.pets` | ||
* - `children.movies.actors` | ||
* - `children.[movies, pets]` | ||
* - `children.[movies.actors, pets]` | ||
* | ||
* @param {String|RelationExpression} expr | ||
* @returns {boolean} | ||
*/ | ||
RelationExpression.prototype.isSubExpression = function (expr) { | ||
var self = this; | ||
/** | ||
* Parses an expression string into a {@link RelationExpression} object. | ||
* | ||
* @param {string|RelationExpression} expr | ||
* @returns {RelationExpression} | ||
*/ | ||
expr = RelationExpression.parse(expr); | ||
(0, _createClass3.default)(RelationExpression, [{ | ||
key: 'isSubExpression', | ||
if (this.isAllRecursive()) { | ||
return true; | ||
} | ||
/** | ||
* Tests if another expression is a sub expression of this one. | ||
* | ||
* Expression B is a sub expression of expression A if: | ||
* | ||
* - A and B have the same root | ||
* - And each path from root to a leaf in B can be found in A | ||
* | ||
* For example sub expressions of `children.[movies.actors, pets]` are: | ||
* | ||
* - `children` | ||
* - `children.movies` | ||
* - `children.pets` | ||
* - `children.movies.actors` | ||
* - `children.[movies, pets]` | ||
* - `children.[movies.actors, pets]` | ||
* | ||
* @param {string|RelationExpression} expr | ||
* @returns {boolean} | ||
*/ | ||
value: function isSubExpression(expr) { | ||
var _this = this; | ||
if (expr.isAllRecursive()) { | ||
return this.isAllRecursive(); | ||
} | ||
expr = RelationExpression.parse(expr); | ||
if (this.name !== expr.name) { | ||
return false; | ||
} | ||
if (this.isAllRecursive()) { | ||
return true; | ||
} | ||
if (expr.isRecursive()) { | ||
return this.isAllRecursive() || this.isRecursive(); | ||
} | ||
if (expr.isAllRecursive()) { | ||
return this.isAllRecursive(); | ||
} | ||
return _.all(expr.children, function (child, childName) { | ||
var ownSubExpression = self.childExpression(childName); | ||
var subExpression = expr.childExpression(childName); | ||
if (this.name !== expr.name) { | ||
return false; | ||
} | ||
return ownSubExpression && ownSubExpression.isSubExpression(subExpression); | ||
}); | ||
}; | ||
if (expr.isRecursive()) { | ||
return this.isAllRecursive() || this.isRecursive(); | ||
} | ||
/** | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
RelationExpression.prototype.isRecursive = function () { | ||
return !!_.find(this.children, {name: '^'}); | ||
}; | ||
return _lodash2.default.all(expr.children, function (child, childName) { | ||
var ownSubExpression = _this.childExpression(childName); | ||
var subExpression = expr.childExpression(childName); | ||
/** | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
RelationExpression.prototype.isAllRecursive = function () { | ||
return this.numChildren === 1 && this.children[Object.keys(this.children)[0]].name === '*'; | ||
}; | ||
return ownSubExpression && ownSubExpression.isSubExpression(subExpression); | ||
}); | ||
} | ||
/** | ||
* @ignore | ||
* @returns {RelationExpression} | ||
*/ | ||
RelationExpression.prototype.childExpression = function (childName) { | ||
if (this.isAllRecursive() || (this.isRecursive() && childName === this.name)) { | ||
return this; | ||
} | ||
/** | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
if (this.children[childName]) { | ||
return new RelationExpression(this.children[childName]); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
}, { | ||
key: 'isRecursive', | ||
value: function isRecursive() { | ||
return !!_lodash2.default.find(this.children, { name: '^' }); | ||
} | ||
/** | ||
* @ignore | ||
*/ | ||
RelationExpression.prototype.forEachChild = function (cb) { | ||
_.each(this.children, function (child, childName) { | ||
if (childName !== '*' && childName !== '^') { | ||
cb(child, childName); | ||
/** | ||
* @ignore | ||
* @returns {boolean} | ||
*/ | ||
}, { | ||
key: 'isAllRecursive', | ||
value: function isAllRecursive() { | ||
return this.numChildren === 1 && this.children[(0, _keys2.default)(this.children)[0]].name === '*'; | ||
} | ||
}) | ||
}; | ||
module.exports = RelationExpression; | ||
/** | ||
* @ignore | ||
* @returns {RelationExpression} | ||
*/ | ||
}, { | ||
key: 'childExpression', | ||
value: function childExpression(childName) { | ||
if (this.isAllRecursive() || this.isRecursive() && childName === this.name) { | ||
return this; | ||
} | ||
if (this.children[childName]) { | ||
return new RelationExpression(this.children[childName]); | ||
} else { | ||
return null; | ||
} | ||
} | ||
/** | ||
* @ignore | ||
*/ | ||
}, { | ||
key: 'forEachChild', | ||
value: function forEachChild(cb) { | ||
_lodash2.default.each(this.children, function (child, childName) { | ||
if (childName !== '*' && childName !== '^') { | ||
cb(child, childName); | ||
} | ||
}); | ||
} | ||
}], [{ | ||
key: 'parse', | ||
value: function parse(expr) { | ||
if (expr instanceof RelationExpression) { | ||
return expr; | ||
} else if (!_lodash2.default.isString(expr) || _lodash2.default.isEmpty(expr.trim())) { | ||
return new RelationExpression(); | ||
} else { | ||
try { | ||
return new RelationExpression(_relationExpressionParser2.default.parse(expr)); | ||
} catch (err) { | ||
throw new _ValidationError2.default({ | ||
message: 'Invalid relation expression "' + expr + '"', | ||
cause: err.message | ||
}); | ||
} | ||
} | ||
} | ||
}]); | ||
return RelationExpression; | ||
})(); | ||
exports.default = RelationExpression; |
'use strict'; | ||
var _ = require('lodash') | ||
, Relation = require('./Relation') | ||
, ownerJoinColumnAlias = 'objectiontmpjoin'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | ||
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | ||
var _get2 = require('babel-runtime/helpers/get'); | ||
var _get3 = _interopRequireDefault(_get2); | ||
var _inherits2 = require('babel-runtime/helpers/inherits'); | ||
var _inherits3 = _interopRequireDefault(_inherits2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _Relation2 = require('./Relation'); | ||
var _Relation3 = _interopRequireDefault(_Relation2); | ||
var _inheritModel = require('../model/inheritModel'); | ||
var _inheritModel2 = _interopRequireDefault(_inheritModel); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var ownerJoinColumnAlias = 'objectiontmpjoin';; | ||
/** | ||
* @constructor | ||
* @ignore | ||
* @extends Relation | ||
*/ | ||
function ManyToManyRelation() { | ||
Relation.apply(this, arguments); | ||
} | ||
Relation.extend(ManyToManyRelation); | ||
var ManyToManyRelation = (function (_Relation) { | ||
(0, _inherits3.default)(ManyToManyRelation, _Relation); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.setMapping = function (mapping) { | ||
var retVal = Relation.prototype.setMapping.call(this, mapping); | ||
function ManyToManyRelation() { | ||
var _Object$getPrototypeO; | ||
if (!this.joinTable || !this.joinTableOwnerCol || !this.joinTableRelatedCol) { | ||
throw new Error(this.ownerModelClass.name + '.relationMappings.' + this.name + '.join must have the `through` that describes the join table.'); | ||
(0, _classCallCheck3.default)(this, ManyToManyRelation); | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
/** | ||
* The join table. | ||
* | ||
* @type {string} | ||
*/ | ||
var _this = (0, _possibleConstructorReturn3.default)(this, (_Object$getPrototypeO = (0, _getPrototypeOf2.default)(ManyToManyRelation)).call.apply(_Object$getPrototypeO, [this].concat(args))); | ||
_this.joinTable = null; | ||
/** | ||
* The relation column in the join table that points to the owner table. | ||
* | ||
* @type {string} | ||
*/ | ||
_this.joinTableOwnerCol = null; | ||
/** | ||
* The relation property in the join model that points to the owner table. | ||
* | ||
* @type {string} | ||
*/ | ||
_this.joinTableOwnerProp = null; | ||
/** | ||
* The relation column in the join table that points to the related table. | ||
* | ||
* @type {string} | ||
*/ | ||
_this.joinTableRelatedCol = null; | ||
/** | ||
* The relation property in the join model that points to the related table. | ||
* | ||
* @type {string} | ||
*/ | ||
_this.joinTableRelatedProp = null; | ||
/** | ||
* The join table model class. | ||
* | ||
* This can be optionally given using the `join.through.modelClass` property, | ||
* otherwise an anonymous model class is created in `setMapping` method. | ||
* | ||
* @type {Class<Model>} | ||
*/ | ||
_this.joinTableModelClass = null; | ||
return _this; | ||
} | ||
return retVal; | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
builder.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()); | ||
(0, _createClass3.default)(ManyToManyRelation, [{ | ||
key: 'setMapping', | ||
value: function setMapping(mapping) { | ||
// Avoid require loop and import here. | ||
var Model = require(__dirname + '/../model/Model').default; | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullJoinTableOwnerCol(), ownerCol); | ||
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.'); | ||
} | ||
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\'}'); | ||
} | ||
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); | ||
if (!joinTableFrom.table || !joinTableFrom.name) { | ||
throw new Error(errorPrefix + '.join.through.from must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
} | ||
if (!joinTableTo.table || !joinTableTo.name) { | ||
throw new Error(errorPrefix + '.join.through.to must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
} | ||
if (joinTableFrom.table !== joinTableTo.table) { | ||
throw new Error(errorPrefix + '.join.through `from` and `to` must point to the same join table.'); | ||
} | ||
this.joinTable = joinTableFrom.table; | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
this.joinTableOwnerCol = joinTableFrom.name; | ||
this.joinTableRelatedCol = joinTableTo.name; | ||
} else { | ||
this.joinTableRelatedCol = joinTableFrom.name; | ||
this.joinTableOwnerCol = joinTableTo.name; | ||
} | ||
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'); | ||
} | ||
this.joinTableModelClass = mapping.join.through.modelClass; | ||
} else { | ||
this.joinTableModelClass = (0, _inheritModel2.default)(Model); | ||
this.joinTableModelClass.tableName = this.joinTable; | ||
// We cannot know if the join table has a primary key. Therefore we set some | ||
// known column as the idColumn so that inserts will work. | ||
this.joinTableModelClass.idColumn = this.joinTableRelatedCol; | ||
} | ||
this.joinTableOwnerProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableOwnerCol); | ||
this.joinTableRelatedProp = this.joinTableModelClass.columnNameToPropertyName(this.joinTableRelatedCol); | ||
return retVal; | ||
} | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
/** | ||
* Reference to the column in the join table that refers to `fullOwnerCol()`. | ||
* | ||
* For example: `Person_Movie.actorId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
}, { | ||
key: 'fullJoinTableOwnerCol', | ||
value: function fullJoinTableOwnerCol() { | ||
return this.joinTable + '.' + this.joinTableOwnerCol; | ||
} | ||
var joinTable = this.joinTable; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
/** | ||
* Reference to the column in the join table that refers to `fullRelatedCol()`. | ||
* | ||
* For example: `Person_Movie.movieId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
var joinTableAlias = this.joinTableAlias(); | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
}, { | ||
key: 'fullJoinTableRelatedCol', | ||
value: function fullJoinTableRelatedCol() { | ||
return this.joinTable + '.' + this.joinTableRelatedCol; | ||
} | ||
return builder | ||
[joinMethod](joinTable + ' as ' + joinTableAlias, joinTableAlias + '.' + this.joinTableOwnerCol, this.fullOwnerCol()) | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, joinTableAlias + '.' + this.joinTableRelatedCol, relatedTableAlias + '.' + this.relatedCol) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* Alias to use for the join table when joining with the owner table. | ||
* | ||
* For example: `Person_Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.find = function (builder, owners) { | ||
var self = this; | ||
}, { | ||
key: 'joinTableAlias', | ||
value: function joinTableAlias() { | ||
return this.joinTable + '_rel_' + this.name; | ||
} | ||
builder.onBuild(function (builder) { | ||
var ownerIds = _.pluck(owners, self.ownerProp); | ||
var ownerJoinColumn = self.fullJoinTableOwnerCol(); | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
if (!builder.has(/select/)) { | ||
// If the user hasn't specified a select clause, select the related model's columns. | ||
// If we don't do this we also get the join table's columns. | ||
builder.select(self.relatedModelClass.tableName + '.*'); | ||
}, { | ||
key: 'clone', | ||
value: function clone() { | ||
var relation = (0, _get3.default)((0, _getPrototypeOf2.default)(ManyToManyRelation.prototype), 'clone', this).call(this); | ||
relation.joinTable = this.joinTable; | ||
relation.joinTableOwnerCol = this.joinTableOwnerCol; | ||
relation.joinTableOwnerProp = this.joinTableOwnerProp; | ||
relation.joinTableRelatedCol = this.joinTableRelatedCol; | ||
relation.joinTableRelatedProp = this.joinTableRelatedProp; | ||
relation.joinTableModelClass = this.joinTableModelClass; | ||
return relation; | ||
} | ||
self.findQuery(builder, ownerIds).select(ownerJoinColumn + ' as ' + ownerJoinColumnAlias); | ||
}); | ||
/** | ||
* @inheritDoc | ||
* @override | ||
*/ | ||
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 = self.relatedModelClass.columnNameToPropertyName(ownerJoinColumnAlias); | ||
var relatedByOwnerId = _.groupBy(related, ownerJoinPropAlias); | ||
}, { | ||
key: 'bindKnex', | ||
value: function bindKnex(knex) { | ||
var bound = (0, _get3.default)((0, _getPrototypeOf2.default)(ManyToManyRelation.prototype), 'bindKnex', this).call(this, knex); | ||
_.each(owners, function (owner) { | ||
owner[self.name] = relatedByOwnerId[owner[self.ownerProp]] || []; | ||
}); | ||
bound.joinTableModelClass = this.joinTableModelClass.bindKnex(knex); | ||
_.each(related, function (rel) { | ||
delete rel[ownerJoinPropAlias]; | ||
}); | ||
return bound; | ||
} | ||
return related; | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.insert = function (builder, owner, insertion) { | ||
var self = this; | ||
}, { | ||
key: 'findQuery', | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
builder.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()); | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
if (_lodash2.default.isArray(ownerCol)) { | ||
builder.whereIn(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullJoinTableOwnerCol(), ownerCol); | ||
} | ||
} | ||
builder.runAfterModelCreate(function (related) { | ||
var ownerId = owner[self.ownerProp]; | ||
var relatedIds = _.pluck(related, self.relatedProp); | ||
var joinRows = self._createJoinRows(ownerId, relatedIds); | ||
return builder.call(this.filter); | ||
} | ||
owner[self.name] = self.mergeModels(owner[self.name], related); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
// Insert the join rows to the join table. | ||
return self.relatedModelClass | ||
.knexQuery() | ||
.insert(joinRows) | ||
.into(self.joinTable) | ||
.then(function () { | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var joinTable = this.joinTable; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var joinTableAlias = this.joinTableAlias(); | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
var joinTableAsAlias = joinTable + ' as ' + joinTableAlias; | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var joinTableOwnerCol = joinTableAlias + '.' + this.joinTableOwnerCol; | ||
var joinTableRelatedCol = joinTableAlias + '.' + this.joinTableRelatedCol; | ||
var ownerCol = this.fullOwnerCol(); | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
return builder[joinMethod](joinTableAsAlias, joinTableOwnerCol, ownerCol)[joinMethod](relatedTableAsAlias, joinTableRelatedCol, relatedCol).call(this.filter); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'find', | ||
value: function find(builder, owners) { | ||
var _this2 = this; | ||
builder.onBuild(function (builder) { | ||
var ownerIds = _lodash2.default.pluck(owners, _this2.ownerProp); | ||
var ownerJoinColumn = _this2.fullJoinTableOwnerCol(); | ||
if (!builder.has(/select/)) { | ||
// If the user hasn't specified a select clause, select the related model's columns. | ||
// If we don't do this we also get the join table's columns. | ||
builder.select(_this2.relatedModelClass.tableName + '.*'); | ||
} | ||
_this2.findQuery(builder, ownerIds).select(ownerJoinColumn + ' as ' + ownerJoinColumnAlias); | ||
}); | ||
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); | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedByOwnerId[owner[_this2.ownerProp]] || []; | ||
}); | ||
_lodash2.default.each(related, function (rel) { | ||
delete rel[ownerJoinPropAlias]; | ||
}); | ||
return related; | ||
}); | ||
}); | ||
}; | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.update = function (builder, owner, update) { | ||
var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = self._makeFindIdQuery(owner[self.ownerProp]); | ||
}, { | ||
key: 'insert', | ||
value: function insert(builder, owner, insertion) { | ||
var _this3 = this; | ||
builder | ||
.$$update(update) | ||
.whereIn(self.relatedModelClass.getFullIdColumn(), idSelectQuery) | ||
.call(self.filter); | ||
}); | ||
}; | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.patch = function (builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
}; | ||
builder.runAfterModelCreate(function (related) { | ||
var ownerId = owner[_this3.ownerProp]; | ||
var relatedIds = _lodash2.default.pluck(related, _this3.relatedProp); | ||
var joinModels = _this3._createJoinModels(ownerId, relatedIds); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.delete = function (builder, owner) { | ||
var self = this; | ||
owner[_this3.name] = _this3.mergeModels(owner[_this3.name], related); | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = self._makeFindIdQuery(owner[self.ownerProp]); | ||
// Insert the join rows to the join table. | ||
return _this3.joinTableModelClass.bindKnex(builder.modelClass().knex()).query().childQueryOf(builder).insert(joinModels).return(related); | ||
}); | ||
} | ||
builder | ||
.$$delete() | ||
.whereIn(self.relatedModelClass.getFullIdColumn(), idSelectQuery) | ||
.call(self.filter); | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.relate = function (builder, owner, ids) { | ||
var self = this; | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
var _this4 = this; | ||
builder.onBuild(function (builder) { | ||
var joinRows = self._createJoinRows(owner[self.ownerProp], ids); | ||
// This is a bit weird: we make this query to the joinTable even though | ||
// this query builder is bound to the related model class. | ||
builder.$$insert(joinRows).into(self.joinTable); | ||
}); | ||
}; | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = _this4._makeFindIdQuery(builder, owner[_this4.ownerProp]); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.unrelate = function (builder, owner) { | ||
var self = this; | ||
builder.$$update(_update).whereIn(_this4.relatedModelClass.getFullIdColumn(), idSelectQuery).call(_this4.filter); | ||
}); | ||
} | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = self.relatedModelClass | ||
.query() | ||
.copyFrom(builder, /where/i) | ||
.select(self.fullRelatedCol()) | ||
.call(self.filter); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
// This is a bit weird: we make this query to the joinTable even though | ||
// this query builder is bound to the related model class. | ||
builder | ||
.clear() | ||
.$$delete() | ||
.from(self.joinTable) | ||
.where(self.fullJoinTableOwnerCol(), owner[self.ownerProp]) | ||
.whereIn(self.fullJoinTableRelatedCol(), idSelectQuery); | ||
}); | ||
}; | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
ManyToManyRelation.prototype._makeFindIdQuery = function (ownerId) { | ||
return this.ownerModelClass | ||
.knex() | ||
.select(this.fullJoinTableRelatedCol()) | ||
.from(this.joinTable) | ||
.where(this.fullJoinTableOwnerCol(), ownerId); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
ManyToManyRelation.prototype._createJoinRows = function (ownerId, relatedIds) { | ||
var self = this; | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
return _.map(relatedIds, function (relatedId) { | ||
var joinRow = {}; | ||
builder.onBuild(function (builder) { | ||
var idSelectQuery = _this5._makeFindIdQuery(builder, owner[_this5.ownerProp]); | ||
joinRow[self.joinTableOwnerCol] = ownerId; | ||
joinRow[self.joinTableRelatedCol] = relatedId; | ||
builder.$$delete().whereIn(_this5.relatedModelClass.getFullIdColumn(), idSelectQuery).call(_this5.filter); | ||
}); | ||
} | ||
return joinRow; | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
module.exports = ManyToManyRelation; | ||
}, { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
var _this6 = this; | ||
builder.setQueryExecutor(function () { | ||
var joinModels = _this6._createJoinModels(owner[_this6.ownerProp], ids); | ||
return _this6.joinTableModelClass.bindKnex(_this6.ownerModelClass.knex()).query().childQueryOf(builder).insert(joinModels).runAfter(_lodash2.default.constant({})); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'unrelate', | ||
value: function unrelate(builder, owner) { | ||
var _this7 = this; | ||
builder.setQueryExecutor(function (builder) { | ||
var idSelectQuery = _this7.relatedModelClass.query().childQueryOf(builder).copyFrom(builder, /where/i).select(_this7.fullRelatedCol()).call(_this7.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({})); | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '_makeFindIdQuery', | ||
value: function _makeFindIdQuery(builder, ownerId) { | ||
return this.joinTableModelClass.bindKnex(this.ownerModelClass.knex()).query().childQueryOf(builder).select(this.fullJoinTableRelatedCol()).where(this.fullJoinTableOwnerCol(), ownerId); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
}, { | ||
key: '_createJoinModels', | ||
value: function _createJoinModels(ownerId, relatedIds) { | ||
var _this8 = this; | ||
return _lodash2.default.map(relatedIds, function (relatedId) { | ||
var joinModel = {}; | ||
joinModel[_this8.joinTableOwnerProp] = ownerId; | ||
joinModel[_this8.joinTableRelatedProp] = relatedId; | ||
return joinModel; | ||
}); | ||
} | ||
}]); | ||
return ManyToManyRelation; | ||
})(_Relation3.default); | ||
exports.default = ManyToManyRelation; |
'use strict'; | ||
var _ = require('lodash') | ||
, Relation = require('./Relation'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | ||
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | ||
var _inherits2 = require('babel-runtime/helpers/inherits'); | ||
var _inherits3 = _interopRequireDefault(_inherits2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _Relation2 = require('./Relation'); | ||
var _Relation3 = _interopRequireDefault(_Relation2); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* @constructor | ||
* @ignore | ||
* @extends Relation | ||
*/ | ||
function OneToManyRelation() { | ||
Relation.apply(this, arguments); | ||
} | ||
Relation.extend(OneToManyRelation); | ||
var OneToManyRelation = (function (_Relation) { | ||
(0, _inherits3.default)(OneToManyRelation, _Relation); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.findQuery = function (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); | ||
} | ||
function OneToManyRelation() { | ||
(0, _classCallCheck3.default)(this, OneToManyRelation); | ||
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(OneToManyRelation).apply(this, arguments)); | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
(0, _createClass3.default)(OneToManyRelation, [{ | ||
key: 'findQuery', | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
/** | ||
* @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); | ||
} | ||
} | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
return builder.call(this.filter); | ||
} | ||
return builder | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, relatedTableAlias + '.' + this.relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.find = function (builder, owners) { | ||
var self = this; | ||
var ownerIds = _.unique(_.pluck(owners, this.ownerProp)); | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
builder.onBuild(function (builder) { | ||
self.findQuery(builder, ownerIds); | ||
}); | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedByOwnerId = _.groupBy(related, self.relatedProp); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
_.each(owners, function (owner) { | ||
owner[self.name] = relatedByOwnerId[owner[self.ownerProp]] || []; | ||
}); | ||
return builder[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()).call(this.filter); | ||
} | ||
}, { | ||
key: 'find', | ||
return related; | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
value: function find(builder, owners) { | ||
var _this2 = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.insert = function (builder, owner, insertion) { | ||
var self = this; | ||
var ownerIds = _lodash2.default.unique(_lodash2.default.pluck(owners, this.ownerProp)); | ||
_.each(insertion.models(), function (insert) { | ||
insert[self.relatedProp] = owner[self.ownerProp]; | ||
}); | ||
builder.onBuild(function (builder) { | ||
_this2.findQuery(builder, ownerIds); | ||
}); | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedByOwnerId = _lodash2.default.groupBy(related, _this2.relatedProp); | ||
builder.runAfterModelCreate(function (related) { | ||
owner[self.name] = self.mergeModels(owner[self.name], related); | ||
return related; | ||
}); | ||
}; | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedByOwnerId[owner[_this2.ownerProp]] || []; | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.update = function (builder, owner, update) { | ||
var self = this; | ||
return related; | ||
}); | ||
} | ||
builder.onBuild(function (builder) { | ||
self.findQuery(builder, owner[self.ownerProp]); | ||
builder.$$update(update); | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.patch = function (builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
}; | ||
}, { | ||
key: 'insert', | ||
value: function insert(builder, owner, insertion) { | ||
var _this3 = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.delete = function (builder, owner) { | ||
var self = this; | ||
_lodash2.default.each(insertion.models(), function (insert) { | ||
insert[_this3.relatedProp] = owner[_this3.ownerProp]; | ||
}); | ||
builder.onBuild(function (builder) { | ||
self.findQuery(builder, owner[self.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
}; | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.relate = function (builder, owner, ids) { | ||
var self = this; | ||
builder.runAfterModelCreate(function (related) { | ||
owner[_this3.name] = _this3.mergeModels(owner[_this3.name], related); | ||
return related; | ||
}); | ||
} | ||
builder.onBuild(function (builder) { | ||
var patch = relatePatch(self, owner[self.ownerProp]); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
// We build the input query, but we never actually execute it. This query is not executed, | ||
// because it would not invoke the $beforeUpdate and $afterUpdate hooks. We build it so that | ||
// toSql(), toString() etc. return the correct string. | ||
builder | ||
.$$update(patch) | ||
.whereIn(self.relatedModelClass.getFullIdColumn(), ids); | ||
}); | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
var _this4 = this; | ||
// Set a custom executor that executes a patch query. | ||
builder.setQueryExecutor(function () { | ||
var patch = relatePatch(self, owner[self.ownerProp]); | ||
builder.onBuild(function (builder) { | ||
_this4.findQuery(builder, owner[_this4.ownerProp]); | ||
builder.$$update(_update); | ||
}); | ||
} | ||
return self.relatedModelClass | ||
.query() | ||
.patch(patch) | ||
.whereIn(self.relatedModelClass.getFullIdColumn(), ids) | ||
.return({}); | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToManyRelation.prototype.unrelate = function (builder, owner) { | ||
var self = this; | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
builder.onBuild(function (builder) { | ||
var patch = relatePatch(self, null); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
// We build the input query, but we never actually execute it. This query is not executed, | ||
// because it would not invoke the $beforeUpdate and $afterUpdate hooks. We build it so that | ||
// toSql(), toString() etc. return the correct string. | ||
builder | ||
.$$update(patch) | ||
.where(self.fullRelatedCol(), owner[self.ownerProp]) | ||
.call(self.filter); | ||
}); | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
// Set a custom executor that executes a patch query. | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = relatePatch(self, null); | ||
builder.onBuild(function (builder) { | ||
_this5.findQuery(builder, owner[_this5.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
return self.relatedModelClass | ||
.query() | ||
.patch(patch) | ||
.copyFrom(builder, /where/i) | ||
.return({}); | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
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; | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = relatePatch(_this7, null); | ||
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({})); | ||
}); | ||
} | ||
}]); | ||
return OneToManyRelation; | ||
})(_Relation3.default); | ||
/** | ||
* @private | ||
*/ | ||
exports.default = OneToManyRelation; | ||
function relatePatch(relation, value) { | ||
@@ -198,4 +232,2 @@ var patch = {}; | ||
return patch; | ||
} | ||
module.exports = OneToManyRelation; | ||
} |
'use strict'; | ||
var _ = require('lodash') | ||
, Promise = require('bluebird') | ||
, Relation = require('./Relation'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | ||
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | ||
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | ||
var _inherits2 = require('babel-runtime/helpers/inherits'); | ||
var _inherits3 = _interopRequireDefault(_inherits2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _Relation2 = require('./Relation'); | ||
var _Relation3 = _interopRequireDefault(_Relation2); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* @constructor | ||
* @ignore | ||
* @extends Relation | ||
*/ | ||
function OneToOneRelation() { | ||
Relation.apply(this, arguments); | ||
} | ||
Relation.extend(OneToOneRelation); | ||
var OneToOneRelation = (function (_Relation) { | ||
(0, _inherits3.default)(OneToOneRelation, _Relation); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.findQuery = function (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) | ||
} | ||
function OneToOneRelation() { | ||
(0, _classCallCheck3.default)(this, OneToOneRelation); | ||
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(OneToOneRelation).apply(this, arguments)); | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
(0, _createClass3.default)(OneToOneRelation, [{ | ||
key: 'findQuery', | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
/** | ||
* @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(), _lodash2.default.compact(ownerCol)); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol); | ||
} | ||
} | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
return builder.call(this.filter); | ||
} | ||
return builder | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, relatedTableAlias + '.' + this.relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.find = function (builder, owners) { | ||
var self = this; | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
builder.onBuild(function (builder) { | ||
var relatedIds = _.unique(_.compact(_.pluck(owners, self.ownerProp))); | ||
self._makeFindQuery(builder, relatedIds); | ||
}); | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedById = _.indexBy(related, self.relatedProp); | ||
var relatedTableAsAlias = relatedTable + ' as ' + relatedTableAlias; | ||
var relatedCol = relatedTableAlias + '.' + this.relatedCol; | ||
_.each(owners, function (owner) { | ||
owner[self.name] = relatedById[owner[self.ownerProp]] || null; | ||
}); | ||
return builder[joinMethod](relatedTableAsAlias, relatedCol, this.fullOwnerCol()).call(this.filter); | ||
} | ||
return related; | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* Person | ||
* .query() | ||
* .update({ | ||
* age: Person.query().avg('age'), | ||
* | ||
* }) | ||
*/ | ||
}, { | ||
key: 'find', | ||
value: function find(builder, owners) { | ||
var _this2 = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.insert = function (builder, owner, insertion) { | ||
var self = this; | ||
builder.onBuild(function (builder) { | ||
var relatedIds = _lodash2.default.unique(_lodash2.default.compact(_lodash2.default.pluck(owners, _this2.ownerProp))); | ||
_this2._makeFindQuery(builder, relatedIds); | ||
}); | ||
if (insertion.models().length > 1) { | ||
throw new Error('can only insert one model to a OneToOneRelation'); | ||
} | ||
builder.runAfterModelCreate(function (related) { | ||
var relatedById = _lodash2.default.indexBy(related, _this2.relatedProp); | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
_lodash2.default.each(owners, function (owner) { | ||
owner[_this2.name] = relatedById[owner[_this2.ownerProp]] || null; | ||
}); | ||
builder.runAfterModelCreate(function (inserted) { | ||
owner[self.ownerProp] = inserted[0][self.relatedProp]; | ||
owner[self.name] = inserted[0]; | ||
return related; | ||
}); | ||
} | ||
var patch = {}; | ||
patch[self.ownerProp] = inserted[0][self.relatedProp]; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
return self.ownerModelClass | ||
.query() | ||
.patch(patch) | ||
.where(self.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.return(inserted); | ||
}); | ||
}; | ||
}, { | ||
key: 'insert', | ||
value: function insert(builder, owner, insertion) { | ||
var _this3 = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.update = function (builder, owner, update) { | ||
var self = this; | ||
if (insertion.models().length > 1) { | ||
throw new Error('can only insert one model to a OneToOneRelation'); | ||
} | ||
builder.onBuild(function (builder) { | ||
self._makeFindQuery(builder, owner[self.ownerProp]); | ||
builder.$$update(update); | ||
}); | ||
}; | ||
builder.onBuild(function (builder) { | ||
builder.$$insert(insertion); | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.patch = function (builder, owner, patch) { | ||
return this.update(builder, owner, patch); | ||
}; | ||
builder.runAfterModelCreate(function (inserted) { | ||
owner[_this3.ownerProp] = inserted[0][_this3.relatedProp]; | ||
owner[_this3.name] = inserted[0]; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.delete = function (builder, owner) { | ||
var self = this; | ||
var patch = {}; | ||
patch[_this3.ownerProp] = inserted[0][_this3.relatedProp]; | ||
builder.onBuild(function (builder) { | ||
self._makeFindQuery(builder, owner[self.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
}; | ||
return _this3.ownerModelClass.query().childQueryOf(builder).patch(patch).where(_this3.ownerModelClass.getFullIdColumn(), owner.$id()).return(inserted); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.relate = function (builder, owner, ids) { | ||
var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
if (ids.length > 1) { | ||
throw new Error('can only relate one model to a OneToOneRelation'); | ||
} | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
var _this4 = this; | ||
builder.onBuild(function (builder) { | ||
var patch = {}; | ||
patch[self.ownerProp] = ids[0]; | ||
owner[self.ownerProp] = ids[0]; | ||
builder.onBuild(function (builder) { | ||
_this4._makeFindQuery(builder, owner[_this4.ownerProp]); | ||
builder.$$update(_update); | ||
}); | ||
} | ||
// We build the input query, but we never actually execute it. This query is not executed, | ||
// because it would not invoke the $beforeUpdate and $afterUpdate hooks. We build it so that | ||
// toSql(), toString() etc. return the correct string. | ||
builder | ||
.$$update(patch) | ||
.from(self.ownerModelClass.tableName) | ||
.where(self.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.call(self.filter); | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
// Set a custom executor that executes a patch query. | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = {}; | ||
patch[self.ownerProp] = ids[0]; | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
return this.update(builder, owner, _patch); | ||
} | ||
return self.ownerModelClass | ||
.query() | ||
.patch(patch) | ||
.copyFrom(builder, /where/i) | ||
.return({}); | ||
}); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
OneToOneRelation.prototype.unrelate = function (builder, owner) { | ||
var self = this; | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
var _this5 = this; | ||
builder.onBuild(function (builder) { | ||
var patch = {}; | ||
patch[self.ownerProp] = null; | ||
owner[self.ownerProp] = null; | ||
builder.onBuild(function (builder) { | ||
_this5._makeFindQuery(builder, owner[_this5.ownerProp]); | ||
builder.$$delete(); | ||
}); | ||
} | ||
// We build the input query, but we never actually execute it. This query is not executed, | ||
// because it would not invoke the $beforeUpdate and $afterUpdate hooks. We build it so that | ||
// toSql(), toString() etc. return the correct string. | ||
builder | ||
.$$update(patch) | ||
.from(self.ownerModelClass.tableName) | ||
.where(self.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.call(self.filter); | ||
}); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
// Set a custom executor that executes a patch query. | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = {}; | ||
patch[self.ownerProp] = null; | ||
}, { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
var _this6 = this; | ||
return self.ownerModelClass | ||
.query() | ||
.patch(patch) | ||
.copyFrom(builder, /where/i) | ||
.return({}); | ||
}); | ||
}; | ||
if (ids.length > 1) { | ||
throw new Error('can only relate one model to a OneToOneRelation'); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
OneToOneRelation.prototype._makeFindQuery = function (builder, relatedIds) { | ||
if ((_.isArray(relatedIds) && _.isEmpty(relatedIds)) || !relatedIds) { | ||
return builder.setQueryExecutor(_.constant(Promise.resolve([]))); | ||
} else { | ||
return this.findQuery(builder, relatedIds); | ||
} | ||
}; | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = {}; | ||
module.exports = OneToOneRelation; | ||
patch[_this6.ownerProp] = ids[0]; | ||
owner[_this6.ownerProp] = ids[0]; | ||
return _this6.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).where(_this6.ownerModelClass.getFullIdColumn(), owner.$id()).runAfterModelCreate(_lodash2.default.constant({})); | ||
}); | ||
} | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
}, { | ||
key: 'unrelate', | ||
value: function unrelate(builder, owner) { | ||
var _this7 = this; | ||
builder.setQueryExecutor(function (builder) { | ||
var patch = {}; | ||
patch[_this7.ownerProp] = null; | ||
owner[_this7.ownerProp] = null; | ||
return _this7.ownerModelClass.query().childQueryOf(builder).patch(patch).copyFrom(builder, /where/i).where(_this7.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); | ||
} | ||
} | ||
}]); | ||
return OneToOneRelation; | ||
})(_Relation3.default); | ||
exports.default = OneToOneRelation; |
'use strict'; | ||
var _ = require('lodash') | ||
, utils = require('../utils') | ||
, QueryBuilder = require('../queryBuilder/QueryBuilder'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = undefined; | ||
var _create = require('babel-runtime/core-js/object/create'); | ||
var _create2 = _interopRequireDefault(_create); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _utils = require('../utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
var _QueryBuilder = require('../queryBuilder/QueryBuilder'); | ||
var _QueryBuilder2 = _interopRequireDefault(_QueryBuilder); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
@@ -32,3 +59,3 @@ * @typedef {Object} RelationJoin | ||
* | ||
* @property {String} from | ||
* @property {string} from | ||
* The relation column in the owner table. Must be given with the table name. | ||
@@ -38,3 +65,3 @@ * For example `Person.id`. Note that neither this nor `to` need to be foreign | ||
* | ||
* @property {String} to | ||
* @property {string} to | ||
* The relation column in the related table. Must be given with the table name. | ||
@@ -47,7 +74,11 @@ * For example `Movie.id`. Note that neither this nor `from` need to be foreign | ||
* | ||
* @property {String} through.from | ||
* @property {Model} through.modelClass | ||
* If the there is model class available for the join table, it can be provided | ||
* using this property. | ||
* | ||
* @property {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. | ||
* | ||
* @property {String} through.to | ||
* @property {string} through.to | ||
* The column that is joined to `to` property of the `RelationJoin`. For example | ||
@@ -60,3 +91,3 @@ * `Person_Movie.movieId` where `Person_Movie` is the join table. | ||
* | ||
* @property {Model|String} modelClass | ||
* @property {Model|string} modelClass | ||
* A {@link Model} subclass constructor or an absolute path to a module that exports one. | ||
@@ -81,3 +112,3 @@ * | ||
* | ||
* @param {String} relationName | ||
* @param {string} relationName | ||
* Name of the relation. | ||
@@ -90,483 +121,456 @@ * | ||
* @abstract | ||
* @constructor | ||
*/ | ||
function Relation(relationName, OwnerClass) { | ||
/** | ||
* Name of the relation. | ||
* | ||
* @type {String} | ||
*/ | ||
this.name = relationName; | ||
/** | ||
* The owner class of this relation. | ||
* | ||
* This must be a subclass of Model. | ||
* | ||
* @type {Model} | ||
*/ | ||
this.ownerModelClass = OwnerClass; | ||
var Relation = (function () { | ||
function Relation(relationName, OwnerClass) { | ||
(0, _classCallCheck3.default)(this, Relation); | ||
/** | ||
* The related class. | ||
* | ||
* This must be a subclass of Model. | ||
* | ||
* @type {Model} | ||
*/ | ||
this.relatedModelClass = null; | ||
/** | ||
* Name of the relation. | ||
* | ||
* @type {string} | ||
*/ | ||
this.name = relationName; | ||
/** | ||
* The relation column in the owner table. | ||
* | ||
* @type {String} | ||
*/ | ||
this.ownerCol = null; | ||
/** | ||
* The owner class of this relation. | ||
* | ||
* This must be a subclass of Model. | ||
* | ||
* @type {Class<Model>} | ||
*/ | ||
this.ownerModelClass = OwnerClass; | ||
/** | ||
* The relation property in the owner model. | ||
* | ||
* @type {String} | ||
*/ | ||
this.ownerProp = null; | ||
/** | ||
* The related class. | ||
* | ||
* This must be a subclass of Model. | ||
* | ||
* @type {Class<Model>} | ||
*/ | ||
this.relatedModelClass = null; | ||
/** | ||
* The relation column in the related table. | ||
* | ||
* @type {String} | ||
*/ | ||
this.relatedCol = null; | ||
/** | ||
* The relation column in the owner table. | ||
* | ||
* @type {string} | ||
*/ | ||
this.ownerCol = null; | ||
/** | ||
* The relation property in the related model. | ||
* | ||
* @type {String} | ||
*/ | ||
this.relatedProp = null; | ||
/** | ||
* The relation property in the owner model. | ||
* | ||
* @type {string} | ||
*/ | ||
this.ownerProp = null; | ||
/** | ||
* The join table. | ||
* | ||
* @type {String} | ||
*/ | ||
this.joinTable = null; | ||
/** | ||
* The relation column in the related table. | ||
* | ||
* @type {string} | ||
*/ | ||
this.relatedCol = null; | ||
/** | ||
* The relation column in the join table that points to the owner table. | ||
* | ||
* @type {String} | ||
*/ | ||
this.joinTableOwnerCol = null; | ||
/** | ||
* The relation property in the related model. | ||
* | ||
* @type {string} | ||
*/ | ||
this.relatedProp = null; | ||
/** | ||
* The relation column in the join table that points to the related table. | ||
* | ||
* @type {String} | ||
*/ | ||
this.joinTableRelatedCol = null; | ||
/** | ||
* Optional additional filter query. | ||
* | ||
* @type {function (QueryBuilder)} | ||
*/ | ||
this.filter = null; | ||
} | ||
/** | ||
* Optional additional filter query. | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @type {function (QueryBuilder)} | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
this.filter = null; | ||
} | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
Relation.extend = function (subclassConstructor) { | ||
utils.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
}; | ||
(0, _createClass3.default)(Relation, [{ | ||
key: 'setMapping', | ||
/** | ||
* Constructs the instance based on a mapping data. | ||
* | ||
* @param {RelationMapping} mapping | ||
*/ | ||
Relation.prototype.setMapping = function (mapping) { | ||
// Avoid require loop and import here. | ||
var Model = require(__dirname + '/../model/Model'); | ||
/** | ||
* Constructs the instance based on a mapping data. | ||
* | ||
* @param {RelationMapping} mapping | ||
*/ | ||
value: function setMapping(mapping) { | ||
// Avoid require loop and import here. | ||
var Model = require(__dirname + '/../model/Model').default; | ||
if (!utils.isSubclassOf(this.ownerModelClass, Model)) { | ||
throw new Error('Relation\'s owner is not a subclass of Model'); | ||
} | ||
if (!_utils2.default.isSubclassOf(this.ownerModelClass, Model)) { | ||
throw new Error('Relation\'s owner is not a subclass of Model'); | ||
} | ||
var errorPrefix = this.ownerModelClass.name + '.relationMappings.' + this.name; | ||
var errorPrefix = this.ownerModelClass.name + '.relationMappings.' + this.name; | ||
if (!mapping.modelClass) { | ||
throw new Error(errorPrefix + '.modelClass is not defined'); | ||
} | ||
if (!mapping.modelClass) { | ||
throw new Error(errorPrefix + '.modelClass is not defined'); | ||
} | ||
if (_.isString(mapping.modelClass)) { | ||
try { | ||
// babel 6 style of exposing es6 exports to commonjs https://github.com/babel/babel/issues/2683 | ||
var relatedModelClassModule = require(mapping.modelClass); | ||
this.relatedModelClass = utils.isSubclassOf(relatedModelClassModule.default, Model) ? | ||
relatedModelClassModule.default : relatedModelClassModule; | ||
} catch (err) { | ||
throw new Error(errorPrefix + '.modelClass is an invalid file path to a model class.'); | ||
} | ||
if (_lodash2.default.isString(mapping.modelClass)) { | ||
try { | ||
// babel 6 style of exposing es6 exports to commonjs https://github.com/babel/babel/issues/2683 | ||
var relatedModelClassModule = require(mapping.modelClass); | ||
this.relatedModelClass = _utils2.default.isSubclassOf(relatedModelClassModule.default, Model) ? relatedModelClassModule.default : relatedModelClassModule; | ||
} catch (err) { | ||
throw new Error(errorPrefix + '.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.'); | ||
} | ||
} else { | ||
this.relatedModelClass = mapping.modelClass; | ||
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.'); | ||
} | ||
} else { | ||
this.relatedModelClass = mapping.modelClass; | ||
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 (!_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 (!mapping.relation) { | ||
throw new Error(errorPrefix + '.relation is not defined'); | ||
} | ||
if (!mapping.relation) { | ||
throw new Error(errorPrefix + '.relation is not defined'); | ||
} | ||
if (!utils.isSubclassOf(mapping.relation, Relation)) { | ||
throw new Error(errorPrefix + '.relation is not a subclass of Relation'); | ||
} | ||
if (!_utils2.default.isSubclassOf(mapping.relation, Relation)) { | ||
throw new Error(errorPrefix + '.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 || !_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\'}'); | ||
} | ||
var joinOwner = null; | ||
var joinRelated = null; | ||
var joinOwner = null; | ||
var joinRelated = null; | ||
var joinFrom = parseColumn(mapping.join.from); | ||
var joinTo = parseColumn(mapping.join.to); | ||
var joinFrom = Relation.parseColumn(mapping.join.from); | ||
var joinTo = Relation.parseColumn(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 || !joinFrom.name) { | ||
throw new Error(errorPrefix + '.join.from must have format TableName.columnName. For example `SomeTable.id`.'); | ||
} | ||
if (!joinTo.table || !joinTo.name) { | ||
throw new Error(errorPrefix + '.join.to must have format TableName.columnName. For example `SomeTable.id`.'); | ||
} | ||
if (!joinTo.table || !joinTo.name) { | ||
throw new Error(errorPrefix + '.join.to must have format TableName.columnName. For example `SomeTable.id`.'); | ||
} | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
joinOwner = joinFrom; | ||
joinRelated = joinTo; | ||
} else if (joinTo.table === this.ownerModelClass.tableName) { | ||
joinOwner = joinTo; | ||
joinRelated = joinFrom; | ||
} else { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the owner model table.'); | ||
} | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
joinOwner = joinFrom; | ||
joinRelated = joinTo; | ||
} else if (joinTo.table === this.ownerModelClass.tableName) { | ||
joinOwner = joinTo; | ||
joinRelated = joinFrom; | ||
} else { | ||
throw new Error(errorPrefix + '.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.'); | ||
} | ||
if (joinRelated.table !== this.relatedModelClass.tableName) { | ||
throw new Error(errorPrefix + '.join: either `from` or `to` must point to the related model table.'); | ||
} | ||
if (mapping.join.through) { | ||
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\'}'); | ||
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); | ||
} | ||
var joinTableFrom = parseColumn(mapping.join.through.from); | ||
var joinTableTo = parseColumn(mapping.join.through.to); | ||
/** | ||
* Reference to the relation column in the owner model's table. | ||
* | ||
* For example: `Person.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
if (!joinTableFrom.table || !joinTableFrom.name) { | ||
throw new Error(errorPrefix + '.join.through.from must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
}, { | ||
key: 'fullOwnerCol', | ||
value: function fullOwnerCol() { | ||
return this.ownerModelClass.tableName + '.' + this.ownerCol; | ||
} | ||
if (!joinTableTo.table || !joinTableTo.name) { | ||
throw new Error(errorPrefix + '.join.through.to must have format JoinTable.columnName. For example `JoinTable.someId`.'); | ||
/** | ||
* Reference to the relation column in the related model's table. | ||
* | ||
* For example: `Movie.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
}, { | ||
key: 'fullRelatedCol', | ||
value: function fullRelatedCol() { | ||
return this.relatedModelClass.tableName + '.' + this.relatedCol; | ||
} | ||
if (joinTableFrom.table !== joinTableTo.table) { | ||
throw new Error(errorPrefix + '.join.through `from` and `to` must point to the same join table.'); | ||
/** | ||
* Alias to use for the related table when joining with the owner table. | ||
* | ||
* For example: `Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
}, { | ||
key: 'relatedTableAlias', | ||
value: function relatedTableAlias() { | ||
return this.relatedModelClass.tableName + '_rel_' + this.name; | ||
} | ||
this.joinTable = joinTableFrom.table; | ||
/** | ||
* Clones this relation. | ||
* | ||
* @returns {Relation} | ||
*/ | ||
if (joinFrom.table === this.ownerModelClass.tableName) { | ||
this.joinTableOwnerCol = joinTableFrom.name; | ||
this.joinTableRelatedCol = joinTableTo.name; | ||
} else { | ||
this.joinTableRelatedCol = joinTableFrom.name; | ||
this.joinTableOwnerCol = joinTableTo.name; | ||
}, { | ||
key: 'clone', | ||
value: function clone() { | ||
var relation = new this.constructor(this.name, this.ownerModelClass); | ||
relation.relatedModelClass = this.relatedModelClass; | ||
relation.ownerCol = this.ownerCol; | ||
relation.ownerProp = this.ownerProp; | ||
relation.relatedCol = this.relatedCol; | ||
relation.relatedProp = this.relatedProp; | ||
relation.filter = this.filter; | ||
return relation; | ||
} | ||
} | ||
this.ownerProp = this._propertyName(joinOwner, this.ownerModelClass); | ||
this.ownerCol = joinOwner.name; | ||
this.relatedProp = this._propertyName(joinRelated, this.relatedModelClass); | ||
this.relatedCol = joinRelated.name; | ||
this.filter = parseFilter(mapping); | ||
}; | ||
/** | ||
* Returns a clone of this relation with `relatedModelClass` and `ownerModelClass` bound to the given knex. | ||
* | ||
* See `Model.bindKnex`. | ||
* | ||
* @param knex | ||
* @returns {Relation} | ||
*/ | ||
/** | ||
* Reference to the relation column in the owner model's table. | ||
* | ||
* For example: `Person.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullOwnerCol = function () { | ||
return this.ownerModelClass.tableName + '.' + this.ownerCol; | ||
}; | ||
}, { | ||
key: 'bindKnex', | ||
value: function bindKnex(knex) { | ||
var bound = this.clone(); | ||
/** | ||
* Reference to the relation column in the related model's table. | ||
* | ||
* For example: `Movie.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullRelatedCol = function () { | ||
return this.relatedModelClass.tableName + '.' + this.relatedCol; | ||
}; | ||
bound.relatedModelClass = this.relatedModelClass.bindKnex(knex); | ||
bound.ownerModelClass = this.ownerModelClass.bindKnex(knex); | ||
/** | ||
* Reference to the column in the join table that refers to `fullOwnerCol()`. | ||
* | ||
* For example: `Person_Movie.actorId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullJoinTableOwnerCol = function () { | ||
return this.joinTable + '.' + this.joinTableOwnerCol; | ||
}; | ||
return bound; | ||
} | ||
/** | ||
* Reference to the column in the join table that refers to `fullRelatedCol()`. | ||
* | ||
* For example: `Person_Movie.movieId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullJoinTableRelatedCol = function () { | ||
return this.joinTable + '.' + this.joinTableRelatedCol; | ||
}; | ||
/** | ||
* @protected | ||
* @param {Array.<Model>} models1 | ||
* @param {Array.<Model>} models2 | ||
* @returns {Array.<Model>} | ||
*/ | ||
/** | ||
* Alias to use for the related table when joining with the owner table. | ||
* | ||
* For example: `Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.relatedTableAlias = function () { | ||
return this.relatedModelClass.tableName + '_rel_' + this.name; | ||
}; | ||
}, { | ||
key: 'mergeModels', | ||
value: function mergeModels(models1, models2) { | ||
models1 = _lodash2.default.compact(models1); | ||
models2 = _lodash2.default.compact(models2); | ||
var modelsById = (0, _create2.default)(null); | ||
/** | ||
* Alias to use for the join table when joining with the owner table. | ||
* | ||
* For example: `Person_Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.joinTableAlias = function () { | ||
return this.joinTable + '_rel_' + this.name; | ||
}; | ||
_lodash2.default.forEach(models1, function (model) { | ||
modelsById[model.$id()] = model; | ||
}); | ||
/** | ||
* Clones this relation. | ||
* | ||
* @returns {Relation} | ||
*/ | ||
Relation.prototype.clone = function () { | ||
var clone = new this.constructor(this.name, this.ownerModelClass); | ||
_lodash2.default.forEach(models2, function (model) { | ||
modelsById[model.$id()] = model; | ||
}); | ||
clone.relatedModelClass = this.relatedModelClass; | ||
clone.ownerCol = this.ownerCol; | ||
clone.ownerProp = this.ownerProp; | ||
clone.relatedCol = this.relatedCol; | ||
clone.relatedProp = this.relatedProp; | ||
clone.joinTable = this.joinTable; | ||
clone.joinTableOwnerCol = this.joinTableOwnerCol; | ||
clone.joinTableRelatedCol = this.joinTableRelatedCol; | ||
clone.filter = this.filter; | ||
return _lodash2.default.sortBy(_lodash2.default.values(modelsById), function (model) { | ||
return model.$id(); | ||
}); | ||
} | ||
return clone; | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {number|string} ownerCol | ||
* @param {boolean} isColumnRef | ||
* @returns {QueryBuilder} | ||
*/ | ||
/** | ||
* Returns a clone of this relation with `relatedModelClass` and `ownerModelClass` bound to the given knex. | ||
* | ||
* See `Model.bindKnex`. | ||
* | ||
* @param knex | ||
* @returns {Relation} | ||
*/ | ||
Relation.prototype.bindKnex = function (knex) { | ||
var bound = this.clone(); | ||
}, { | ||
key: 'findQuery', | ||
value: function findQuery(builder, ownerCol, isColumnRef) { | ||
throw new Error('not implemented'); | ||
} | ||
bound.relatedModelClass = bound.relatedModelClass.bindKnex(knex); | ||
bound.ownerModelClass = bound.ownerModelClass.bindKnex(knex); | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {string} joinMethod | ||
* @returns {QueryBuilder} | ||
*/ | ||
return bound; | ||
}; | ||
}, { | ||
key: 'join', | ||
value: function join(builder, joinMethod) { | ||
throw new Error('not implemented'); | ||
} | ||
/** | ||
* @protected | ||
* @param {Array.<Model>} models1 | ||
* @param {Array.<Model>} models2 | ||
* @returns {Array.<Model>} | ||
*/ | ||
Relation.prototype.mergeModels = function (models1, models2) { | ||
models1 = _.compact(models1); | ||
models2 = _.compact(models2); | ||
var modelsById = Object.create(null); | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} owners | ||
*/ | ||
_.each(models1, function (model) { | ||
modelsById[model.$id()] = model; | ||
}); | ||
}, { | ||
key: 'find', | ||
value: function find(builder, owners) { | ||
throw new Error('not implemented'); | ||
} | ||
_.each(models2, function (model) { | ||
modelsById[model.$id()] = model; | ||
}); | ||
/* istanbul ignore next */ | ||
/** | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} insertion | ||
*/ | ||
return _.sortBy(_.values(modelsById), function (model) { | ||
return model.$id(); | ||
}); | ||
}; | ||
}, { | ||
key: 'insert', | ||
value: function insert(builder, owner, insertion) { | ||
throw new Error('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {number|string} ownerCol | ||
* @param {boolean} isColumnRef | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} update | ||
*/ | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {string} joinMethod | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.join = function (builder, joinMethod) { | ||
throw new Error('not implemented'); | ||
}; | ||
}, { | ||
key: 'update', | ||
value: function update(builder, owner, _update) { | ||
return builder; | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} owners | ||
*/ | ||
Relation.prototype.find = function (builder, owners) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} patch | ||
*/ | ||
/* istanbul ignore next */ | ||
/** | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} insertion | ||
*/ | ||
Relation.prototype.insert = function (builder, owner, insertion) { | ||
throw new Error('not implemented'); | ||
}; | ||
}, { | ||
key: 'patch', | ||
value: function patch(builder, owner, _patch) { | ||
throw new Error('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} update | ||
*/ | ||
Relation.prototype.update = function (builder, owner, update) { | ||
return builder; | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
*/ | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {InsertionOrUpdate} patch | ||
*/ | ||
Relation.prototype.patch = function (builder, owner, patch) { | ||
throw new Error('not implemented'); | ||
}; | ||
}, { | ||
key: 'delete', | ||
value: function _delete(builder, owner) { | ||
throw new Error('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
*/ | ||
Relation.prototype.delete = function (builder, owner) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {number|string|Array.<number>|Array.<string>} ids | ||
*/ | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
* @param {number|string|Array.<number>|Array.<string>} ids | ||
*/ | ||
Relation.prototype.relate = function (builder, owner, ids) { | ||
throw new Error('not implemented'); | ||
}; | ||
}, { | ||
key: 'relate', | ||
value: function relate(builder, owner, ids) { | ||
throw new Error('not implemented'); | ||
} | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
*/ | ||
Relation.prototype.unrelate = function (builder, owner) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} owner | ||
*/ | ||
/** | ||
* @private | ||
*/ | ||
Relation.prototype._propertyName = function (column, modelClass) { | ||
var propertyName = modelClass.columnNameToPropertyName(column.name); | ||
}, { | ||
key: 'unrelate', | ||
value: function unrelate(builder, owner) { | ||
throw new Error('not implemented'); | ||
} | ||
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); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
return propertyName; | ||
}; | ||
}, { | ||
key: '_propertyName', | ||
value: function _propertyName(column, modelClass) { | ||
var propertyName = modelClass.columnNameToPropertyName(column.name); | ||
/** | ||
* @private | ||
*/ | ||
function parseFilter(mapping) { | ||
if (_.isFunction(mapping.filter)) { | ||
return mapping.filter; | ||
} else if (_.isObject(mapping.filter)) { | ||
return function (queryBuilder) { | ||
queryBuilder.where(mapping.filter); | ||
}; | ||
} else { | ||
return _.noop; | ||
} | ||
} | ||
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); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function parseColumn(column) { | ||
var parts = column.split('.'); | ||
return propertyName; | ||
} | ||
return { | ||
table: parts[0] && parts[0].trim(), | ||
name: parts[1] && parts[1].trim() | ||
}; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
module.exports = Relation; | ||
}], [{ | ||
key: 'extend', | ||
value: function extend(subclassConstructor) { | ||
_utils2.default.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
} | ||
}, { | ||
key: 'parseFilter', | ||
value: function parseFilter(mapping) { | ||
if (_lodash2.default.isFunction(mapping.filter)) { | ||
return mapping.filter; | ||
} else if (_lodash2.default.isObject(mapping.filter)) { | ||
return function (queryBuilder) { | ||
queryBuilder.where(mapping.filter); | ||
}; | ||
} else { | ||
return _lodash2.default.noop; | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
}, { | ||
key: 'parseColumn', | ||
value: function parseColumn(column) { | ||
var parts = column.split('.'); | ||
return { | ||
table: parts[0] && parts[0].trim(), | ||
name: parts[1] && parts[1].trim() | ||
}; | ||
} | ||
}]); | ||
return Relation; | ||
})(); | ||
exports.default = Relation; |
'use strict'; | ||
var _ = require('lodash'); | ||
var Promise = require('bluebird'); | ||
var Model = require('./model/Model'); | ||
var utils = require('./utils'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = transaction; | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _bluebird = require('bluebird'); | ||
var _bluebird2 = _interopRequireDefault(_bluebird); | ||
var _Model = require('./model/Model'); | ||
var _Model2 = _interopRequireDefault(_Model); | ||
var _utils = require('./utils'); | ||
var _utils2 = _interopRequireDefault(_utils); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
@@ -66,3 +84,3 @@ * Starts a transaction. | ||
* objection.transaction(Person, function (Person) { | ||
* var knex = this; | ||
* let knex = this; | ||
* | ||
@@ -86,23 +104,23 @@ * return Person | ||
*/ | ||
module.exports = function transaction() { | ||
function transaction() { | ||
// There must be at least one model class and the callback. | ||
if (arguments.length < 2) { | ||
return Promise.reject(new Error('objection.transaction: provide at least one Model class to bind to the transaction')); | ||
return _bluebird2.default.reject(new Error('objection.transaction: provide at least one Model class to bind to the transaction')); | ||
} | ||
// The last argument should be the callback and all other Model subclasses. | ||
var callback = _.last(arguments); | ||
var modelClasses = _.take(arguments, arguments.length - 1); | ||
var i; | ||
var callback = _lodash2.default.last(arguments); | ||
var modelClasses = _lodash2.default.take(arguments, arguments.length - 1); | ||
var i = undefined; | ||
for (i = 0; i < modelClasses.length; ++i) { | ||
if (!utils.isSubclassOf(modelClasses[i], Model)) { | ||
return Promise.reject(new Error('objection.transaction: all but the last argument should be Model subclasses')); | ||
if (!_utils2.default.isSubclassOf(modelClasses[i], _Model2.default)) { | ||
return _bluebird2.default.reject(new Error('objection.transaction: all but the last argument should be Model subclasses')); | ||
} | ||
} | ||
var knex = _.first(modelClasses).knex(); | ||
var knex = _lodash2.default.first(modelClasses).knex(); | ||
for (i = 0; i < modelClasses.length; ++i) { | ||
if (modelClasses[i].knex() !== knex) { | ||
return Promise.reject(new Error('objection.transaction: all Model subclasses must be bound to the same database')); | ||
return _bluebird2.default.reject(new Error('objection.transaction: all Model subclasses must be bound to the same database')); | ||
} | ||
@@ -113,15 +131,15 @@ } | ||
if (isGenerator(callback)) { | ||
callback = Promise.coroutine(callback); | ||
callback = _bluebird2.default.coroutine(callback); | ||
} | ||
return knex.transaction(function (trx) { | ||
for (var i = 0; i < modelClasses.length; ++i) { | ||
modelClasses[i] = modelClasses[i].bindTransaction(trx); | ||
for (var _i = 0; _i < modelClasses.length; ++_i) { | ||
modelClasses[_i] = modelClasses[_i].bindTransaction(trx); | ||
} | ||
return Promise.try(function () { | ||
return _bluebird2.default.try(function () { | ||
return callback.apply(trx, modelClasses); | ||
}); | ||
}); | ||
}; | ||
} | ||
@@ -137,4 +155,4 @@ /** | ||
* ```js | ||
* var Person = require('./models/Person'); | ||
* var transaction; | ||
* let Person = require('./models/Person'); | ||
* let transaction; | ||
* | ||
@@ -167,14 +185,14 @@ * objection.transaction.start(Person).then(function (trx) { | ||
*/ | ||
module.exports.start = function (modelClassOrKnex) { | ||
transaction.start = function (modelClassOrKnex) { | ||
var knex = modelClassOrKnex; | ||
if (utils.isSubclassOf(modelClassOrKnex, Model)) { | ||
if (_utils2.default.isSubclassOf(modelClassOrKnex, _Model2.default)) { | ||
knex = modelClassOrKnex.knex(); | ||
} | ||
if (!_.isFunction(knex.transaction)) { | ||
return Promise.reject(new Error('objection.transaction.start: first argument must be a model class or a knex instance')); | ||
if (!_lodash2.default.isFunction(knex.transaction)) { | ||
return _bluebird2.default.reject(new Error('objection.transaction.start: first argument must be a model class or a knex instance')); | ||
} | ||
return new Promise(function (resolve) { | ||
return new _bluebird2.default(function (resolve) { | ||
knex.transaction(function (trx) { | ||
@@ -190,2 +208,2 @@ resolve(trx); | ||
return fn && fn.constructor && fn.constructor.name === 'GeneratorFunction'; | ||
} | ||
} |
'use strict'; | ||
var _ = require('lodash') | ||
, util = require('util'); | ||
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'), | ||
util = require('util'); | ||
/** | ||
@@ -18,8 +28,8 @@ * Makes the `Constructor` inherit `SuperConstructor`. | ||
*/ | ||
module.exports.inherits = function(subClass, 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); | ||
throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : (0, _typeof3.default)(superClass))); | ||
} | ||
subClass.prototype = Object.create(superClass && superClass.prototype, { | ||
subClass.prototype = (0, _create2.default)(superClass && superClass.prototype, { | ||
constructor: { | ||
@@ -48,3 +58,3 @@ value: subClass, | ||
*/ | ||
module.exports.isSubclassOf = function(Constructor, SuperConstructor) { | ||
module.exports.isSubclassOf = function (Constructor, SuperConstructor) { | ||
if (!_.isFunction(SuperConstructor)) { | ||
@@ -100,2 +110,2 @@ return false; | ||
return knex.client.dialect === 'postgresql'; | ||
}; | ||
}; |
'use strict'; | ||
var util = require('util'); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = ValidationError; | ||
var _stringify = require('babel-runtime/core-js/json/stringify'); | ||
var _stringify2 = _interopRequireDefault(_stringify); | ||
var _util = require('util'); | ||
var _util2 = _interopRequireDefault(_util); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
// Note: babel cannot inherit from built-in types like Error. | ||
// that's why we use ES5 inheritance here. | ||
/** | ||
@@ -30,9 +46,5 @@ * Error of this class is thrown when a Model validation fails. | ||
*/ | ||
this.message = JSON.stringify(errors, null, 2); | ||
this.message = (0, _stringify2.default)(errors, null, 2); | ||
} | ||
util.inherits(ValidationError, Error); | ||
module.exports = ValidationError; | ||
_util2.default.inherits(ValidationError, Error); |
{ | ||
"name": "objection", | ||
"version": "0.3.3", | ||
"version": "0.4.0-rc.1", | ||
"description": "An SQL-friendly ORM for Node.js", | ||
"main": "objection.js", | ||
"main": "lib/objection.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"test": "istanbul --config=.istanbul.yml cover _mocha -- --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"build": "node build", | ||
"test": "npm run build && istanbul --config=.istanbul.yml cover _mocha -- --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"perf": "mocha --slow 60000 --timeout 60000 --reporter spec --recursive perf", | ||
"test-only": "mocha --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"test-bail": "mocha --slow 10 --timeout 5000 --reporter spec --recursive tests --bail", | ||
"test-trace-opt": "mocha --slow 10 --timeout 5000 --reporter spec --recursive tests --trace_opt --trace_deopt", | ||
"coveralls": "cat ./test-coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" | ||
"coveralls": "cat ./test-coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", | ||
"prepublish": "npm run build" | ||
}, | ||
@@ -40,5 +40,6 @@ "author": { | ||
"lib/*", | ||
"objection.js" | ||
"src/*" | ||
], | ||
"dependencies": { | ||
"babel-runtime": "^6.3.19", | ||
"bluebird": "^3.0.5", | ||
@@ -50,5 +51,10 @@ "lodash": "^3.9.0", | ||
"devDependencies": { | ||
"browserify-global-shim": "^1.0.0", | ||
"babel-core": "^6.3.17", | ||
"babel-plugin-transform-decorators-legacy": "^1.3.3", | ||
"babel-plugin-transform-runtime": "^6.3.13", | ||
"babel-preset-es2015": "^6.3.13", | ||
"babel-preset-stage-0": "^6.3.13", | ||
"coveralls": "^2.11.2", | ||
"expect.js": "^0.3.1", | ||
"glob": "^6.0.1", | ||
"grunt": "^0.4.5", | ||
@@ -58,2 +64,3 @@ "grunt-jsdoc": "^1.0.0", | ||
"knex": "^0.9.0", | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^2.3.4", | ||
@@ -63,12 +70,3 @@ "mysql": "^2.7.0", | ||
"sqlite3": "^3.0.8" | ||
}, | ||
"browserify": { | ||
"transform": [ | ||
"browserify-global-shim" | ||
] | ||
}, | ||
"browserify-global-shim": { | ||
"bluebird": "Promise", | ||
"lodash": "_" | ||
} | ||
} |
986
README.md
[![Build Status](https://travis-ci.org/Vincit/objection.js.svg?branch=master)](https://travis-ci.org/Vincit/objection.js) [![Coverage Status](https://coveralls.io/repos/Vincit/objection.js/badge.svg?branch=master&service=github)](https://coveralls.io/github/Vincit/objection.js?branch=master) | ||
# Introduction | ||
Objection.js is an [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Node.js](https://nodejs.org/) | ||
that aims to stay out of your way and make it as easy as possible to use the full power of SQL and the underlying | ||
database engine. | ||
Objection.js is built on the wonderful SQL query builder [knex](http://knexjs.org). All databases supported by knex | ||
are supported by objection.js. **SQLite3**, **Postgres** and **MySQL** are [thoroughly tested](https://travis-ci.org/Vincit/objection.js). | ||
What objection.js gives you: | ||
* An easy declarative way of [defining models](#models) and relations between them | ||
* Simple and fun way to [fetch, insert, update and delete](#query-examples) models using the full power of SQL | ||
* Powerful mechanisms for [eager loading](#eager-queries) and [inserting](#eager-inserts) arbitrarily large trees of relations | ||
* A way to [store complex documents](#documents) as single rows | ||
* Completely [Promise](https://github.com/petkaantonov/bluebird) based API | ||
* Easy to use [transactions](#transactions) | ||
* Optional [JSON schema](#validation) validation | ||
What objection.js **doesn't** give you: | ||
* A custom query DSL. SQL is used as a query language. | ||
* Automatic database schema creation and migration. | ||
It is useful for the simple things, but usually just gets in your way when doing anything non-trivial. | ||
Objection.js leaves the schema related things to you. knex has a great [migration tool](http://knexjs.org/#Migrations) | ||
that we recommend for this job. Check out the [example project](https://github.com/Vincit/objection.js/tree/master/examples/express). | ||
Objection.js uses Promises and coding practices that make it ready for the future. We use Well known | ||
[OOP](https://en.wikipedia.org/wiki/Object-oriented_programming) techniques and ES6 compatible classes and inheritance | ||
in the codebase. You can even use things like ES7 [async/await](http://jakearchibald.com/2014/es7-async-functions/) | ||
using a transpiler such as [Babel](https://babeljs.io/). Check out our [ES6](https://github.com/Vincit/objection.js/tree/master/examples/express-es6) | ||
and [ES7](https://github.com/Vincit/objection.js/tree/master/examples/express-es7) example projects. | ||
# Topics | ||
- [Installation](#installation) | ||
- [Getting started](#getting-started) | ||
- [Query examples](#query-examples) | ||
- [Eager queries](#eager-queries) | ||
- [Eager inserts](#eager-inserts) | ||
- [Transactions](#transactions) | ||
- [Documents](#documents) | ||
- [Validation](#validation) | ||
- [Models](#models) | ||
- [Testing](#testing) | ||
- [API Documentation](http://vincit.github.io/objection.js/Model.html) | ||
- [Recipe book](RECIPES.md) | ||
- [Changelog](#changelog) | ||
# Installation | ||
```sh | ||
npm install knex objection | ||
``` | ||
You also need to install one of the following depending on the database you want to use: | ||
```sh | ||
npm install pg | ||
npm install sqlite3 | ||
npm install mysql | ||
npm install mysql2 | ||
npm install mariasql | ||
``` | ||
# Getting started | ||
To use objection.js all you need to do is [initialize knex](http://knexjs.org/#Installation-node) and give the | ||
connection to objection.js using `Model.knex(knex)`: | ||
```js | ||
var Knex = require('knex'); | ||
var Model = require('objection').Model; | ||
var knex = Knex({ | ||
client: 'postgres', | ||
connection: { | ||
host: '127.0.0.1', | ||
database: 'your_database' | ||
} | ||
}); | ||
Model.knex(knex); | ||
``` | ||
The next step is to create some migrations and models and start using objection.js. The best way to get started is to | ||
use the [example project](https://github.com/Vincit/objection.js/tree/master/examples/express): | ||
```sh | ||
git clone git@github.com:Vincit/objection.js.git objection | ||
cd objection/examples/express | ||
npm install | ||
# We use knex for migrations in this example. | ||
npm install knex -g | ||
knex migrate:latest | ||
npm start | ||
``` | ||
The `express` example project is a simple express server. The `example-requests.sh` file contains a bunch of curl | ||
commands for you to start playing with the REST API. | ||
```sh | ||
cat example-requests.sh | ||
``` | ||
If you are using a newer version of Node and you want to use ES6 features then our | ||
[ES6 version of the example project](https://github.com/Vincit/objection.js/tree/master/examples/express-es6) | ||
is the best place to start. | ||
We also have an [ES7 version of the example project](https://github.com/Vincit/objection.js/tree/master/examples/express-es7) | ||
that uses [Babel](https://babeljs.io/) for ES7 --> ES5 transpiling. | ||
Also check out our [API documentation](http://vincit.github.io/objection.js/Model.html) and [recipe book](RECIPES.md). | ||
# Query examples | ||
The `Person` model used in the examples is defined [here](#models). | ||
All queries are started with one of the [Model](http://vincit.github.io/objection.js/Model.html) methods [query()](http://vincit.github.io/objection.js/Model.html#_P_query), | ||
[$query()](http://vincit.github.io/objection.js/Model.html#Squery) or [$relatedQuery()](http://vincit.github.io/objection.js/Model.html#SrelatedQuery). | ||
All these methods return a [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) instance that can be used just like | ||
a [knex QueryBuilder](http://knexjs.org/#Builder). | ||
Insert a person to the database: | ||
```js | ||
Person | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}) | ||
.then(function (jennifer) { | ||
console.log(jennifer instanceof Person); // --> true | ||
console.log(jennifer.firstName); // --> 'Jennifer' | ||
console.log(jennifer.fullName()); // --> 'Jennifer Lawrence' | ||
}) | ||
.catch(function (err) { | ||
console.log('oh noes'); | ||
}); | ||
``` | ||
```sql | ||
insert into "Person" ("firstName", "lastName") values ('Jennifer', 'Lawrence') | ||
``` | ||
Fetch all persons from the database: | ||
```js | ||
Person | ||
.query() | ||
.then(function (persons) { | ||
console.log(persons[0] instanceof Person); // --> true | ||
console.log('there are', persons.length, 'Persons in total'); | ||
}) | ||
.catch(function (err) { | ||
console.log('oh noes'); | ||
}); | ||
``` | ||
```sql | ||
select * from "Person" | ||
``` | ||
The return value of the `.query()` method is an instance of [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) | ||
that has all the methods a [knex QueryBuilder](http://knexjs.org/#Builder) has. Here is a simple example that uses some of them: | ||
```js | ||
Person | ||
.query() | ||
.where('age', '>', 40) | ||
.andWhere('age', '<', 60) | ||
.andWhere('firstName', 'Jennifer') | ||
.orderBy('lastName') | ||
.then(function (middleAgedJennifers) { | ||
console.log('The last name of the first middle aged Jennifer is'); | ||
console.log(middleAgedJennifers[0].lastName); | ||
}); | ||
``` | ||
```sql | ||
select * from "Person" | ||
where "age" > 40 | ||
and "age" < 60 | ||
and "firstName" = 'Jennifer' | ||
order by "lastName" asc | ||
``` | ||
The next example shows how easy it is to build complex queries: | ||
```js | ||
Person | ||
.query() | ||
.select('Person.*', 'Parent.firstName as parentFirstName') | ||
.join('Person as Parent', 'Person.parentId', 'Parent.id') | ||
.where('Person.age', '<', Person.query().avg('Person.age')) | ||
.whereExists(Animal.query().select(1).whereRef('Person.id', 'Animal.ownerId')) | ||
.orderBy('Person.lastName') | ||
.then(function (persons) { | ||
console.log(persons[0].parentFirstName); | ||
}); | ||
``` | ||
```sql | ||
select "Person".*, "Parent"."firstName" as "parentFirstName" | ||
from "Person" | ||
inner join "Person" as "Parent" on "Person"."parentId" = "Parent"."id" | ||
where "Person"."age" < (select avg("Person"."age") from "Person") | ||
and exists (select 1 from "Animal" where "Person"."id" = "Animal"."ownerId") | ||
order by "Person"."lastName" asc | ||
``` | ||
Update models: | ||
```js | ||
Person | ||
.query() | ||
.patch({lastName: 'Dinosaur'}) | ||
.where('age', '>', 60) | ||
.then(function (numUpdated) { | ||
console.log('all persons over 60 years old are now dinosaurs'); | ||
console.log(numUpdated, 'people were updated');. | ||
}) | ||
.catch(function (err) { | ||
console.log(err.stack); | ||
}); | ||
``` | ||
```sql | ||
update "Person" set "lastName" = 'Dinosaur' where "age" > 60 | ||
``` | ||
The `.patch()` and `.update()` method return the number of updated rows. If you want the freshly updated | ||
model as a result you can use the helper method `.patchAndFetchById()` and `.updateAndFetchById()`. | ||
```js | ||
Person | ||
.query() | ||
.patchAndFetchById(246, {lastName: 'Updated'}) | ||
.then(function (updated) { | ||
console.log(updated.lastName); // --> Updated. | ||
}) | ||
.catch(function (err) { | ||
console.log(err.stack); | ||
}); | ||
``` | ||
```sql | ||
update "Person" set "lastName" = 'Updated' where "id" = 246 | ||
select * from "Person" where "id" = 246 | ||
``` | ||
While the static `.query()` method can be used to create a query to a whole table `.$relatedQuery()` method | ||
can be used to query a single relation. `.$relatedQuery()` returns an instance of [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) | ||
just like the `.query()` method. | ||
```js | ||
var jennifer; | ||
Person | ||
.query() | ||
.where('firstName', 'Jennifer') | ||
.first() | ||
.then(function (person) { | ||
jennifer = person; | ||
return jennifer | ||
.$relatedQuery('pets') | ||
.where('species', 'dog') | ||
.orderBy('name'); | ||
}) | ||
.then(function (jennifersDogs) { | ||
console.log(jennifersDogs[0] instanceof Animal); // --> true | ||
console.log(jennifer.pets === jennifersDogs); // --> true | ||
console.log('Jennifer has', jennifersDogs.length, 'dogs'); | ||
}) | ||
.catch(function (err) { | ||
console.log(err.stack); | ||
}); | ||
``` | ||
```sql | ||
select * from "Person" where "firstName" = 'Jennifer' | ||
select * from "Animal" | ||
where "species" = 'dog' | ||
and "Animal"."ownerId" = 1 | ||
order by "name" asc | ||
``` | ||
Insert a related model: | ||
```js | ||
Person | ||
.query() | ||
.where('id', 1) | ||
.first() | ||
.then(function (person) { | ||
return person.$relatedQuery('pets').insert({name: 'Fluffy'}); | ||
}) | ||
.then(function (fluffy) { | ||
console.log(fluffy.id); | ||
}) | ||
.catch(function (err) { | ||
console.log('something went wrong with finding the person OR inserting the pet'); | ||
console.log(err.stack); | ||
}); | ||
``` | ||
```sql | ||
select * from "Person" where "id" = 1 | ||
insert into "Animal" ("name", "ownerId") values ('Fluffy', 1) | ||
``` | ||
# Eager queries | ||
Okay I said there is no custom DSL but actually we have teeny-tiny one for fetching relations eagerly, as it isn't | ||
something that can be done easily using SQL. In addition to making your life easier, eager queries avoid the select N+1 | ||
problem and provide a great performance. The following examples demonstrate how to use it: | ||
Fetch the `pets` relation for all results of a query: | ||
```js | ||
Person | ||
.query() | ||
.eager('pets') | ||
.then(function (persons) { | ||
// Each person has the `.pets` property populated with Animal objects related | ||
// through `pets` relation. | ||
console.log(persons[0].pets[0].name); | ||
console.log(persons[0].pets[0] instanceof Animal); // --> true | ||
}); | ||
``` | ||
Fetch multiple relations on multiple levels: | ||
```js | ||
Person | ||
.query() | ||
.eager('[pets, children.[pets, children]]') | ||
.then(function (persons) { | ||
// Each person has the `.pets` property populated with Animal objects related | ||
// through `pets` relation. The `.children` property contains the Person's | ||
// children. Each child also has the `pets` and `children` relations eagerly | ||
// fetched. | ||
console.log(persons[0].pets[0].name); | ||
console.log(persons[1].children[2].pets[1].name); | ||
console.log(persons[1].children[2].children[0].name); | ||
}); | ||
``` | ||
Fetch one relation recursively: | ||
```js | ||
Person | ||
.query() | ||
.eager('[pets, children.^]') | ||
.then(function (persons) { | ||
// The children relation is from Person to Person. If we want to fetch the whole | ||
// descendant tree of a person we can just say "fetch this relation recursively" | ||
// using the `.^` notation. | ||
console.log(persons[0].children[0].children[0].children[0].children[0].firstName); | ||
}); | ||
``` | ||
Relations can be filtered using named filters like this: | ||
```js | ||
Person | ||
.query() | ||
.eager('[pets(orderByName, onlyDogs), children(orderByAge).[pets, children]]', { | ||
orderByName: function (builder) { | ||
builder.orderBy('name'); | ||
}, | ||
orderByAge: function (builder) { | ||
builder.orderBy('age') | ||
}, | ||
onlyDogs: function (builder) { | ||
builder.where('species', 'dog') | ||
} | ||
}) | ||
.then(function (persons) { | ||
console.log(persons[0].children[0].pets[0].name); | ||
console.log(persons[0].children[0].movies[0].id); | ||
}); | ||
``` | ||
The expressions can be arbitrarily deep. See the full description [here](http://vincit.github.io/objection.js/RelationExpression.html). | ||
Because the eager expressions are strings they can be easily passed for example as a query parameter of an HTTP | ||
request. However, allowing the client to pass expressions like this without any limitations is not very secure. | ||
Therefore the [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) has the `.allowEager` method. | ||
allowEager can be used to limit the allowed eager expression to a certain subset. Like this: | ||
```js | ||
expressApp.get('/persons', function (req, res, next) { | ||
Person | ||
.query() | ||
.allowEager('[pets, children.pets]') | ||
.eager(req.query.eager) | ||
.then(function (persons) { res.send(persons); }) | ||
.catch(next); | ||
}); | ||
``` | ||
The example above allows `req.query.eager` to be one of: | ||
* `'pets'` | ||
* `'children'` | ||
* `'children.pets'` | ||
* `'[pets, children]'` | ||
* `'[pets, children.pets]'`. | ||
Examples of failing eager expressions are: | ||
* `'movies'` | ||
* `'children.children'` | ||
* `'[pets, children.children]'` | ||
* `'notEvenAnExistingRelation'`. | ||
In addition to the `.eager` method, relations can be fetched using the `loadRelated` and `$loadRelated` methods of | ||
[Model](http://vincit.github.io/objection.js/Model.html). | ||
# Eager inserts | ||
Arbitrary relation trees can be inserted using the [insertWithRelated()](http://vincit.github.io/objection.js/QueryBuilder.html#insertWithRelated) | ||
method: | ||
```js | ||
Person | ||
.query() | ||
.insertWithRelated({ | ||
firstName: 'Sylvester', | ||
lastName: 'Stallone', | ||
children: [{ | ||
firstName: 'Sage', | ||
lastName: 'Stallone', | ||
pets: [{ | ||
name: 'Fluffy', | ||
species: 'dog' | ||
}] | ||
}] | ||
}); | ||
``` | ||
The query above will insert 'Sylvester', 'Sage' and 'Fluffy' into db and create relationships between them as defined | ||
in the `relationMappings` of the models. Technically `insertWithRelated` builds a dependency graph from the object | ||
tree and inserts the models that don't depend on any other models until the whole tree is inserted. | ||
If you need to refer to the same model in multiple places you can use the special properties `#id` and `#ref` like this: | ||
```js | ||
Person | ||
.query() | ||
.insertWithRelated([{ | ||
firstName: 'Jennifer', | ||
lastName: 'Lawrence', | ||
movies: [{ | ||
"#id": 'silverLiningsPlaybook' | ||
name: 'Silver Linings Playbook', | ||
duration: 122 | ||
}] | ||
}, { | ||
firstName: 'Bradley', | ||
lastName: 'Cooper', | ||
movies: [{ | ||
"#ref": 'silverLiningsPlaybook' | ||
}] | ||
}]); | ||
``` | ||
The query above will insert only one movie (the 'Silver Linings Playbook') but both 'Jennifer' and 'Bradley' will have | ||
the movie related to them through the many-to-many relation `movies`. The `#id` can be any string. There are no format | ||
or length requirements for them. It is quite easy to create circular dependencies using `#id` and `#ref`. Luckily | ||
`insertWithRelated` detects them and rejects the query with a clear error message. | ||
You can refer to the properties of other models anywhere in the graph using expressions of format `#ref{<id>.<property>}` | ||
as long as the reference doesn't create a circular dependency. For example: | ||
```js | ||
Person | ||
.query() | ||
.insertWithRelated([{ | ||
"#id": 'jenniLaw', | ||
firstName: 'Jennifer', | ||
lastName: 'Lawrence', | ||
pets: [{ | ||
name: "I am the dog of #ref{jenniLaw.firstName} whose id is #ref{jenniLaw.id}", | ||
species: 'dog' | ||
}] | ||
}]); | ||
``` | ||
The query above will insert a pet named `I am the dog of Jennifer whose id is 523` for Jennifer. If `#ref{}` is used | ||
within a string, the references are replaced with the referred values inside the string. If the reference string | ||
contains nothing but the reference, the referred value is copied to it's place preserving its type. | ||
See the [allowInsert](http://vincit.github.io/objection.js/QueryBuilder.html#allowInsert) method if you need to limit | ||
which relations can be inserted using `insertWithRelated` method to avoid security issues. | ||
By the way, if you are using Postgres the inserts are done in batches for maximum performance. | ||
# Transactions | ||
There are two ways to use transactions in objection.js | ||
1. [Transaction callback](#transaction-callback) | ||
2. [Transaction object](#transaction-object) | ||
## Transaction callback | ||
The first way to work with transactions is to perform all operations inside one callback using the | ||
[objection.transaction](http://vincit.github.io/objection.js/global.html#transaction) function. The beauty of this | ||
method is that you don't need to pass a transaction object to each query explicitly as long as you start all queries | ||
using the bound model classes that are passed to the transaction callback as arguments. | ||
Transactions are started by calling the [objection.transaction](http://vincit.github.io/objection.js/global.html#transaction) | ||
function. Give all the models you want to use in the transaction as parameters to the `transaction` function. The model | ||
classes are bound to a newly created transaction and passed to the callback function. Inside this callback, all queries | ||
started through them take part in the same transaction. | ||
The transaction is committed if the returned Promise is resolved successfully. If the returned Promise is rejected | ||
the transaction is rolled back. | ||
```js | ||
objection.transaction(Person, Animal, function (Person, Animal) { | ||
return Person | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}) | ||
.then(function () { | ||
return Animal | ||
.query() | ||
.insert({name: 'Scrappy'}); | ||
}); | ||
}).then(function (scrappy) { | ||
console.log('Jennifer and Scrappy were successfully inserted'); | ||
}).catch(function (err) { | ||
console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted'); | ||
}); | ||
``` | ||
You only need to give the `transaction` function the model classes you use explicitly. All the related model classes | ||
are implicitly bound to the same transaction. | ||
```js | ||
objection.transaction(Person, function (Person) { | ||
return Person | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}) | ||
.then(function (jennifer) { | ||
// This creates a query using the `Animal` model class but we | ||
// don't need to give `Animal` as one of the arguments to the | ||
// transaction function. | ||
return jennifer | ||
.$relatedQuery('pets') | ||
.insert({name: 'Scrappy'}); | ||
}); | ||
}).then(function (scrappy) { | ||
console.log('Jennifer and Scrappy were successfully inserted'); | ||
}).catch(function (err) { | ||
console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted'); | ||
}); | ||
``` | ||
The only way you can mess up with the transactions is if you _explicitly_ start a query using a model class that is not | ||
bound to the transaction: | ||
```js | ||
var Person = require('./models/Person'); | ||
var Animal = require('./models/Animal'); | ||
objection.transaction(Person, function (Person) { | ||
return Person | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}) | ||
.then(function (jennifer) { | ||
// OH NO! This query is executed outside the transaction | ||
// since the `Animal` class is not bound to the transaction. | ||
return Animal | ||
.query() | ||
.insert({name: 'Scrappy'}); | ||
}); | ||
}); | ||
``` | ||
## Transaction object | ||
The second way to use transactions is to express the transaction as an object and bind model classes to the transaction | ||
when you use them. This way is more convenient when you need to pass the transaction to functions and services. | ||
The transaction object can be created using the [objection.transaction.start](http://vincit.github.io/objection.js/global.html#start) | ||
method. You need to remember to call either the `commit` or `rollback` method of the transaction object. | ||
```js | ||
// You need to pass some model (any model with a knex connection) | ||
// or the knex connection itself to the start method. | ||
var trx; | ||
objection.transaction.start(Person).then(function (transaction) { | ||
trx = transaction; | ||
return Person | ||
.bindTransaction(trx) | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}); | ||
}).then(function (jennifer) { | ||
// jennifer was created using a bound model class. Therefore | ||
// all queries started through it automatically take part in | ||
// the same transaction. We don't need to bind anything here. | ||
return jennifer | ||
.$relatedQuery('pets') | ||
.insert({name: 'Fluffy'}); | ||
}).then(function () { | ||
return Movie | ||
.bindTransaction(trx) | ||
.query() | ||
.where('name', 'ilike', '%forrest%'); | ||
}).then(function (movies) { | ||
console.log(movies); | ||
return trx.commit(); | ||
}).catch(function () { | ||
return trx.rollback(); | ||
}); | ||
``` | ||
To understand what is happening in the above example you need to know how `bindTransaction` method works. | ||
`bindTransaction` actually returns an anonymous subclass of the model class you call it for and sets the | ||
transaction's database connection as that class's database connection. This way all queries started through | ||
that anonymous class use the same connection and take part in the same transaction. If we didn't create a | ||
subclass and just set the original model's database connection, all query chains running in parallel would | ||
suddenly jump into the same transaction and things would go terribly wrong. | ||
Now that you have an idea how the `bindTransaction` works you should see that the previous example could | ||
also be implemented like this: | ||
```js | ||
var BoundPerson; | ||
var BoundMovie; | ||
objection.transaction.start(Person).then(function (transaction) { | ||
BoundPerson = Person.bindTransaction(transaction); | ||
BoundMovie = Movie.bindTransaction(transaction); | ||
return BoundPerson | ||
.query() | ||
.insert({firstName: 'Jennifer', lastName: 'Lawrence'}); | ||
}).then(function (jennifer) { | ||
return jennifer | ||
.$relatedQuery('pets') | ||
.insert({name: 'Fluffy'}); | ||
}).then(function () { | ||
return BoundMovie | ||
.query() | ||
.where('name', 'ilike', '%forrest%'); | ||
}).then(function (movies) { | ||
console.log(movies); | ||
return BoundPerson.knex().commit(); | ||
}).catch(function () { | ||
return BoundPerson.knex().rollback(); | ||
}); | ||
``` | ||
which is pretty much what the [objection.transaction](http://vincit.github.io/objection.js/global.html#transaction) | ||
function does. | ||
# Documents | ||
Objection.js makes it easy to store non-flat documents as table rows. All properties of a model that are marked as | ||
objects or arrays in the model's `jsonSchema` are automatically converted to JSON strings in the database and | ||
back to objects when read from the database. The database columns for the object properties can be normal | ||
text columns. Postgresql has the `json` and `jsonb` data types that can be used instead for better performance | ||
and possibility to [make queries](http://www.postgresql.org/docs/9.4/static/functions-json.html) to the documents. | ||
The `address` property of the Person model is defined as an object in the [Person.jsonSchema](#models): | ||
```js | ||
Person | ||
.query() | ||
.insert({ | ||
firstName: 'Jennifer', | ||
lastName: 'Lawrence', | ||
age: 24, | ||
address: { | ||
street: 'Somestreet 10', | ||
zipCode: '123456', | ||
city: 'Tampere' | ||
} | ||
}) | ||
.then(function (jennifer) { | ||
console.log(jennifer.address.city); // --> Tampere | ||
return Person.query().where('id', jennifer.id); | ||
}) | ||
.then(function (jenniferFromDb) { | ||
console.log(jenniferFromDb.address.city); // --> Tampere | ||
}) | ||
.catch(function (err) { | ||
console.log('oh noes'); | ||
}); | ||
``` | ||
# Validation | ||
[JSON schema](http://json-schema.org/) validation can be enabled by setting the [jsonSchema](#models) property | ||
of a model class. The validation is ran each time a `Model` instance is created. For example all these will trigger | ||
the validation: | ||
```js | ||
Person.fromJson({firstName: 'jennifer', lastName: 'Lawrence'}); | ||
Person.query().insert({firstName: 'jennifer', lastName: 'Lawrence'}); | ||
Person.query().update({firstName: 'jennifer', lastName: 'Lawrence'}).where('id', 10); | ||
// Patch operation ignores the `required` property of the schema and only validates the | ||
// given properties. This allows a subset of model's properties to be updated. | ||
Person.query().patch({age: 24}).where('age', '<', 24); | ||
``` | ||
You rarely need to call [$validate](http://vincit.github.io/objection.js/Model.html#Svalidate) method explicitly, but you | ||
can do it when needed. If validation fails a [ValidationError](http://vincit.github.io/objection.js/ValidationError.html) | ||
will be thrown. Since we use Promises, this usually means that a promise will be rejected with an instance of | ||
`ValidationError`. | ||
```js | ||
Person.query().insert({firstName: 'jennifer'}).catch(function (err) { | ||
console.log(err instanceof objection.ValidationError); // --> true | ||
console.log(err.data); // --> {lastName: 'required property missing'} | ||
}); | ||
``` | ||
See [the recipe book](https://github.com/Vincit/objection.js/blob/master/RECIPES.md#custom-validation) for instructions | ||
if you want to use some other validation library. | ||
# Models | ||
Models are created by inheriting from the [Model](http://vincit.github.io/objection.js/Model.html) base class. | ||
In objection.js the inheritance is done as transparently as possible. There is no custom Class abstraction making you | ||
wonder what the hell is happening. Just plain old ugly javascript inheritance. | ||
## Minimal model | ||
A working model with minimal amount of code: | ||
```js | ||
var Model = require('objection').Model; | ||
function MinimalModel() { | ||
Model.apply(this, arguments); | ||
} | ||
// Inherit `Model`. This does the basic prototype inheritance but also | ||
// inherits all the static methods and properties like `Model.query()` | ||
// and `Model.fromJson()`. This is consistent with ES6 class inheritance. | ||
Model.extend(MinimalModel); | ||
// After the js class boilerplate, all you need to do is set the table name. | ||
MinimalModel.tableName = 'SomeTableName'; | ||
module.exports = MinimalModel; | ||
``` | ||
## A model with custom methods, json schema validation and relations | ||
This is the model used in the examples: | ||
```js | ||
var Model = require('objection').Model; | ||
function Person() { | ||
Model.apply(this, arguments); | ||
} | ||
Model.extend(Person); | ||
module.exports = Person; | ||
// You can add custom functionality to Models just as you would | ||
// to any javascript class. | ||
Person.prototype.fullName = function () { | ||
return this.firstName + ' ' + this.lastName; | ||
}; | ||
// Table name is the only required property. | ||
Person.tableName = 'Person'; | ||
// Optional JSON schema. This is not the database schema! Nothing is generated | ||
// based on this. This is only used for validation. Whenever a model instance | ||
// is created it is checked against this schema. http://json-schema.org/. | ||
Person.jsonSchema = { | ||
type: 'object', | ||
required: ['firstName', 'lastName'], | ||
properties: { | ||
id: {type: 'integer'}, | ||
parentId: {type: ['integer', 'null']}, | ||
firstName: {type: 'string', minLength: 1, maxLength: 255}, | ||
lastName: {type: 'string', minLength: 1, maxLength: 255}, | ||
age: {type: 'number'}, | ||
address: { | ||
type: 'object', | ||
properties: { | ||
street: {type: 'string'}, | ||
city: {type: 'string'}, | ||
zipCode: {type: 'string'} | ||
} | ||
} | ||
} | ||
}; | ||
// This object defines the relations to other models. | ||
Person.relationMappings = { | ||
pets: { | ||
relation: Model.OneToManyRelation, | ||
// The related model. This can be either a Model subclass constructor or an | ||
// absolute file path to a module that exports one. We use the file path version | ||
// here to prevent require loops. | ||
modelClass: __dirname + '/Animal', | ||
join: { | ||
from: 'Person.id', | ||
to: 'Animal.ownerId' | ||
} | ||
}, | ||
movies: { | ||
relation: Model.ManyToManyRelation, | ||
modelClass: __dirname + '/Movie', | ||
join: { | ||
from: 'Person.id', | ||
// ManyToMany relation needs the `through` object to describe the join table. | ||
through: { | ||
from: 'Person_Movie.personId', | ||
to: 'Person_Movie.movieId' | ||
}, | ||
to: 'Movie.id' | ||
} | ||
}, | ||
parent: { | ||
relation: Model.OneToOneRelation, | ||
modelClass: Person, | ||
join: { | ||
from: 'Person.parentId', | ||
to: 'Person.id' | ||
} | ||
}, | ||
children: { | ||
relation: Model.OneToManyRelation, | ||
modelClass: Person, | ||
join: { | ||
from: 'Person.id', | ||
to: 'Person.parentId' | ||
} | ||
} | ||
}; | ||
``` | ||
# Testing | ||
To run the tests, all you need to do is configure the databases and run `npm test`. Check out | ||
[this](https://github.com/Vincit/objection.js/blob/master/tests/integration/index.js) file for the | ||
test database configurations. If you don't want to run the tests against all databases you can | ||
just comment out configurations from the `testDatabaseConfigs` list. | ||
# Changelog | ||
## 0.3.3 | ||
#### What's new | ||
* fix regression: QueryBuilder.from is broken. | ||
## 0.3.2 | ||
#### What's new | ||
* Improved relation expression whitespace handling. | ||
## 0.3.1 | ||
#### What's new | ||
* `whereJson*` methods can now be used inside functions given to `where` methods. | ||
* Added multiple missing knex methods to `QueryBuilder`. | ||
## 0.3.0 | ||
#### What's new | ||
* [insertWithRelated](http://vincit.github.io/objection.js/QueryBuilder.html#insertWithRelated) method for | ||
inserting model trees | ||
* [insertAndFetch](http://vincit.github.io/objection.js/QueryBuilder.html#insertAndFetch), | ||
[updateAndFetchById](http://vincit.github.io/objection.js/QueryBuilder.html#updateAndFetchById) and | ||
[patchAndFetchById](http://vincit.github.io/objection.js/QueryBuilder.html#patchAndFetchById) helper methods | ||
* Filters for [eager expressions](#eager-queries) | ||
* [New alternative way to use transactions](#transaction-object) | ||
* Many performance updates related to cloning, serializing and deserializing model trees. | ||
#### Breaking changes | ||
* QueryBuilder methods `update`, `patch` and `delete` now return the number of affected rows. | ||
The new methods `updateAndFetchById` and `patchAndFetchById` may help with the migration | ||
* `modelInstance.$query()` instance method now returns a single model instead of an array | ||
* Removed `Model.generateId()` method. `$beforeInsert` can be used instead | ||
## 0.2.8 | ||
#### What's new | ||
* ES6 inheritance support | ||
* generator function support for transactions | ||
* traverse,pick and omit methods for Model and QueryBuilder | ||
* bugfix: issue #38 | ||
## 0.2.7 | ||
#### What's new | ||
* bugfix: fix #37 also for `$query()`. | ||
* Significant `toJson`/`fromJson` performance boost. | ||
## 0.2.6 | ||
#### What's new | ||
* bugfix: fix regression bug that broke dumpSql. | ||
## 0.2.5 | ||
#### What's new | ||
* bugfix: fix regression bug that prevented values assigned to `this` in `$before` callbacks from getting into | ||
the actual database query | ||
## 0.2.4 | ||
#### What's new | ||
* bugfix: many-to-many relations didn't work correctly with a snake_case to camelCase conversion | ||
in the related model class. | ||
## 0.2.3 | ||
#### What's new | ||
* Promise constructor is now exposed through `require('objection').Promise`. | ||
## 0.2.2 | ||
#### What's new | ||
* $beforeUpdate, $afterUpdate, $beforeInsert etc. are now asynchronous and you can return promises from them. | ||
* Added `Model.fn()` shortcut to `knex.fn`. | ||
* Added missing `asCallback` and `nodeify` methods for `QueryBuilder`. | ||
## 0.2.1 | ||
#### What's new | ||
* bugfix: Chaining `insert` with `returning` now returns all listed columns. | ||
## 0.2.0 | ||
#### What's new | ||
* New name `objection.js`. | ||
* `$beforeInsert`, `$afterInsert`, `$beforeUpdate` and `$afterUpdate` hooks for `Model`. | ||
* Postgres jsonb query methods: `whereJsonEquals`, `whereJsonSupersetOf`, `whereJsonSubsetOf` and friends. | ||
* `whereRef` query method. | ||
* Expose `knex.raw()` through `Model.raw()`. | ||
* Expose `knex.client.formatter()` through `Model.formatter()`. | ||
* `QueryBuilder` can be used to make sub queries just like knex's `QueryBuilder`. | ||
* Possibility to use a custom `QueryBuilder` subclass by overriding `Model.QueryBuilder`. | ||
* Filter queries/objects for relations. | ||
* A pile of bug fixes. | ||
#### Breaking changes | ||
* Project was renamed to objection.js. Migrate simply by replacing `moron` with `objection`. | ||
## 0.1.0 | ||
First release. | ||
# [Objection.js](http://vincit.github.io/objection.js) |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
611086
47
18912
5
17
4
6
1
+ Addedbabel-runtime@^6.3.19
+ Addedbabel-runtime@6.26.0(transitive)
+ Addedcore-js@2.6.12(transitive)
+ Addedregenerator-runtime@0.11.1(transitive)