const once = require('../../utils/objectUtils').once; const QueryBuilderOperation = require('./QueryBuilderOperation'); const { isPlainObject, isObject } = require('../../utils/objectUtils'); const { isKnexQueryBuilder, isKnexJoinBuilder, isKnexRaw } = require('../../utils/knexUtils'); const getQueryBuilderBase = once(() => require('../QueryBuilderBase')); const getJoinBuilder = once(() => require('../JoinBuilder')); // An abstract operation base class that converts all arguments from objection types // to knex types. For example objection query builders are converted into knex query // builders and objection RawBuilder instances are converted into knex Raw instances. class ObjectionToKnexConvertingOperation extends QueryBuilderOperation { constructor(name, opt) { super(name, opt); this.args = null; } onAdd(builder, args) { this.args = args; return shouldBeAdded(this.name, builder, args); } onBuild(builder) { this.args = convertArgs(this.name, builder, this.args); } } function shouldBeAdded(opName, builder, args) { const skipUndefined = builder.internalOptions().skipUndefined; for (let i = 0, l = args.length; i < l; ++i) { const arg = args[i]; if (isUndefined(arg)) { if (skipUndefined) { return false; } else { throw new Error( `undefined passed as argument #${i} for '${opName}' operation. Call skipUndefined() method to ignore the undefined values.` ); } } } return true; } function convertArgs(opName, builder, args) { const skipUndefined = builder.internalOptions().skipUndefined; const out = new Array(args.length); for (let i = 0, l = args.length; i < l; ++i) { const arg = args[i]; if (hasToKnexRawMethod(arg)) { out[i] = convertToKnexRaw(arg, builder); } else if (isObjectionQueryBuilderBase(arg)) { out[i] = convertQueryBuilderBase(arg, builder); } else if (isArray(arg)) { out[i] = convertArray(arg, builder, i, opName, skipUndefined); } else if (isFunction(arg)) { out[i] = convertFunction(arg, builder); } else if (isModel(arg)) { out[i] = convertModel(arg); } else if (isPlainObject(arg)) { out[i] = convertPlainObject(arg, builder, i, opName, skipUndefined); } else { out[i] = arg; } } return out; } function isUndefined(item) { return item === undefined; } function hasToKnexRawMethod(item) { return isObject(item) && typeof item.toKnexRaw === 'function'; } function convertToKnexRaw(item, builder) { return item.toKnexRaw(builder.knex()); } function isObjectionQueryBuilderBase(item) { return isObject(item) && item.isObjectionQueryBuilderBase === true; } function convertQueryBuilderBase(item, builder) { return item.subqueryOf(builder).build(); } function isArray(item) { return Array.isArray(item); } function convertArray(arr, builder, i, opName, skipUndefined) { const out = []; for (let j = 0, l = arr.length; j < l; ++j) { const item = arr[j]; if (item === undefined) { if (!skipUndefined) { throw new Error( `undefined passed as an item in argument #${i} for '${opName}' operation. Call skipUndefined() method to ignore the undefined values.` ); } } else if (hasToKnexRawMethod(item)) { out.push(convertToKnexRaw(item, builder)); } else if (isObjectionQueryBuilderBase(item)) { out.push(convertQueryBuilderBase(item)); } else { out.push(item); } } return out; } function isFunction(item) { return typeof item === 'function'; } function convertFunction(func, builder) { return function convertedKnexArgumentFunction() { if (isKnexQueryBuilder(this)) { convertQueryBuilderFunction(this, func, builder); } else if (isKnexJoinBuilder(this)) { convertJoinBuilderFunction(this, func, builder); } else { return func.apply(this, arguments); } }; } function convertQueryBuilderFunction(knexQueryBuilder, func, builder) { const QueryBuilderBase = getQueryBuilderBase(); const convertedQueryBuilder = new QueryBuilderBase(builder.knex()); convertedQueryBuilder.subqueryOf(builder); func.call(convertedQueryBuilder, convertedQueryBuilder); convertedQueryBuilder.buildInto(knexQueryBuilder); } function convertJoinBuilderFunction(knexJoinBuilder, func, builder) { const JoinBuilder = getJoinBuilder(); const joinClauseBuilder = new JoinBuilder(builder.knex()); joinClauseBuilder.subqueryOf(builder); func.call(joinClauseBuilder, joinClauseBuilder); joinClauseBuilder.buildInto(knexJoinBuilder); } function isModel(item) { return isObject(item) && item.$isObjectionModel; } function convertModel(model) { return model.$toDatabaseJson(); } function convertPlainObject(obj, builder, i, opName, skipUndefined) { const out = {}; const keys = Object.keys(obj); for (let j = 0, l = keys.length; j < l; ++j) { const key = keys[j]; const item = obj[key]; if (item === undefined) { if (!skipUndefined) { throw new Error( `undefined passed as a property in argument #${i} for '${opName}' operation. Call skipUndefined() method to ignore the undefined values.` ); } } else if (hasToKnexRawMethod(item)) { out[key] = convertToKnexRaw(item, builder); } else if (isObjectionQueryBuilderBase(item)) { out[key] = convertQueryBuilderBase(item, builder); } else { out[key] = item; } } return out; } module.exports = ObjectionToKnexConvertingOperation;