const clone = require('lodash/clone'); const isEmpty = require('lodash/isEmpty'); const { callbackify } = require('util'); const finallyMixin = require('./util/finally-mixin'); const { formatQuery } = require('./execution/internal/query-executioner'); function augmentWithBuilderInterface(Target) { Target.prototype.toQuery = function (tz) { let data = this.toSQL(this._method, tz); if (!Array.isArray(data)) data = [data]; if (!data.length) { return ''; } return data .map((statement) => { return formatQuery(statement.sql, statement.bindings, tz, this.client); }) .reduce((a, c) => a.concat(a.endsWith(';') ? '\n' : ';\n', c)); }; // Create a new instance of the `Runner`, passing in the current object. Target.prototype.then = function (/* onFulfilled, onRejected */) { let result = this.client.runner(this).run(); if (this.client.config.asyncStackTraces) { result = result.catch((err) => { err.originalStack = err.stack; const firstLine = err.stack.split('\n')[0]; // a hack to get a callstack into the client code despite this // node.js bug https://github.com/nodejs/node/issues/11865 // see lib/util/save-async-stack.js for more details const { error, lines } = this._asyncStack; const stackByLines = error.stack.split('\n'); const asyncStack = stackByLines.slice(lines); asyncStack.unshift(firstLine); // put the fake more helpful "async" stack on the thrown error err.stack = asyncStack.join('\n'); throw err; }); } return result.then.apply(result, arguments); }; // Add additional "options" to the builder. Typically used for client specific // items, like the `mysql` and `sqlite3` drivers. Target.prototype.options = function (opts) { this._options = this._options || []; this._options.push(clone(opts) || {}); return this; }; // Sets an explicit "connection" we wish to use for this query. Target.prototype.connection = function (connection) { this._connection = connection; this.client.processPassedConnection(connection); return this; }; // Set a debug flag for the current schema query stack. Target.prototype.debug = function (enabled) { this._debug = arguments.length ? enabled : true; return this; }; // Set the transaction object for this query. Target.prototype.transacting = function (transaction) { if (transaction && transaction.client) { if (!transaction.client.transacting) { transaction.client.logger.warn( `Invalid transaction value: ${transaction.client}` ); } else { this.client = transaction.client; } } if (isEmpty(transaction)) { this.client.logger.error( 'Invalid value on transacting call, potential bug' ); throw Error( 'Invalid transacting value (null, undefined or empty object)' ); } return this; }; // Initializes a stream. Target.prototype.stream = function (options) { return this.client.runner(this).stream(options); }; // Initialize a stream & pipe automatically. Target.prototype.pipe = function (writable, options) { return this.client.runner(this).pipe(writable, options); }; Target.prototype.asCallback = function (cb) { const promise = this.then(); callbackify(() => promise)(cb); return promise; }; Target.prototype.catch = function (onReject) { return this.then().catch(onReject); }; Object.defineProperty(Target.prototype, Symbol.toStringTag, { get: () => 'object', }); finallyMixin(Target.prototype); } module.exports = { augmentWithBuilderInterface, };