objection
Version:
An SQL-friendly ORM for Node.js
1,723 lines (1,356 loc) • 47.1 kB
JavaScript
;
const { wrapError } = require('db-errors');
const { raw } = require('./RawBuilder');
const { deprecate } = require('../utils/deprecate');
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());
}
applyModifier(...args) {
deprecate(
'applyModifier is deprecated. Use the modify method instead. applyModifier will be removed in objection 3.0'
);
return this.modify(flatten(args));
}
applyFilter(...args) {
deprecate(
'applyFilter is deprecated. Use the modify method instead. applyFilter will be removed in objection 3.0'
);
return this.modify(flatten(args));
}
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;
}
eagerAlgorithm(...args) {
deprecate(
'`QueryBuilder#eagerAlgorithm` method is deprecated. You should explicitly use either withGraphFetched or withGraphJoined methods. `eagerAlgoritm` method will be removed in 3.0'
);
return this._eagerAlgorithm(...args);
}
_eagerAlgorithm(algorithm, eagerOptions) {
ensureEagerOperation(this, algorithm);
if (eagerOptions) {
this._eagerOptions(eagerOptions);
}
return this;
}
eager(...args) {
deprecate(
'`QueryBuilder#eager` method is deprecated. You should use the `withGraphFetched` method instead. `eager` method will be removed in 3.0'
);
return this._eager(...args);
}
_eager(exp, modifiers) {
const eagerOp = ensureEagerOperation(this);
eagerOp.expression = parseRelationExpression(this.modelClass(), exp);
checkEager(this);
if (modifiers) {
this.clearModifiers().modifiers(modifiers);
}
return this;
}
joinEager(exp, modifiers) {
deprecate(
'`QueryBuilder#joinEager` method is deprecated. You should use the `withGraphJoined` method instead. `joinEager` method will be removed in 3.0'
);
return this._eagerAlgorithm(getJoinEagerAlgorithm(this))._eager(exp, modifiers);
}
naiveEager(exp, modifiers) {
deprecate(
'`QueryBuilder#naiveEager` method is deprecated. You should use the `withGraphFetched` method with the `maxBatchSize: 1` option instead. `naiveEager` method will be removed in 3.0'
);
return this._eagerAlgorithm(getNaiveEagerAlgorithm(this))._eager(exp, modifiers);
}
mergeEager(...args) {
deprecate(
'`QueryBuilder#mergeEager` method is deprecated. You should use the `withGraphFetched` method instead. `mergeEager` method will be removed in 3.0'
);
return this._mergeEager(...args);
}
_mergeEager(exp, modifiers) {
const eagerOp = ensureEagerOperation(this);
eagerOp.expression = eagerOp.expression.merge(parseRelationExpression(this.modelClass(), exp));
checkEager(this);
return this.modifiers(modifiers);
}
mergeJoinEager(exp, modifiers) {
deprecate(
'`QueryBuilder#mergeJoinEager` method is deprecated. You should use the `withGraphJoined` method instead. `mergeJoinEager` method will be removed in 3.0'
);
return this._eagerAlgorithm(this.modelClass().JoinEagerAlgorithm)._mergeEager(exp, modifiers);
}
mergeNaiveEager(exp, modifiers) {
deprecate(
'`QueryBuilder#mergeNaiveEager` method is deprecated. You should use the `withGraphFetched` method with the `maxBatchSize: 1` option instead. `mergeNaiveEager` method will be removed in 3.0'
);
return this._eagerAlgorithm(this.modelClass().NaiveEagerAlgorithm)._mergeEager(exp, modifiers);
}
allowEager(exp) {
deprecate(
'`QueryBuilder#allowEager` method is deprecated. You should use `allowGraph` instead. `allowEager` method will be removed in 3.0'
);
return this.clearAllowGraph().allowGraph(exp);
}
allowGraph(exp) {
const currentExpr = this._allowedGraphExpression || RelationExpression.create();
this._allowedGraphExpression = currentExpr.merge(
parseRelationExpression(this.modelClass(), exp)
);
checkEager(this);
return this;
}
allowedGraphExpression() {
return this._allowedGraphExpression;
}
mergeAllowEager(exp) {
deprecate(
'`QueryBuilder#mergeAllowEager` method is deprecated. You should use `allowGraph` instead. `mergeAllowEager` method will be removed in 3.0'
);
return this.allowGraph(exp);
}
eagerObject() {
deprecate(
'`QueryBuilder#eagerObject` method is deprecated. You should use `graphExpressionObject` instead. `eagerObject` method will be removed in 3.0'
);
return this.graphExpressionObject();
}
graphExpressionObject() {
const eagerOp = this.findOperation(EagerOperation);
if (eagerOp && !eagerOp.expression.isEmpty) {
return eagerOp.expression.toPojo();
} else {
return null;
}
}
eagerModifiers() {
deprecate(
'`QueryBuilder#eagerModifiers` method is deprecated. You should use `modifiers` instead. `eagerModifiers` method will be removed in 3.0'
);
return this.modifiers();
}
eagerModifiersAtPath() {
deprecate(
'`QueryBuilder#eagerModifiersAtPath` method is deprecated. You should use `graphModifiersAtPath` instead. `eagerModifiersAtPath` method will be removed in 3.0'
);
return this.graphModifiersAtPath();
}
graphModifiersAtPath() {
const eagerOp = this.findOperation(EagerOperation);
if (eagerOp && !eagerOp.expression.isEmpty) {
return eagerOp.modifiersAtPath.map((it) => Object.assign({}, it));
} else {
return [];
}
}
modifyEager(...args) {
deprecate(
'`QueryBuilder#modifyEager` method is deprecated. You should use `modifyGraph` instead. `modifyEager` method will be removed in 3.0'
);
return this.modifyGraph(...args);
}
modifyGraph(path, modifier) {
const eagerOp = ensureEagerOperation(this);
eagerOp.modifiersAtPath.push({ path, modifier });
return this;
}
filterEager(...args) {
deprecate(
'`QueryBuilder#filterEager` method is deprecated. You should use `modifyGraph` instead. `filterEager` method will be removed in 3.0'
);
return this.modifyGraph(...args);
}
allowUpsert(exp) {
deprecate(
'`QueryBuilder#allowUpsert` method is deprecated. You should use `allowGraph` instead. `allowUpsert` method will be removed in 3.0'
);
return this.clearAllowGraph().allowGraph(exp);
}
allowInsert(exp) {
deprecate(
'`QueryBuilder#allowInsert` method is deprecated. You should use `allowGraph` instead. `allowInsert` method will be removed in 3.0'
);
return this.clearAllowGraph().allowGraph(exp);
}
eagerOptions(...args) {
deprecate(
'`QueryBuilder#eagerOptions` method is deprecated. You should use `withGraphFetched` or `withGraphJoined` and pass options in the second argument. `eagerOptions` method will be removed in 3.0'
);
return this._eagerOptions(...args);
}
_eagerOptions(opt) {
const eagerOp = ensureEagerOperation(this);
eagerOp.graphOptions = Object.assign({}, eagerOp.graphOptions, opt);
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() {
return this.has(QueryBuilderBase.WhereSelector);
}
hasSelects() {
return this.has(QueryBuilderBase.SelectSelector);
}
hasEager() {
deprecate(
`QueryBuilder#hasEager is deprecated. Use hasWithGraph instead. This method will be removed in version 3.0`
);
return this.hasWithGraph();
}
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)) {
return true;
} else {
return false;
}
});
}
isFindQuery() {
deprecate(
`isFindQuery is deprecated. Use isFind instead. This method will be removed in version 2.0`
);
return this.isFind();
}
isEagerQuery() {
deprecate(
`isEagerQuery is deprecated. Use hasWithGraph instead. This method will be removed in version 2.0`
);
return this.hasEager();
}
toString() {
return '[object QueryBuilder]';
}
toFindQuery() {
return this.clone().clear(
(op) => op.is(InsertOperation) || op.is(UpdateOperation) || op.is(DeleteOperation)
);
}
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;
}
clearEager() {
deprecate(
'`QueryBuilder#clearEager` method is deprecated. You should use `clearWithGraph` instead. `clearEager` method will be removed in 3.0'
);
return this.clearWithGraph();
}
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;
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()) {
// Take a clone so that we don't modify this instance during build.
let builder = this.clone();
builder = addImplicitOperations(builder);
builder = callOnBuildHooks(builder);
const queryExecutorOperation = findQueryExecutorOperation(builder);
if (queryExecutorOperation) {
// If the query executor is set, we build the builder that it returns.
return queryExecutorOperation.queryExecutor(builder).toKnexQuery();
} else {
return buildKnexQuery(builder, 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);
}
}
pluck(propertyName) {
deprecate(`QueryBuilder#pluck is deprecated. This method will be removed in version 3.0`);
return this.runAfter((result) => {
if (Array.isArray(result)) {
return result.map((it) => it && it[propertyName]);
} else if (isObject(result)) {
return result[propertyName];
} else {
return result;
}
});
}
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;
});
}
pick(modelClass, properties) {
deprecate(`QueryBuilder#pick is deprecated. This method will be removed in version 3.0`);
if (typeof properties === 'undefined') {
properties = modelClass;
modelClass = null;
}
// Turn the properties into a hash for performance.
properties = properties.reduce((obj, prop) => {
obj[prop] = true;
return obj;
}, {});
return this.traverse(modelClass, (model) => {
model.$pick(properties);
});
}
omit(modelClass, properties) {
deprecate(`QueryBuilder#omit is deprecated. This method will be removed in version 3.0`);
if (typeof properties === 'undefined') {
properties = modelClass;
modelClass = null;
}
if (Array.isArray(properties)) {
// Turn the properties into a hash for performance.
properties = properties.reduce((obj, prop) => {
obj[prop] = true;
return obj;
}, {});
} else {
properties = { [properties]: true };
}
return this.traverse(modelClass, (model) => {
model.$omit(properties);
});
}
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]);
});
}
insertWithRelated(...args) {
deprecate(
`insertWithRelated is deprecated. Use insertGraph instead. This method will be removed in version 2.0`
);
return this.insertGraph(...args);
}
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]);
});
}
insertWithRelatedAndFetch(...args) {
deprecate(
`insertWithRelatedAndFetch is deprecated. Use insertGraphAndFetch instead. This method will be removed in version 2.0`
);
return this.insertGraphAndFetch(...args);
}
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;
}
joinRelation(...args) {
deprecate(
`joinRelation is deprecated. Use joinRelated instead. This method will be removed in version 3.0`
);
return this.joinRelated(...args);
}
innerJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'innerJoin').addCall({
expression,
options,
});
return this;
}
innerJoinRelation(...args) {
deprecate(
`innerJoinRelation is deprecated. Use innerJoinRelated instead. This method will be removed in version 3.0`
);
return this.innerJoinRelated(...args);
}
outerJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'outerJoin').addCall({
expression,
options,
});
return this;
}
outerJoinRelation(...args) {
deprecate(
`outerJoinRelation is deprecated. Use outerJoinRelated instead. This method will be removed in version 3.0`
);
return this.outerJoinRelated(...args);
}
fullOuterJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'fullOuterJoin').addCall({
expression,
options,
});
return this;
}
fullOuterJoinRelation(...args) {
deprecate(
`fullOuterJoinRelation is deprecated. Use fullOuterJoinRelated instead. This method will be removed in version 3.0`
);
return this.fullOuterJoinRelated(...args);
}
leftJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'leftJoin').addCall({
expression,
options,
});
return this;
}
leftJoinRelation(...args) {
deprecate(
`leftJoinRelation is deprecated. Use leftJoinRelated instead. This method will be removed in version 3.0`
);
return this.leftJoinRelated(...args);
}
leftOuterJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'leftOuterJoin').addCall({
expression,
options,
});
return this;
}
leftOuterJoinRelation(...args) {
deprecate(
`leftOuterJoinRelation is deprecated. Use leftOuterJoinRelated instead. This method will be removed in version 3.0`
);
return this.leftOuterJoinRelated(...args);
}
rightJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'rightJoin').addCall({
expression,
options,
});
return this;
}
rightJoinRelation(...args) {
deprecate(
`rightJoinRelation is deprecated. Use rightJoinRelated instead. This method will be removed in version 3.0`
);
return this.rightJoinRelated(...args);
}
rightOuterJoinRelated(expression, options) {
ensureJoinRelatedOperation(this, 'rightOuterJoin').addCall({
expression,
options,
});
return this;
}
rightOuterJoinRelation(...args) {
deprecate(
`rightOuterJoinRelation is deprecated. Use rightOuterJoinRelated instead. This method will be removed in version 3.0`
);
return this.rightOuterJoinRelated(...args);
}
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,
getDefaultEagerAlgorithm(builder)
);
const newEagerOp = new EagerOperationClass('eager', {
defaultGraphOptions,
});
builder.addOperation(newEagerOp);
return newEagerOp;
}
}
}
function getDefaultEagerAlgorithm(builder) {
const eagerAlgorithm = builder.modelClass().defaultEagerAlgorithm;
if (eagerAlgorithm !== getWhereInEagerAlgorithm(builder)) {
deprecate(
'Model.defaultEagerAlgorithm is deprecated. You need to use `withGraphFetched` or `withGraphJoined` explicitly. `defaultEagerAlgorithm` will be removed in 3.0'
);
}
return eagerAlgorithm;
}
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 = chainHooks(promise, builder, builder.context().runBefore);
promise = chainHooks(promise, builder, builder.internalContext().runBefore);
promise = chainOperationHooks(promise, builder, 'onBefore2');
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;
}
function handleExecuteError(builder, err) {
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) =>
builder.callAsyncOperationMethod(op, hookName, [builder, result])
);
}
});
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 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);
const hasSelects = builder.has(QueryBuilderBase.SelectSelector);
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 (!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) => func.call(builder, result, builder));
} else if (Array.isArray(func)) {
func.forEach((func) => {
promise = chainHooks(promise, builder, func);
});
}
return promise;
});
}
function createModels(result, builder) {
const modelClass = builder.resultModelClass();
if (result === null || result === undefined) {
return null;
}
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,
};