UNPKG

objection

Version:
357 lines (303 loc) 8.17 kB
import _ from 'lodash'; import {inherits} from '../utils/classUtils'; import QueryBuilderContextBase from './QueryBuilderContextBase'; import KnexOperation from './operations/KnexOperation'; /** * Base functionality to be able to use query builder operation annotations. */ export default class QueryBuilderOperationSupport { constructor(knex, QueryBuilderContext) { /** * @type {knex} * @protected */ this._knex = knex; /** * @type {Array.<QueryBuilderOperation>} * @protected */ this._operations = []; /** * @type {QueryBuilderContextBase} * @protected */ this._context = new (QueryBuilderContext || QueryBuilderContextBase)(this._createUserContextBase()); } /** * @param {function=} subclassConstructor * @return {Constructor.<QueryBuilderOperationSupport>} */ static extend(subclassConstructor) { inherits(subclassConstructor, this); return subclassConstructor; } /** * @param {Object=} ctx * @returns {Object|QueryBuilderOperationSupport} */ context(ctx) { if (arguments.length === 0) { return this._context.userContext; } else { const ctxBase = this._createUserContextBase(); this._context.userContext = Object.assign(ctxBase, ctx); return this; } } /** * @param {Object=} ctx * @returns {QueryBuilderOperationSupport} */ mergeContext(ctx) { const oldCtx = this._context.userContext; this._context.userContext = Object.assign(oldCtx, ctx); return this; } /** * @param {QueryBuilderContextBase=} ctx * @returns {QueryBuilderContextBase|QueryBuilderOperationSupport} */ internalContext(ctx) { if (arguments.length === 0) { return this._context; } else { this._context = ctx; return this; } } /** * @param {Object} opt * @returns {Object|QueryBuilderOperationSupport} */ internalOptions(opt) { if (arguments.length === 0) { return this._context.options; } else { this._context.options = Object.assign({}, this._context.options, opt); return this; } } /** * @param {knex=} knex * @returns {Object|QueryBuilderOperationSupport} */ knex(knex) { if (arguments.length === 0) { const knex = this._context.knex || this._knex; if (!knex) { throw new Error( `no database connection available for a query for table ${this.modelClass().tableName}. ` + `You need to bind the model class or the query to a knex instance.`); } return knex; } else { this._knex = knex; return this; } } /** * @param {RegExp|Constructor.<? extends QueryBuilderOperation>} operationSelector * @return {QueryBuilderBase} */ clear(operationSelector) { const operations = []; this.forEachOperation(operationSelector, (op) => { operations.push(op); }, false); this._operations = operations; return this; } /** * @param {QueryBuilderBase} queryBuilder * @param {RegExp|Constructor.<? extends QueryBuilderOperation>} operationSelector * @return {QueryBuilderBase} */ copyFrom(queryBuilder, operationSelector) { queryBuilder.forEachOperation(operationSelector, (op) => { this._operations.push(op); }); return this; } /** * @param {RegExp|Constructor.<? extends QueryBuilderOperation>} operationSelector * @returns {boolean} */ has(operationSelector) { let found = false; this.forEachOperation(operationSelector, () => { found = true; return false; }); return found; } /** * @param {RegExp|Constructor.<? extends QueryBuilderOperation>} operationSelector * @returns {boolean} */ indexOfOperation(operationSelector) { let idx = -1; this.forEachOperation(operationSelector, (op, i) => { idx = i; return false; }); return idx; } /** * @param {RegExp|Constructor.<? extends QueryBuilderOperation>} operationSelector * @param {function(QueryBuilderOperation)} callback * @param {boolean} match * @returns {QueryBuilderBase} */ forEachOperation(operationSelector, callback, match = true) { if (_.isRegExp(operationSelector)) { this._forEachOperationRegex(operationSelector, callback, match); } else { this._forEachOperationInstanceOf(operationSelector, callback, match); } return this; } /** * @param {QueryBuilderOperation} operation * @param {Array.<*>} args * @param {Boolean=} pushFront * @returns {QueryBuilderOperationSupport} */ callQueryBuilderOperation(operation, args, pushFront) { if (operation.call(this, args || [])) { if (pushFront) { this._operations.splice(0, 0, operation); } else { this._operations.push(operation); } } return this; } /** * @param {string} methodName * @param {Array.<*>} args * @returns {QueryBuilderOperationSupport} */ callKnexQueryBuilderOperation(methodName, args, pushFront) { return this.callQueryBuilderOperation(new KnexOperation(methodName), args, pushFront); } /** * @returns {QueryBuilderOperationSupport} */ clone() { return this.baseCloneInto(new this.constructor(this.knex())); } /** * @protected * @returns {QueryBuilderOperationSupport} */ baseCloneInto(builder) { builder._knex = this._knex; builder._operations = this._operations.slice(); builder._context = this._context.clone(); return builder; } /** * @returns {knex.QueryBuilder} */ build() { return this.buildInto(this.knex().queryBuilder()); } /** * @protected */ buildInto(knexBuilder) { const tmp = new Array(10); let i = 0; while (i < this._operations.length) { const op = this._operations[i]; const ln = this._operations.length; op.onBeforeBuild(this); const numNew = this._operations.length - ln; // onBeforeBuild may call methods that add more operations. If // this was the case, move the operations to be executed next. if (numNew > 0) { while (tmp.length < numNew) { tmp.push(null); } for (let j = 0; j < numNew; ++j) { tmp[j] = this._operations[ln + j]; } for (let j = ln + numNew - 1; j > i + numNew; --j) { this._operations[j] = this._operations[j - numNew]; } for (let j = 0; j < numNew; ++j) { this._operations[i + j + 1] = tmp[j]; } } ++i; } // onBuild operations should never add new operations. They should only call // methods on the knex query builder. for (let i = 0, l = this._operations.length; i < l; ++i) { this._operations[i].onBuild(knexBuilder, this) } return knexBuilder; } /** * @returns {string} */ toString() { return this.build().toString(); } /** * @returns {string} */ toSql() { return this.toString(); } /** * @returns {QueryBuilderOperationSupport} */ skipUndefined() { this._context.skipUndefined = true; return this; } /** * @returns {boolean} */ shouldSkipUndefined() { return this._context.skipUndefined; } /** * @private */ _createUserContextBase() { const ctxProto = {}; Object.defineProperty(ctxProto, 'transaction', { enumerable: false, get: () => this.knex() }); return Object.create(ctxProto); } /** * @private */ _forEachOperationRegex(operationSelector, callback, match) { for (let i = 0, l = this._operations.length; i < l; ++i) { const op = this._operations[i]; if (operationSelector.test(op.name) === match) { if (callback(op, i) === false) { break; } } } } /** * @private */ _forEachOperationInstanceOf(operationSelector, callback, match) { for (let i = 0, l = this._operations.length; i < l; ++i) { const op = this._operations[i]; if ((op instanceof operationSelector) === match) { if (callback(op, i) === false) { break; } } } } }