UNPKG

objection

Version:
1,380 lines (1,095 loc) 37 kB
'use strict'; const { wrapError } = require('db-errors'); const { raw } = require('./RawBuilder'); const { createModifier } = require('../utils/createModifier'); const { ValidationErrorType } = require('../model/ValidationError'); const { isObject, isString, isFunction, last, flatten } = require('../utils/objectUtils'); const { RelationExpression, DuplicateRelationError } = require('./RelationExpression'); const { assertIdNotUndefined } = require('../utils/assert'); const { Selection } = require('./operations/select/Selection'); const { QueryBuilderContext } = require('./QueryBuilderContext'); const { QueryBuilderBase } = require('./QueryBuilderBase'); const { FindOperation } = require('./operations/FindOperation'); const { DeleteOperation } = require('./operations/DeleteOperation'); const { UpdateOperation } = require('./operations/UpdateOperation'); const { InsertOperation } = require('./operations/InsertOperation'); const { RelateOperation } = require('./operations/RelateOperation'); const { UnrelateOperation } = require('./operations/UnrelateOperation'); const { JoinEagerOperation } = require('./operations/eager/JoinEagerOperation'); const { NaiveEagerOperation } = require('./operations/eager/NaiveEagerOperation'); const { WhereInEagerOperation } = require('./operations/eager/WhereInEagerOperation'); const { InsertGraphAndFetchOperation } = require('./operations/InsertGraphAndFetchOperation'); const { UpsertGraphAndFetchOperation } = require('./operations/UpsertGraphAndFetchOperation'); const { InsertAndFetchOperation } = require('./operations/InsertAndFetchOperation'); const { UpdateAndFetchOperation } = require('./operations/UpdateAndFetchOperation'); const { JoinRelatedOperation } = require('./operations/JoinRelatedOperation'); const { OnBuildKnexOperation } = require('./operations/OnBuildKnexOperation'); const { InsertGraphOperation } = require('./operations/InsertGraphOperation'); const { UpsertGraphOperation } = require('./operations/UpsertGraphOperation'); const { RunBeforeOperation } = require('./operations/RunBeforeOperation'); const { RunAfterOperation } = require('./operations/RunAfterOperation'); const { FindByIdOperation } = require('./operations/FindByIdOperation'); const { FindByIdsOperation } = require('./operations/FindByIdsOperation'); const { OnBuildOperation } = require('./operations/OnBuildOperation'); const { OnErrorOperation } = require('./operations/OnErrorOperation'); const { SelectOperation } = require('./operations/select/SelectOperation'); const { EagerOperation } = require('./operations/eager/EagerOperation'); const { RangeOperation } = require('./operations/RangeOperation'); const { FirstOperation } = require('./operations/FirstOperation'); const { FromOperation } = require('./operations/FromOperation'); const { KnexOperation } = require('./operations/KnexOperation'); class QueryBuilder extends QueryBuilderBase { static init(self, modelClass) { super.init(self, modelClass); self._resultModelClass = null; self._explicitRejectValue = null; self._explicitResolveValue = null; self._modifiers = {}; self._allowedGraphExpression = null; self._findOperationOptions = modelClass.defaultFindOptions; self._relatedQueryFor = null; self._findOperationFactory = findOperationFactory; self._insertOperationFactory = insertOperationFactory; self._updateOperationFactory = updateOperationFactory; self._patchOperationFactory = patchOperationFactory; self._relateOperationFactory = relateOperationFactory; self._unrelateOperationFactory = unrelateOperationFactory; self._deleteOperationFactory = deleteOperationFactory; } static get QueryBuilderContext() { return QueryBuilderContext; } static parseRelationExpression(expr) { return RelationExpression.create(expr).toPojo(); } tableNameFor(modelClassOrTableName, newTableName) { return super.tableNameFor(getTableName(modelClassOrTableName), newTableName); } tableName(newTableName) { return this.tableNameFor(this.modelClass().getTableName(), newTableName); } tableRef() { return this.tableRefFor(this.modelClass().getTableName()); } aliasFor(modelClassOrTableName, alias) { return super.aliasFor(getTableName(modelClassOrTableName), alias); } alias(alias) { return this.aliasFor(this.modelClass().getTableName(), alias); } fullIdColumnFor(modelClass) { const tableName = this.tableRefFor(modelClass); const idColumn = modelClass.getIdColumn(); if (Array.isArray(idColumn)) { return idColumn.map((col) => `${tableName}.${col}`); } else { return `${tableName}.${idColumn}`; } } fullIdColumn() { return this.fullIdColumnFor(this.modelClass()); } modifiers(modifiers) { if (arguments.length === 0) { return { ...this._modifiers }; } else { this._modifiers = { ...this._modifiers, ...modifiers }; return this; } } modify(modifier, ...args) { if (!modifier) { return this; } modifier = createModifier({ modifier, modelClass: this.modelClass(), modifiers: this._modifiers, }); modifier(this, ...args); return this; } reject(error) { this._explicitRejectValue = error; return this; } resolve(value) { this._explicitResolveValue = value; return this; } isExplicitlyResolvedOrRejected() { return !!(this._explicitRejectValue || this._explicitResolveValue); } isExecutable() { return !this.isExplicitlyResolvedOrRejected() && !findQueryExecutorOperation(this); } findOperationFactory(factory) { this._findOperationFactory = factory; return this; } insertOperationFactory(factory) { this._insertOperationFactory = factory; return this; } updateOperationFactory(factory) { this._updateOperationFactory = factory; return this; } patchOperationFactory(factory) { this._patchOperationFactory = factory; return this; } deleteOperationFactory(factory) { this._deleteOperationFactory = factory; return this; } relateOperationFactory(factory) { this._relateOperationFactory = factory; return this; } unrelateOperationFactory(factory) { this._unrelateOperationFactory = factory; return this; } withGraphFetched(exp, options = {}) { return this._withGraph(exp, options, getWhereInEagerAlgorithm(this)); } withGraphJoined(exp, options = {}) { return this._withGraph(exp, options, getJoinEagerAlgorithm(this)); } _withGraph(exp, options, algorithm) { const eagerOp = ensureEagerOperation(this, algorithm); const parsedExp = parseRelationExpression(this.modelClass(), exp); eagerOp.expression = eagerOp.expression.merge(parsedExp); eagerOp.graphOptions = { ...eagerOp.graphOptions, ...options }; checkEager(this); return this; } allowGraph(exp) { const currentExpr = this._allowedGraphExpression || RelationExpression.create(); this._allowedGraphExpression = currentExpr.merge( parseRelationExpression(this.modelClass(), exp), ); checkEager(this); return this; } allowedGraphExpression() { return this._allowedGraphExpression; } graphExpressionObject() { const eagerOp = this.findOperation(EagerOperation); if (eagerOp && !eagerOp.expression.isEmpty) { return eagerOp.expression.toPojo(); } else { return null; } } graphModifiersAtPath() { const eagerOp = this.findOperation(EagerOperation); if (eagerOp && !eagerOp.expression.isEmpty) { return eagerOp.modifiersAtPath.map((it) => Object.assign({}, it)); } else { return []; } } modifyGraph(path, modifier) { const eagerOp = ensureEagerOperation(this); eagerOp.modifiersAtPath.push({ path, modifier }); return this; } findOptions(opt) { if (arguments.length !== 0) { this._findOperationOptions = Object.assign({}, this._findOperationOptions, opt); return this; } else { return this._findOperationOptions; } } resultModelClass() { return this._resultModelClass || this.modelClass(); } isFind() { return !( this.isInsert() || this.isUpdate() || this.isDelete() || this.isRelate() || this.isUnrelate() ); } isInsert() { return this.has(InsertOperation); } isUpdate() { return this.has(UpdateOperation); } isDelete() { return this.has(DeleteOperation); } isRelate() { return this.has(RelateOperation); } isUnrelate() { return this.has(UnrelateOperation); } hasWheres() { const queryWithoutGraph = this.clone().clearWithGraph(); return prebuildQuery(queryWithoutGraph).has(QueryBuilderBase.WhereSelector); } hasSelects() { return this.has(QueryBuilderBase.SelectSelector); } hasWithGraph() { const eagerOp = this.findOperation(EagerOperation); return !!eagerOp && !eagerOp.expression.isEmpty; } isSelectAll() { if (this._operations.length === 0) { return true; } const tableRef = this.tableRef(); const tableName = this.tableName(); return this.everyOperation((op) => { if (op.constructor === SelectOperation) { // SelectOperations with zero selections are the ones that only have // raw items or other non-trivial selections. return ( op.selections.length > 0 && op.selections.every((select) => { return (!select.table || select.table === tableRef) && select.column === '*'; }) ); } else if (op.constructor === FromOperation) { return op.table === tableName; } else if (op.name === 'as' || op.is(FindOperation) || op.is(OnErrorOperation)) { return true; } else { return false; } }); } toString() { return '[object QueryBuilder]'; } clone() { const builder = this.emptyInstance(); // Call the super class's clone implementation. this.baseCloneInto(builder); builder._resultModelClass = this._resultModelClass; builder._explicitRejectValue = this._explicitRejectValue; builder._explicitResolveValue = this._explicitResolveValue; builder._modifiers = { ...this._modifiers }; builder._allowedGraphExpression = this._allowedGraphExpression; builder._findOperationOptions = this._findOperationOptions; builder._relatedQueryFor = this._relatedQueryFor; return builder; } emptyInstance() { const builder = new this.constructor(this.modelClass()); 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._relatedQueryFor = this._relatedQueryFor; return builder; } clearWithGraph() { this.clear(EagerOperation); return this; } clearWithGraphFetched() { this.clear(WhereInEagerOperation); return this; } clearAllowGraph() { this._allowedGraphExpression = null; return this; } clearModifiers() { this._modifiers = {}; return this; } clearReject() { this._explicitRejectValue = null; return this; } clearResolve() { this._explicitResolveValue = null; return this; } castTo(modelClass) { this._resultModelClass = modelClass || null; return this; } then(...args) { const promise = this.execute(); return promise.then(...args); } catch(...args) { const promise = this.execute(); return promise.catch(...args); } async resultSize() { const knex = this.knex(); const builder = this.clone().clear(/orderBy|offset|limit/); const countQuery = knex.count('* as count').from((knexBuilder) => { builder.toKnexQuery(knexBuilder).as('temp'); }); if (this.internalOptions().debug) { countQuery.debug(); } const result = await countQuery; return result[0] && result[0].count ? parseInt(result[0].count) : 0; } toKnexQuery(knexBuilder = this.knex().queryBuilder()) { const prebuiltQuery = prebuildQuery(this.clone()); return buildKnexQuery(prebuiltQuery, knexBuilder); } async execute() { // Take a clone so that we don't modify this instance during execution. const builder = this.clone(); try { await beforeExecute(builder); const result = await doExecute(builder); return await afterExecute(builder, result); } catch (error) { return await handleExecuteError(builder, error); } } throwIfNotFound(data = {}) { return this.runAfter((result) => { if ( (Array.isArray(result) && result.length === 0) || result === null || result === undefined || result === 0 ) { throw this.modelClass().createNotFoundError(this.context(), data); } else { return result; } }); } findSelection(selection, explicit = false) { let noSelectStatements = true; let selectionInstance = null; this.forEachOperation(true, (op) => { if (op.constructor === SelectOperation) { selectionInstance = op.findSelection(this, selection); noSelectStatements = false; if (selectionInstance) { return false; } } }); if (selectionInstance) { return selectionInstance; } if (noSelectStatements && !explicit) { const selectAll = new Selection(this.tableRef(), '*'); if (Selection.doesSelect(this, selectAll, selection)) { return selectAll; } else { return null; } } else { return null; } } findAllSelections() { let allSelections = []; this.forEachOperation(true, (op) => { if (op.constructor === SelectOperation) { allSelections = allSelections.concat(op.selections); } }); return allSelections; } hasSelection(selection, explicit) { return this.findSelection(selection, explicit) !== null; } hasSelectionAs(selection, alias, explicit) { selection = Selection.create(selection); const foundSelection = this.findSelection(selection, explicit); if (foundSelection === null) { return false; } else { if (foundSelection.column === '*') { // * selects the columns with their column names as aliases. return selection.column === alias; } else { return foundSelection.name === alias; } } } traverse(modelClass, traverser) { if (typeof traverser === 'undefined') { traverser = modelClass; modelClass = null; } return this.runAfter((result) => { this.resultModelClass().traverse(modelClass, result, traverser); return result; }); } page(page, pageSize) { return this.range(+page * +pageSize, (+page + 1) * +pageSize - 1); } columnInfo({ table = null } = {}) { table = table || this.tableName(); const knex = this.knex(); const tableParts = table.split('.'); const columnInfoQuery = knex(last(tableParts)).columnInfo(); const schema = this.internalOptions().schema; if (schema) { columnInfoQuery.withSchema(schema); } else if (tableParts.length > 1) { columnInfoQuery.withSchema(tableParts[0]); } if (this.internalOptions().debug) { columnInfoQuery.debug(); } return columnInfoQuery; } withSchema(schema) { this.internalOptions().schema = 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.addOperationToFront(new KnexOperation('withSchema'), [schema]); } }); return this; } debug /* istanbul ignore next */() { this.internalOptions().debug = true; this.internalContext().onBuild.push((builder) => { builder.addOperation(new KnexOperation('debug'), []); }); return this; } insert(modelsOrObjects) { return writeOperation(this, () => { const insertOperation = this._insertOperationFactory(this); this.addOperation(insertOperation, [modelsOrObjects]); }); } insertAndFetch(modelsOrObjects) { return writeOperation(this, () => { const insertOperation = this._insertOperationFactory(this); const insertAndFetchOperation = new InsertAndFetchOperation('insertAndFetch', { delegate: insertOperation, }); this.addOperation(insertAndFetchOperation, [modelsOrObjects]); }); } insertGraph(modelsOrObjects, opt) { return writeOperation(this, () => { const insertOperation = this._insertOperationFactory(this); const insertGraphOperation = new InsertGraphOperation('insertGraph', { delegate: insertOperation, opt, }); this.addOperation(insertGraphOperation, [modelsOrObjects]); }); } insertGraphAndFetch(modelsOrObjects, opt) { return writeOperation(this, () => { const insertOperation = this._insertOperationFactory(this); const insertGraphOperation = new InsertGraphOperation('insertGraph', { delegate: insertOperation, opt, }); const insertGraphAndFetchOperation = new InsertGraphAndFetchOperation('insertGraphAndFetch', { delegate: insertGraphOperation, }); return this.addOperation(insertGraphAndFetchOperation, [modelsOrObjects]); }); } update(modelOrObject) { return writeOperation(this, () => { const updateOperation = this._updateOperationFactory(this); this.addOperation(updateOperation, [modelOrObject]); }); } updateAndFetch(modelOrObject) { return writeOperation(this, () => { const updateOperation = this._updateOperationFactory(this); if (!(updateOperation.instance instanceof this.modelClass())) { throw new Error('updateAndFetch can only be called for instance operations'); } const updateAndFetch = new UpdateAndFetchOperation('updateAndFetch', { delegate: updateOperation, }); // patchOperation is an instance update operation that already adds the // required "where id = $" clause. updateAndFetch.skipIdWhere = true; this.addOperation(updateAndFetch, [updateOperation.instance.$id(), modelOrObject]); }); } updateAndFetchById(id, modelOrObject) { return writeOperation(this, () => { const updateOperation = this._updateOperationFactory(this); const updateAndFetch = new UpdateAndFetchOperation('updateAndFetch', { delegate: updateOperation, }); this.addOperation(updateAndFetch, [id, modelOrObject]); }); } upsertGraph(modelsOrObjects, upsertOptions) { return writeOperation(this, () => { const upsertGraphOperation = new UpsertGraphOperation('upsertGraph', { upsertOptions, }); this.addOperation(upsertGraphOperation, [modelsOrObjects]); }); } upsertGraphAndFetch(modelsOrObjects, upsertOptions) { return writeOperation(this, () => { const upsertGraphOperation = new UpsertGraphOperation('upsertGraph', { upsertOptions, }); const upsertGraphAndFetchOperation = new UpsertGraphAndFetchOperation('upsertGraphAndFetch', { delegate: upsertGraphOperation, }); return this.addOperation(upsertGraphAndFetchOperation, [modelsOrObjects]); }); } patch(modelOrObject) { return writeOperation(this, () => { const patchOperation = this._patchOperationFactory(this); this.addOperation(patchOperation, [modelOrObject]); }); } patchAndFetch(modelOrObject) { return writeOperation(this, () => { const patchOperation = this._patchOperationFactory(this); if (!(patchOperation.instance instanceof this.modelClass())) { throw new Error('patchAndFetch can only be called for instance operations'); } const patchAndFetch = new UpdateAndFetchOperation('patchAndFetch', { delegate: patchOperation, }); // patchOperation is an instance update operation that already adds the // required "where id = $" clause. patchAndFetch.skipIdWhere = true; this.addOperation(patchAndFetch, [patchOperation.instance.$id(), modelOrObject]); }); } patchAndFetchById(id, modelOrObject) { return writeOperation(this, () => { const patchOperation = this._patchOperationFactory(this); const patchAndFetch = new UpdateAndFetchOperation('patchAndFetch', { delegate: patchOperation, }); this.addOperation(patchAndFetch, [id, modelOrObject]); }); } delete(...args) { return writeOperation(this, () => { if (args.length) { throw new Error( `Don't pass arguments to delete(). You should use it like this: delete().where('foo', 'bar').andWhere(...)`, ); } const deleteOperation = this._deleteOperationFactory(this); this.addOperation(deleteOperation, args); }); } del(...args) { return this.delete(...args); } relate(...args) { return writeOperation(this, () => { const relateOperation = this._relateOperationFactory(this); this.addOperation(relateOperation, args); }); } unrelate(...args) { return writeOperation(this, () => { if (args.length) { throw new Error( `Don't pass arguments to unrelate(). You should use it like this: unrelate().where('foo', 'bar').andWhere(...)`, ); } const unrelateOperation = this._unrelateOperationFactory(this); this.addOperation(unrelateOperation, args); }); } increment(propertyName, howMuch) { const columnName = this.modelClass().propertyNameToColumnName(propertyName); return this.patch({ [columnName]: raw('?? + ?', [columnName, howMuch]), }); } decrement(propertyName, howMuch) { const columnName = this.modelClass().propertyNameToColumnName(propertyName); return this.patch({ [columnName]: raw('?? - ?', [columnName, howMuch]), }); } findOne(...args) { return this.where.apply(this, args).first(); } range(...args) { return this.clear(RangeOperation).addOperation(new RangeOperation('range'), args); } first(...args) { return this.addOperation(new FirstOperation('first'), args); } joinRelated(expression, options) { ensureJoinRelatedOperation(this, 'innerJoin').addCall({ expression, options, }); return this; } innerJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'innerJoin').addCall({ expression, options, }); return this; } outerJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'outerJoin').addCall({ expression, options, }); return this; } fullOuterJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'fullOuterJoin').addCall({ expression, options, }); return this; } leftJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'leftJoin').addCall({ expression, options, }); return this; } leftOuterJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'leftOuterJoin').addCall({ expression, options, }); return this; } rightJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'rightJoin').addCall({ expression, options, }); return this; } rightOuterJoinRelated(expression, options) { ensureJoinRelatedOperation(this, 'rightOuterJoin').addCall({ expression, options, }); return this; } deleteById(id) { return this.findById(id) .delete() .runBefore((_, builder) => { if (!builder.internalOptions().skipUndefined) { assertIdNotUndefined(id, `undefined was passed to deleteById`); } }); } findById(...args) { return this.addOperation(new FindByIdOperation('findById'), args).first(); } findByIds(...args) { return this.addOperation(new FindByIdsOperation('findByIds'), args); } runBefore(...args) { return this.addOperation(new RunBeforeOperation('runBefore'), args); } onBuild(...args) { return this.addOperation(new OnBuildOperation('onBuild'), args); } onBuildKnex(...args) { return this.addOperation(new OnBuildKnexOperation('onBuildKnex'), args); } runAfter(...args) { return this.addOperation(new RunAfterOperation('runAfter'), args); } onError(...args) { return this.addOperation(new OnErrorOperation('onError'), args); } from(...args) { return this.addOperation(new FromOperation('from'), args); } table(...args) { return this.addOperation(new FromOperation('table'), args); } for(relatedQueryFor) { if (arguments.length === 0) { return this._relatedQueryFor; } else { this._relatedQueryFor = relatedQueryFor; return this; } } } Object.defineProperties(QueryBuilder.prototype, { isObjectionQueryBuilder: { enumerable: false, writable: false, value: true, }, }); function getTableName(modelClassOrTableName) { if (isString(modelClassOrTableName)) { return modelClassOrTableName; } else { return modelClassOrTableName.getTableName(); } } function ensureEagerOperation(builder, algorithm = null) { const modelClass = builder.modelClass(); const defaultGraphOptions = modelClass.getDefaultGraphOptions(); const eagerOp = builder.findOperation(EagerOperation); if (algorithm) { const EagerOperationClass = getOperationClassForEagerAlgorithm(builder, algorithm); if (eagerOp instanceof EagerOperationClass) { return eagerOp; } else { const newEagerOp = new EagerOperationClass('eager', { defaultGraphOptions, }); if (eagerOp) { newEagerOp.cloneFrom(eagerOp); } builder.clear(EagerOperation); builder.addOperation(newEagerOp); return newEagerOp; } } else { if (eagerOp) { return eagerOp; } else { const EagerOperationClass = getOperationClassForEagerAlgorithm( builder, getWhereInEagerAlgorithm(builder), ); const newEagerOp = new EagerOperationClass('eager', { defaultGraphOptions, }); builder.addOperation(newEagerOp); return newEagerOp; } } } function getWhereInEagerAlgorithm(builder) { return builder.modelClass().WhereInEagerAlgorithm; } function getJoinEagerAlgorithm(builder) { return builder.modelClass().JoinEagerAlgorithm; } function getNaiveEagerAlgorithm(builder) { return builder.modelClass().NaiveEagerAlgorithm; } function getOperationClassForEagerAlgorithm(builder, algorithm) { if (algorithm === getJoinEagerAlgorithm(builder)) { return JoinEagerOperation; } else if (algorithm === getNaiveEagerAlgorithm(builder)) { return NaiveEagerOperation; } else { return WhereInEagerOperation; } } function parseRelationExpression(modelClass, exp) { try { return RelationExpression.create(exp); } catch (err) { if (err instanceof DuplicateRelationError) { throw modelClass.createValidationError({ type: ValidationErrorType.RelationExpression, message: `Duplicate relation name "${err.relationName}" in relation expression "${exp}". For example, use "${err.relationName}.[foo, bar]" instead of "[${err.relationName}.foo, ${err.relationName}.bar]".`, }); } else { throw modelClass.createValidationError({ type: ValidationErrorType.RelationExpression, message: `Invalid relation expression "${exp}"`, }); } } } function checkEager(builder) { const eagerOp = builder.findOperation(EagerOperation); if (!eagerOp) { return; } const expr = eagerOp.expression; const allowedExpr = builder.allowedGraphExpression(); if (expr.numChildren > 0 && allowedExpr && !allowedExpr.isSubExpression(expr)) { const modelClass = builder.modelClass(); builder.reject( modelClass.createValidationError({ type: ValidationErrorType.UnallowedRelation, message: 'eager expression not allowed', }), ); } } function findQueryExecutorOperation(builder) { return builder.findOperation((op) => op.hasQueryExecutor()); } function beforeExecute(builder) { let promise = Promise.resolve(); builder = addImplicitOperations(builder); // Resolve all before hooks before building and executing the query // and the rest of the hooks. promise = chainOperationHooks(promise, builder, 'onBefore1'); promise = chainOperationHooks(promise, builder, 'onBefore2'); promise = chainHooks(promise, builder, builder.context().runBefore); promise = chainHooks(promise, builder, builder.internalContext().runBefore); promise = chainOperationHooks(promise, builder, 'onBefore3'); return promise; } function doExecute(builder) { let promise = Promise.resolve(); builder = callOnBuildHooks(builder); const queryExecutorOperation = findQueryExecutorOperation(builder); const explicitRejectValue = builder._explicitRejectValue; const explicitResolveValue = builder._explicitResolveValue; if (explicitRejectValue !== null) { promise = Promise.reject(explicitRejectValue); } else if (explicitResolveValue !== null) { promise = Promise.resolve(explicitResolveValue); } else if (queryExecutorOperation !== null) { promise = Promise.resolve(queryExecutorOperation.queryExecutor(builder)); } else { promise = Promise.resolve(buildKnexQuery(builder)); promise = chainOperationHooks(promise, builder, 'onRawResult'); promise = promise.then((result) => createModels(result, builder)); } return promise; } function afterExecute(builder, result) { let promise = Promise.resolve(result); promise = chainOperationHooks(promise, builder, 'onAfter1'); promise = chainOperationHooks(promise, builder, 'onAfter2'); promise = chainHooks(promise, builder, builder.context().runAfter); promise = chainHooks(promise, builder, builder.internalContext().runAfter); promise = chainOperationHooks(promise, builder, 'onAfter3'); return promise; } class ReturnImmediatelyException { constructor(value) { this.value = value; } } function handleReturnImmediatelyValue(builder) { const { returnImmediatelyValue } = builder.internalOptions(); if (returnImmediatelyValue !== undefined) { throw new ReturnImmediatelyException(returnImmediatelyValue); } } function handleExecuteError(builder, err) { if (err instanceof ReturnImmediatelyException) { return Promise.resolve(err.value); } let promise = Promise.reject(wrapError(err)); builder.forEachOperation(true, (op) => { if (op.hasOnError()) { promise = promise.catch((err) => builder.callAsyncOperationMethod(op, 'onError', [builder, err]), ); } }); return promise; } function chainOperationHooks(promise, builder, hookName) { return promise.then((result) => { let promise = Promise.resolve(result); builder.forEachOperation(true, (op) => { if (op.hasHook(hookName)) { promise = promise.then((result) => { const res = builder.callAsyncOperationMethod(op, hookName, [builder, result]); handleReturnImmediatelyValue(builder); return res; }); } }); return promise; }); } function ensureJoinRelatedOperation(builder, joinOperation) { const opName = joinOperation + 'Relation'; let op = builder.findOperation(opName); if (!op) { op = new JoinRelatedOperation(opName, { joinOperation }); builder.addOperation(op); } return op; } function prebuildQuery(builder) { builder = addImplicitOperations(builder); builder = callOnBuildHooks(builder); const queryExecutorOperation = findQueryExecutorOperation(builder); if (queryExecutorOperation) { return prebuildQuery(queryExecutorOperation.queryExecutor(builder)); } else { return builder; } } function addImplicitOperations(builder) { if (builder.isFind()) { // 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. addFindOperation(builder); } if (builder.hasWithGraph()) { moveEagerOperationToEnd(builder); } return builder; } function addFindOperation(builder) { if (!builder.has(FindOperation)) { const operation = builder._findOperationFactory(builder); builder.addOperationToFront(operation, []); } } function moveEagerOperationToEnd(builder) { const eagerOp = builder.findOperation(EagerOperation); builder.clear(EagerOperation); builder.addOperation(eagerOp); } function callOnBuildHooks(builder) { callOnBuildFuncs(builder, builder.context().onBuild); callOnBuildFuncs(builder, builder.internalContext().onBuild); builder.executeOnBuild(); return builder; } function callOnBuildFuncs(builder, func) { if (isFunction(func)) { func.call(builder, builder); } else if (Array.isArray(func)) { func.forEach((func) => callOnBuildFuncs(builder, func)); } } function buildKnexQuery(builder, knexBuilder = builder.knex().queryBuilder()) { knexBuilder = builder.executeOnBuildKnex(knexBuilder); const fromOperation = builder.findLastOperation(QueryBuilderBase.FromSelector); if (!builder.isPartial()) { // Set the table only if it hasn't been explicitly set yet. if (!fromOperation) { knexBuilder = setDefaultTable(builder, knexBuilder); } // Only add `table.*` select if there are no explicit selects // and `from` is a table name and not a subquery. if (!builder.hasSelects() && (!fromOperation || fromOperation.table)) { knexBuilder = setDefaultSelect(builder, knexBuilder); } } return knexBuilder; } function setDefaultTable(builder, knexBuilder) { const table = builder.tableName(); const tableRef = builder.tableRef(); if (table === tableRef) { return knexBuilder.table(table); } else { return knexBuilder.table(`${table} as ${tableRef}`); } } function setDefaultSelect(builder, knexBuilder) { const tableRef = builder.tableRef(); return knexBuilder.select(`${tableRef}.*`); } async function chainHooks(promise, builder, func) { return promise.then((result) => { let promise = Promise.resolve(result); if (isFunction(func)) { promise = promise.then((result) => { const res = func.call(builder, result, builder); handleReturnImmediatelyValue(builder); return res; }); } else if (Array.isArray(func)) { func.forEach((func) => { promise = chainHooks(promise, builder, func); }); } return promise; }); } function createModels(result, builder) { if (result === null || result === undefined) { return null; } if (builder.isInsert()) { // results are applied to input models in `InsertOperation.onAfter1` instead. return result; } const modelClass = builder.resultModelClass(); if (Array.isArray(result)) { if (result.length && shouldBeConvertedToModel(result[0], modelClass)) { for (let i = 0, l = result.length; i < l; ++i) { result[i] = modelClass.fromDatabaseJson(result[i]); } } } else if (shouldBeConvertedToModel(result, modelClass)) { result = modelClass.fromDatabaseJson(result); } return result; } function shouldBeConvertedToModel(obj, modelClass) { return isObject(obj) && !(obj instanceof modelClass); } function writeOperation(builder, cb) { if (!builder.isFind()) { return builder.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 { cb(); return builder; } catch (err) { return builder.reject(err); } } function findOperationFactory() { return new FindOperation('find'); } function insertOperationFactory() { return new InsertOperation('insert'); } function updateOperationFactory() { return new UpdateOperation('update'); } function patchOperationFactory() { return new UpdateOperation('patch', { modelOptions: { patch: true }, }); } function relateOperationFactory() { return new RelateOperation('relate', {}); } function unrelateOperationFactory() { return new UnrelateOperation('unrelate', {}); } function deleteOperationFactory() { return new DeleteOperation('delete'); } module.exports = { QueryBuilder, };