objection
Version:
An SQL-friendly ORM for Node.js
1,231 lines (1,044 loc) • 32.8 kB
JavaScript
import _ from 'lodash';
import Promise from 'bluebird';
import queryBuilderOperation from './decorators/queryBuilderOperation';
import QueryBuilderContext from './QueryBuilderContext';
import RelationExpression from './RelationExpression';
import QueryBuilderBase from './QueryBuilderBase';
import ValidationError from '../model/ValidationError';
import FindOperation from './operations/FindOperation';
import DeleteOperation from './operations/DeleteOperation';
import UpdateOperation from './operations/UpdateOperation';
import InsertOperation from './operations/InsertOperation';
import InsertGraphAndFetchOperation from './operations/InsertGraphAndFetchOperation';
import InsertAndFetchOperation from './operations/InsertAndFetchOperation';
import UpdateAndFetchOperation from './operations/UpdateAndFetchOperation';
import QueryBuilderOperation from './operations/QueryBuilderOperation';
import JoinRelationOperation from './operations/JoinRelationOperation';
import InsertGraphOperation from './operations/InsertGraphOperation';
import RunBeforeOperation from './operations/RunBeforeOperation';
import RunAfterOperation from './operations/RunAfterOperation';
import OnBuildOperation from './operations/OnBuildOperation';
import SelectOperation from './operations/SelectOperation';
import EagerOperation from './operations/EagerOperation';
export default class QueryBuilder extends QueryBuilderBase {
constructor(modelClass) {
super(modelClass.knex(), QueryBuilderContext);
this._modelClass = modelClass;
this._explicitRejectValue = null;
this._explicitResolveValue = null;
this._eagerExpression = null;
this._eagerFilterExpressions = [];
this._allowedEagerExpression = null;
this._allowedInsertExpression = null;
this._findOperationOptions = {};
this._eagerOperationOptions = {};
this._findOperationFactory = findOperationFactory;
this._insertOperationFactory = insertOperationFactory;
this._updateOperationFactory = updateOperationFactory;
this._patchOperationFactory = patchOperationFactory;
this._relateOperationFactory = relateOperationFactory;
this._unrelateOperationFactory = unrelateOperationFactory;
this._deleteOperationFactory = deleteOperationFactory;
this._eagerOperationFactory = modelClass.defaultEagerAlgorithm;
}
/**
* @param {Model} modelClass
* @returns {QueryBuilder}
*/
static forClass(modelClass) {
return new this(modelClass);
}
/**
* @param {QueryBuilderBase} query
* @returns {QueryBuilder}
*/
childQueryOf(query) {
if (query) {
this.internalContext(query.internalContext());
}
return this;
}
/**
* @param {Error} error
* @returns {QueryBuilder}
*/
reject(error) {
this._explicitRejectValue = error;
return this;
}
/**
* @param {*} value
* @returns {QueryBuilder}
*/
resolve(value) {
this._explicitResolveValue = value;
return this;
}
/**
* @returns {boolean}
*/
isExecutable() {
const hasExecutor = !!this._queryExecutorOperation();
return !this._explicitRejectValue && !this._explicitResolveValue && !hasExecutor;
}
/**
* @param {function(*, QueryBuilder)} runBefore
* @returns {QueryBuilder}
*/
runBefore(runBefore) {}
/**
* @param {function(QueryBuilder)} onBuild
* @returns {QueryBuilder}
*/
onBuild(onBuild) {}
/**
* @param {function(Model|Array.<Model>, QueryBuilder)} runAfter
* @returns {QueryBuilder}
*/
runAfter(runAfter) {}
/**
* @param {function(QueryBuilder):EagerOperation} algorithm
* @param {object=} eagerOptions
* @returns {QueryBuilder}
*/
eagerAlgorithm(algorithm, eagerOptions) {
this.eagerOperationFactory(algorithm);
if (eagerOptions) {
this.eagerOptions(eagerOptions);
}
return this;
}
/**
* @param {function(QueryBuilder):EagerOperation} factory
* @returns {QueryBuilder}
*/
eagerOperationFactory(factory) {
this._eagerOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
findOperationFactory(factory) {
this._findOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
insertOperationFactory(factory) {
this._insertOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
updateOperationFactory(factory) {
this._updateOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
patchOperationFactory(factory) {
this._patchOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
deleteOperationFactory(factory) {
this._deleteOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
relateOperationFactory(factory) {
this._relateOperationFactory = factory;
return this;
}
/**
* @param {function(QueryBuilder):QueryBuilderOperation} factory
* @returns {QueryBuilder}
*/
unrelateOperationFactory(factory) {
this._unrelateOperationFactory = factory;
return this;
}
/**
* @param {string|RelationExpression} exp
* @param {Object.<string, function(QueryBuilder)>=} filters
* @returns {QueryBuilder}
*/
eager(exp, filters) {
this._eagerExpression = exp || null;
if (_.isString(this._eagerExpression)) {
this._eagerExpression = RelationExpression.parse(this._eagerExpression);
}
if (_.isObject(filters)) {
this._eagerExpression.filters = filters;
}
checkEager(this);
return this;
}
/**
* @param {string|RelationExpression} exp
* @param {Object.<string, function(QueryBuilder)>=} filters
* @returns {QueryBuilder}
*/
mergeEager(exp, filters) {
if (!this._eagerExpression) {
return this.eager(exp, filters);
}
const expr = RelationExpression.parse(exp);
if (_.isObject(filters)) {
expr.filters = filters;
}
this._eagerExpression = this._eagerExpression.merge(expr);
checkEager(this);
return this;
}
/**
* @param {string|RelationExpression} exp
* @returns {QueryBuilder}
*/
allowEager(exp) {
this._allowedEagerExpression = exp || null;
if (_.isString(this._allowedEagerExpression)) {
this._allowedEagerExpression = RelationExpression.parse(this._allowedEagerExpression);
}
checkEager(this);
return this;
}
/**
* @param {string|RelationExpression} path
* @param {function(QueryBuilder)} modifier
* @returns {QueryBuilder}
*/
modifyEager(path, modifier) {
this._eagerFilterExpressions.push({
path: path,
filter: modifier
});
return this;
}
filterEager(...args) {
return this.modifyEager(...args);
}
/**
* @param {string|RelationExpression} exp
* @returns {QueryBuilder}
*/
allowInsert(exp) {
this._allowedInsertExpression = exp || null;
if (_.isString(this._allowedInsertExpression)) {
this._allowedInsertExpression = RelationExpression.parse(this._allowedInsertExpression);
}
return this;
}
/**
* @param {object} opt
* @return {QueryBuilder}
*/
eagerOptions(opt) {
this._eagerOperationOptions = Object.assign({}, this._eagerOperationOptions, opt);
const opIdx = this.indexOfOperation(EagerOperation);
if (opIdx !== -1) {
this._operations[opIdx] = this._operations[opIdx].clone({
opt: this._eagerOperationOptions
});
}
return this;
}
/**
* @param {object} opt
* @return {QueryBuilder}
*/
findOptions(opt) {
this._findOperationOptions = Object.assign({}, this._findOperationOptions, opt);
const opIdx = this.indexOfOperation(FindOperation);
if (opIdx !== -1) {
this._operations[opIdx] = this._operations[opIdx].clone({
opt: this._findOperationOptions
});
}
return this;
}
/**
* @returns {Constructor.<Model>}
*/
modelClass() {
return this._modelClass;
}
/**
* @returns {boolean}
*/
isFindQuery() {
return !_.some(this._operations, method => method.isWriteOperation) && !this._explicitRejectValue;
}
/**
* @returns {string}
*/
toString() {
return this.build().toString();
}
/**
* @returns {string}
*/
toSql() {
return this.toString();
}
/**
* @returns {QueryBuilder}
*/
clone() {
const builder = new this.constructor(this._modelClass);
this.baseCloneInto(builder);
builder._explicitRejectValue = this._explicitRejectValue;
builder._explicitResolveValue = this._explicitResolveValue;
builder._eagerExpression = this._eagerExpression;
builder._eagerFilterExpressions = this._eagerFilterExpressions.slice();
builder._allowedEagerExpression = this._allowedEagerExpression;
builder._allowedInsertExpression = this._allowedInsertExpression;
builder._findOperationOptions = this._findOperationOptions;
builder._eagerOperationOptions = this._eagerOperationOptions;
builder._findOperationFactory = this._findOperationFactory;
builder._insertOperationFactory = this._insertOperationFactory;
builder._updateOperationFactory = this._updateOperationFactory;
builder._patchOperationFactory = this._patchOperationFactory;
builder._relateOperationFactory = this._relateOperationFactory;
builder._unrelateOperationFactory = this._unrelateOperationFactory;
builder._deleteOperationFactory = this._deleteOperationFactory;
builder._eagerOperationFactory = this._eagerOperationFactory;
return builder;
}
/**
* @returns {QueryBuilder}
*/
clearEager() {
this._eagerExpression = null;
this._eagerFilterExpressions = [];
return this;
}
/**
* @returns {QueryBuilder}
*/
clearReject() {
this._explicitRejectValue = null;
return this;
}
/**
* @returns {QueryBuilder}
*/
clearResolve() {
this._explicitResolveValue = null;
return this;
}
/**
* @param {function=} successHandler
* @param {function=} errorHandler
* @returns {Promise}
*/
then(successHandler, errorHandler) {
var promise = this.execute();
return promise.then.apply(promise, arguments);
}
/**
* @param {function} mapper
* @returns {Promise}
*/
map(mapper) {
var promise = this.execute();
return promise.map.apply(promise, arguments);
}
/**
* @param {function} errorHandler
* @returns {Promise}
*/
catch(errorHandler) {
var promise = this.execute();
return promise.catch.apply(promise, arguments);
}
/**
* @param {*} returnValue
* @returns {Promise}
*/
return(returnValue) {
var promise = this.execute();
return promise.return.apply(promise, arguments);
}
/**
* @param {*} context
* @returns {Promise}
*/
bind(context) {
var promise = this.execute();
return promise.bind.apply(promise, arguments);
}
/**
* @param {function} callback
* @returns {Promise}
*/
asCallback(callback) {
var promise = this.execute();
return promise.asCallback.apply(promise, arguments);
}
/**
* @param {function} callback
* @returns {Promise}
*/
nodeify(callback) {
var promise = this.execute();
return promise.nodeify.apply(promise, arguments);
}
/**
* @returns {Promise}
*/
resultSize() {
const knex = this.knex();
// orderBy is useless here and it can make things a lot slower (at least with postgresql 9.3).
// Remove it from the count query. We also remove the offset and limit
let query = this.clone().clear(/orderBy|offset|limit/).build();
let rawQuery = knex.raw(query).wrap('(', ') as temp');
let countQuery = knex.count('* as count').from(rawQuery);
return countQuery.then(result => result[0] ? result[0].count : 0);
}
/**
* @param {number} page
* @param {number} pageSize
* @returns {QueryBuilder}
*/
page(page, pageSize) {
return this.range(page * pageSize, (page + 1) * pageSize - 1);
}
/**
* @param {number} start
* @param {number} end
* @returns {QueryBuilder}
*/
range(start, end) {
let resultSizePromise;
return this
.limit(end - start + 1)
.offset(start)
.runBefore(() => {
// Don't return the promise so that it is executed
// in parallel with the actual query.
resultSizePromise = this.resultSize();
return null;
})
.runAfter(results => {
// Now that the actual query is finished, wait until the
// result size has been calculated.
return Promise.all([results, resultSizePromise]);
})
.runAfter(arr => {
return {
results: arr[0],
total: _.parseInt(arr[1])
};
});
};
/**
* @returns {knex.QueryBuilder}
*/
build() {
// Take a clone so that we don't modify this instance during build.
const builder = this.clone();
if (builder.isFindQuery()) {
// If no write operations have been called at this point this query is a
// find query and we need to call the custom find implementation.
builder._callFindOperation();
}
if (builder._eagerExpression) {
builder._callEagerFetchOperation();
}
// We need to build the builder even if a query executor operation
// has been called so that the onBuild hooks get called.
const knexBuilder = build(builder);
const queryExecutorOperation = builder._queryExecutorOperation();
if (queryExecutorOperation) {
// If the query executor is set, we build the builder that it returns.
return queryExecutorOperation.queryExecutor(builder).build();
} else {
return knexBuilder;
}
}
/**
* @returns {Promise}
*/
execute() {
// Take a clone so that we don't modify this instance during execution.
let builder = this.clone();
let promiseCtx = {builder: builder};
let promise = Promise.bind(promiseCtx);
let context = builder.context() || {};
let internalContext = builder.internalContext();
if (builder.isFindQuery()) {
// If no write operations have been called at this point this query is a
// find query and we need to call the custom find implementation.
builder._callFindOperation();
}
if (builder._eagerExpression) {
builder._callEagerFetchOperation();
}
promise = chainBeforeOperations(promise, builder._operations);
promise = chainHooks(promise, context.runBefore);
promise = chainHooks(promise, internalContext.runBefore);
promise = chainBeforeInternalOperations(promise, builder._operations);
// Resolve all before hooks before building and executing the query
// and the rest of the hooks.
return promise.then(function () {
const promiseCtx = this;
const builder = promiseCtx.builder;
let promise = null;
let knexBuilder = build(builder);
let queryExecutorOperation = builder._queryExecutorOperation();
if (builder._explicitRejectValue) {
promise = Promise.reject(builder._explicitRejectValue).bind(promiseCtx);
} else if (builder._explicitResolveValue) {
promise = Promise.resolve(builder._explicitResolveValue).bind(promiseCtx);
} else if (queryExecutorOperation) {
promise = queryExecutorOperation.queryExecutor(builder).bind(promiseCtx);
} else {
promise = knexBuilder.bind(promiseCtx);
promise = chainRawResultOperations(promise, builder._operations);
promise = promise.then(createModels);
}
promise = chainAfterQueryOperations(promise, builder._operations);
promise = chainAfterInternalOperations(promise, builder._operations);
promise = chainHooks(promise, context.runAfter);
promise = chainHooks(promise, internalContext.runAfter);
promise = chainAfterOperations(promise, builder._operations);
return promise;
});
}
/**
* @private
* @returns {QueryBuilderOperation}
*/
_queryExecutorOperation() {
for (let i = 0, l = this._operations.length; i < l; ++i) {
const op = this._operations[i];
if (op.hasQueryExecutor()) {
return op;
}
}
return null;
}
/**
* @private
*/
_callFindOperation() {
if (!this.has(FindOperation)) {
const operation = this._findOperationFactory(this);
operation.opt = _.merge(operation.opt,
this._findOperationOptions
);
this.callQueryBuilderOperation(operation, [], /* pushFront = */ true);
}
}
/**
* @private
*/
_callEagerFetchOperation() {
if (!this.has(EagerOperation) && this._eagerExpression) {
const operation = this._eagerOperationFactory(this);
operation.opt = _.merge(operation.opt,
this._modelClass.defaultEagerOptions,
this._eagerOperationOptions
);
this.callQueryBuilderOperation(operation, [
this._eagerExpression,
this._eagerFilterExpressions
]);
}
}
/**
* @param {string} propertyName
* @returns {QueryBuilder}
*/
pluck(propertyName) {
return this.runAfter(result => {
if (_.isArray(result)) {
return _.map(result, propertyName);
} else {
return result;
}
});
}
/**
* @returns {QueryBuilder}
*/
first() {
return this.runAfter(result => {
if (Array.isArray(result)) {
return result[0];
} else {
return result;
}
});
}
/**
* @returns {boolean}
*/
hasSelection(selection) {
const table = this.modelClass().tableName;
let noSelectStatements = true;
for (let i = 0, l = this._operations.length; i < l; ++i) {
const op = this._operations[i];
if (op instanceof SelectOperation) {
noSelectStatements = false;
if (op.hasSelection(table, selection)) {
return true;
}
}
}
if (noSelectStatements) {
// Implicit `select *`.
return true;
} else {
return false;
}
}
/**
* @param {Constructor.<Model>=} modelClass
* @param {function(Model, Model, string)} traverser
* @returns {QueryBuilder}
*/
traverse(modelClass, traverser) {
if (_.isUndefined(traverser)) {
traverser = modelClass;
modelClass = null;
}
return this.runAfter(result => {
this._modelClass.traverse(modelClass, result, traverser);
return result;
});
}
/**
* @param {Constructor.<Model>=} modelClass
* @param {Array.<string>} properties
* @returns {QueryBuilder}
*/
pick(modelClass, properties) {
if (_.isUndefined(properties)) {
properties = modelClass;
modelClass = null;
}
properties = _.reduce(properties, (obj, prop) => {
obj[prop] = true;
return obj;
}, {});
return this.traverse(modelClass, model => {
model.$pick(properties);
});
}
/**
* @param {Constructor.<Model>=} modelClass
* @param {Array.<string>} properties
* @returns {QueryBuilder}
*/
omit(modelClass, properties) {
if (_.isUndefined(properties)) {
properties = modelClass;
modelClass = null;
}
// Turn the properties into a hash for performance.
properties = _.reduce(properties, (obj, prop) => {
obj[prop] = true;
return obj;
}, {});
return this.traverse(modelClass, model => {
model.$omit(properties);
});
}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
joinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
innerJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
outerJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
leftJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
leftOuterJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
rightJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
rightOuterJoinRelation(relationName) {}
/**
* @param {string} relationName
* @returns {QueryBuilder}
*/
fullOuterJoinRelation(relationName) {}
/**
* @param {string|number|Array.<string|number>} id
* @returns {QueryBuilder}
*/
findById(id) {
return this.whereComposite(this._modelClass.getFullIdColumn(), id).first();
}
/**
* @returns {QueryBuilder}
*/
withSchema(schema) {
this.internalContext().onBuild.push(builder => {
if (!builder.has(/withSchema/)) {
// Need to push this operation to the front because knex doesn't use the
// schema for operations called before `withSchema`.
builder.callKnexQueryBuilderOperation('withSchema', [schema], true);
}
});
return this;
}
/**
* @returns {QueryBuilder}
*/
debug() {
this.internalContext().onBuild.push(builder => {
builder.callKnexQueryBuilderOperation('debug', []);
});
return this;
}
/**
* @param {Object|Model|Array.<Object>|Array.<Model>} modelsOrObjects
* @returns {QueryBuilder}
*/
insert(modelsOrObjects) {
const insertOperation = this._insertOperationFactory(this);
return this.callQueryBuilderOperation(insertOperation, [modelsOrObjects]);
}
/**
* @param {Object|Model|Array.<Object>|Array.<Model>} modelsOrObjects
* @returns {QueryBuilder}
*/
insertAndFetch(modelsOrObjects) {
const insertAndFetchOperation = new InsertAndFetchOperation('insertAndFetch', {
delegate: this._insertOperationFactory(this)
});
return this.callQueryBuilderOperation(insertAndFetchOperation, [modelsOrObjects]);
}
/**
* @param {Object|Model|Array.<Object>|Array.<Model>} modelsOrObjects
* @returns {QueryBuilder}
*/
insertGraph(modelsOrObjects) {
const insertGraphOperation = new InsertGraphOperation('insertGraph', {
delegate: this._insertOperationFactory(this)
});
return this.callQueryBuilderOperation(insertGraphOperation, [modelsOrObjects]);
}
/**
* @returns {QueryBuilder}
*/
insertWithRelated(...args) {
return this.insertGraph(...args);
}
/**
* @param {Object|Model|Array.<Object>|Array.<Model>} modelsOrObjects
* @returns {QueryBuilder}
*/
insertGraphAndFetch(modelsOrObjects) {
const insertGraphAndFetchOperation = new InsertGraphAndFetchOperation('insertGraphAndFetch', {
delegate: new InsertGraphOperation('insertGraph', {
delegate: this._insertOperationFactory(this)
})
});
return this.callQueryBuilderOperation(insertGraphAndFetchOperation, [modelsOrObjects]);
}
/**
* @returns {QueryBuilder}
*/
insertWithRelatedAndFetch(...args) {
return this.insertGraphAndFetch(...args);
}
/**
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
update(modelOrObject) {
const updateOperation = this._updateOperationFactory(this);
return this.callQueryBuilderOperation(updateOperation, [modelOrObject]);
}
/**
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
updateAndFetch(modelOrObject) {
const delegateOperation = this._updateOperationFactory(this);
if (!(delegateOperation.instance instanceof this._modelClass)) {
throw new Error('updateAndFetch can only be called for instance operations');
}
const updateAndFetch = new UpdateAndFetchOperation('updateAndFetch', {
delegate: delegateOperation
});
return this.callQueryBuilderOperation(updateAndFetch, [delegateOperation.instance.$id(), modelOrObject]);
}
/**
* @param {number|string|Array.<number|string>} id
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
updateAndFetchById(id, modelOrObject) {
const updateAndFetch = new UpdateAndFetchOperation('updateAndFetch', {
delegate: this._updateOperationFactory(this)
});
return this.callQueryBuilderOperation(updateAndFetch, [id, modelOrObject]);
}
/**
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
patch(modelOrObject) {
const patchOperation = this._patchOperationFactory(this);
return this.callQueryBuilderOperation(patchOperation, [modelOrObject]);
}
/**
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
patchAndFetch(modelOrObject) {
const delegateOperation = this._patchOperationFactory(this);
if (!(delegateOperation.instance instanceof this._modelClass)) {
throw new Error('patchAndFetch can only be called for instance operations');
}
const patchAndFetch = new UpdateAndFetchOperation('patchAndFetch', {
delegate: delegateOperation
});
return this.callQueryBuilderOperation(patchAndFetch, [delegateOperation.instance.$id(), modelOrObject]);
}
/**
* @param {number|string|Array.<number|string>} id
* @param {Model|Object=} modelOrObject
* @returns {QueryBuilder}
*/
patchAndFetchById(id, modelOrObject) {
const patchAndFetch = new UpdateAndFetchOperation('patchAndFetch', {
delegate: this._patchOperationFactory(this)
});
return this.callQueryBuilderOperation(patchAndFetch, [id, modelOrObject]);
}
/**
* @returns {QueryBuilder}
*/
delete() {
const deleteOperation = this._deleteOperationFactory(this);
return this.callQueryBuilderOperation(deleteOperation, []);
}
/**
* @returns {QueryBuilder}
*/
del() {
return this.delete();
}
/**
* @param {number|string|Array.<number|string>} id
* @returns {QueryBuilder}
*/
deleteById(id) {
return this.delete().whereComposite(this._modelClass.getFullIdColumn(), id);
}
/**
* @param {number|string|object|Array.<number|string>|Array.<Array.<number|string>>|Array.<object>} ids
* @returns {QueryBuilder}
*/
relate(ids) {
const relateOperation = this._relateOperationFactory(this);
return this.callQueryBuilderOperation(relateOperation, [ids]);
}
/**
* @returns {QueryBuilder}
*/
unrelate() {
const unrelateOperation = this._unrelateOperationFactory(this);
return this.callQueryBuilderOperation(unrelateOperation, []);
}
/**
* @returns {QueryBuilder}
*/
increment(propertyName, howMuch) {
let patch = {};
let columnName = this._modelClass.propertyNameToColumnName(propertyName);
patch[propertyName] = this.knex().raw('?? + ?', [columnName, howMuch]);
return this.patch(patch);
}
/**
* @returns {QueryBuilder}
*/
decrement(propertyName, howMuch) {
let patch = {};
let columnName = this._modelClass.propertyNameToColumnName(propertyName);
patch[propertyName] = this.knex().raw('?? - ?', [columnName, howMuch]);
return this.patch(patch);
}
}
function writeQueryOperation(target, property, descriptor) {
const func = descriptor.value;
descriptor.value = function decorator$writeQueryOperation() {
if (!this.isFindQuery()) {
return this.reject(new Error('Double call to a write method. ' +
'You can only call one of the write methods ' +
'(insert, update, patch, delete, relate, unrelate, increment, decrement) ' +
'and only once per query builder.'));
}
try {
func.apply(this, arguments);
} catch (err) {
this.reject(err);
}
return this;
};
}
function checkEager(builder) {
if (builder._eagerExpression && builder._allowedEagerExpression) {
if (!builder._allowedEagerExpression.isSubExpression(builder._eagerExpression)) {
builder.reject(new ValidationError({eager: 'eager expression not allowed'}));
}
}
}
function createModels(result) {
const builder = this.builder;
if (result === null || result === undefined) {
return null;
}
if (Array.isArray(result)) {
if (result.length && typeof result[0] === 'object' && !(result[0] instanceof builder._modelClass)) {
for (let i = 0, l = result.length; i < l; ++i) {
result[i] = builder._modelClass.fromDatabaseJson(result[i]);
}
}
} else if (typeof result === 'object' && !(result instanceof builder._modelClass)) {
result = builder._modelClass.fromDatabaseJson(result);
}
return result;
}
function build(builder) {
let context = builder.context() || {};
let internalContext = builder.internalContext();
let knexBuilder = builder.knex().queryBuilder();
callOnBuildHooks(builder, context.onBuild);
callOnBuildHooks(builder, internalContext.onBuild);
knexBuilder = builder.buildInto(knexBuilder);
if (!builder.has(QueryBuilderBase.FromSelector)) {
const table = builder.modelClass().tableName;
// Set the table only if it hasn't been explicitly set yet.
knexBuilder.table(table);
if (!builder.has(QueryBuilderBase.SelectSelector)) {
knexBuilder.select(`${table}.*`);
}
}
return knexBuilder;
}
function chainHooks(promise, func) {
if (_.isFunction(func)) {
promise = promise.then(function (result) {
return func.call(this.builder, result, this.builder);
});
} else if (Array.isArray(func)) {
func.forEach(func => {
promise = promise.then(function (result) {
return func.call(this.builder, result, this.builder);
});
});
}
return promise;
}
function callOnBuildHooks(builder, func) {
if (_.isFunction(func)) {
func.call(builder, builder);
} else if (_.isArray(func)) {
for (let i = 0, l = func.length; i < l; ++i) {
func[i].call(builder, builder);
}
}
}
function createHookCaller(hook) {
const hasMethod = 'has' + _.upperFirst(hook);
// Compile the caller function for (measured) performance boost.
const caller = new Function('promise', 'op', `
if (op.${hasMethod}()) {
return promise.then(function (result) {
return op.${hook}(this.builder, result);
});
} else {
return promise;
}
`);
return (promise, operations) => {
for (let i = 0, l = operations.length; i < l; ++i) {
promise = caller(promise, operations[i]);
}
return promise;
};
}
function createOperationFactory(OperationClass, name, options) {
return () => {
return new OperationClass(name, options);
};
}
const chainBeforeOperations = createHookCaller('onBefore');
const chainBeforeInternalOperations = createHookCaller('onBeforeInternal');
const chainRawResultOperations = createHookCaller('onRawResult');
const chainAfterQueryOperations = createHookCaller('onAfterQuery');
const chainAfterInternalOperations = createHookCaller('onAfterInternal');
const chainAfterOperations = createHookCaller('onAfter');
const findOperationFactory = createOperationFactory(FindOperation, 'find');
const insertOperationFactory = createOperationFactory(InsertOperation, 'insert');
const updateOperationFactory = createOperationFactory(UpdateOperation, 'update');
const patchOperationFactory = createOperationFactory(UpdateOperation, 'patch', {modelOptions: {patch: true}});
const relateOperationFactory = createOperationFactory(QueryBuilderOperation, 'relate');
const unrelateOperationFactory = createOperationFactory(QueryBuilderOperation, 'unrelate');
const deleteOperationFactory = createOperationFactory(DeleteOperation, 'delete');