From bf1fa631183e143add6be2b35d5a9ee750ae78c8 Mon Sep 17 00:00:00 2001 From: Joel Mukuthu Date: Thu, 1 Feb 2018 23:41:01 +0100 Subject: [PATCH] Add queryContext to schema and query builders (#2314) * feat(query-builder): add hookContext for wrapIdentifier * refactor: use isUndefined * test(transaction): test passing of hookContext * feat(runnner): pass context to postProcessResponse * test(runner): test postProcessResponse for raw responses * test(raw): test passing of hookContext * feat: add hookContext to Raw and SchemaBuilder * test(transaction): fix test for hookContext * chore: fix lint error * fix: check for hookContext before calling it * test(transaction): fix hookContext test * chore: remove whitespace * test(hookContext): test cloning of context object * refactor: hookContext -> queryContext * minor: use more descriptive variable name i.e. refactor: `context` => `queryContext` * fix: remove unnecessary checks for query builder * fix(Raw): pass query builder to formatter * fix(SchemaCompiler): pass schema builder to formatter * refactor: add addQueryContext helper * feat: add queryContext to TableBuilder and ColumnBuilder * fix(TableCompiler): pass table builder to formatter * fix(ColumnCompiler): pass column builder to formatter * fix(pushQuery): fix passing builder to formatter * test(Schema|Table|ColumnCompiler): test passing queryContext * fix(SchemaCompiler): pass queryContext to TableCompiler * fix(TableCompiler): pass queryContext to ColumnCompiler * test: add queryContext tests for all schema dialects * test(TableCompiler): test overwriting queryContext from SchemaCompiler * test(Raw): test passing queryContext to wrapIdentifier * tests: run all the tests --- src/client.js | 18 ++--- src/dialects/mssql/index.js | 2 +- src/dialects/mssql/schema/tablecompiler.js | 4 +- src/dialects/mysql/schema/tablecompiler.js | 6 +- src/dialects/oracle/index.js | 2 +- src/dialects/oracledb/index.js | 2 +- src/formatter.js | 8 +- src/helpers.js | 14 +++- src/query/builder.js | 4 + src/query/compiler.js | 2 +- src/raw.js | 7 +- src/runner.js | 3 +- src/schema/builder.js | 2 + src/schema/columnbuilder.js | 5 +- src/schema/columncompiler.js | 2 +- src/schema/compiler.js | 10 ++- src/schema/helpers.js | 15 +++- src/schema/tablebuilder.js | 2 + src/schema/tablecompiler.js | 15 +++- test/integration/builder/additional.js | 35 ++++++++- test/integration/builder/transaction.js | 59 ++++++++++---- test/unit/query/builder.js | 64 ++++++++++++++- test/unit/schema/mssql.js | 88 +++++++++++++++++++++ test/unit/schema/mysql.js | 90 +++++++++++++++++++++- test/unit/schema/oracle.js | 88 +++++++++++++++++++++ test/unit/schema/oracledb.js | 90 +++++++++++++++++++++- test/unit/schema/postgres.js | 88 +++++++++++++++++++++ test/unit/schema/sqlite3.js | 88 +++++++++++++++++++++ 28 files changed, 758 insertions(+), 55 deletions(-) diff --git a/src/client.js b/src/client.js index a56a67ba28..65d0661062 100644 --- a/src/client.js +++ b/src/client.js @@ -62,8 +62,8 @@ inherits(Client, EventEmitter) assign(Client.prototype, { - formatter() { - return new Formatter(this) + formatter(builder) { + return new Formatter(this, builder) }, queryBuilder() { @@ -163,20 +163,20 @@ assign(Client.prototype, { return sql; }, - postProcessResponse(resp) { + postProcessResponse(resp, queryContext) { if (this.config.postProcessResponse) { - return this.config.postProcessResponse(resp); + return this.config.postProcessResponse(resp, queryContext); } return resp; }, - wrapIdentifier(value) { - return this.customWrapIdentifier(value, this.wrapIdentifierImpl); + wrapIdentifier(value, queryContext) { + return this.customWrapIdentifier(value, this.wrapIdentifierImpl, queryContext); }, - customWrapIdentifier(value, origImpl) { + customWrapIdentifier(value, origImpl, queryContext) { if (this.config.wrapIdentifier) { - return this.config.wrapIdentifier(value, origImpl); + return this.config.wrapIdentifier(value, origImpl, queryContext); } return origImpl(value); }, @@ -232,7 +232,7 @@ assign(Client.prototype, { .catch(err => { // Acquire connection must never reject, because generic-pool // will retry trying to get connection until acquireConnectionTimeout is - // reached. acquireConnectionTimeout should trigger in knex only + // reached. acquireConnectionTimeout should trigger in knex only // in that case if aquiring connection waits because pool is full // https://github.com/coopernurse/node-pool/pull/184 // https://github.com/tgriesser/knex/issues/2325 diff --git a/src/dialects/mssql/index.js b/src/dialects/mssql/index.js index 5039c62fc8..6e9ee05f64 100644 --- a/src/dialects/mssql/index.js +++ b/src/dialects/mssql/index.js @@ -43,7 +43,7 @@ assign(Client_MSSQL.prototype, { }, formatter() { - return new MSSQL_Formatter(this) + return new MSSQL_Formatter(this, ...arguments) }, transaction() { diff --git a/src/dialects/mssql/schema/tablecompiler.js b/src/dialects/mssql/schema/tablecompiler.js index bd6109a6db..231902dbd4 100644 --- a/src/dialects/mssql/schema/tablecompiler.js +++ b/src/dialects/mssql/schema/tablecompiler.js @@ -74,7 +74,7 @@ assign(TableCompiler_MSSQL.prototype, { }, dropFKRefs (runner, refs) { - const formatter = this.client.formatter(); + const formatter = this.client.formatter(this.tableBuilder); return Promise.all(refs.map(function (ref) { const constraintName = formatter.wrap(ref.CONSTRAINT_NAME); const tableName = formatter.wrap(ref.TABLE_NAME); @@ -84,7 +84,7 @@ assign(TableCompiler_MSSQL.prototype, { })); }, createFKRefs (runner, refs) { - const formatter = this.client.formatter(); + const formatter = this.client.formatter(this.tableBuilder); return Promise.all(refs.map(function (ref) { const tableName = formatter.wrap(ref.TABLE_NAME); diff --git a/src/dialects/mysql/schema/tablecompiler.js b/src/dialects/mysql/schema/tablecompiler.js index ba433b7c2b..14a5f8760f 100644 --- a/src/dialects/mysql/schema/tablecompiler.js +++ b/src/dialects/mysql/schema/tablecompiler.js @@ -111,7 +111,7 @@ assign(TableCompiler_MySQL.prototype, { }, getFKRefs (runner) { - const formatter = this.client.formatter(); + const formatter = this.client.formatter(this.tableBuilder); const sql = 'SELECT KCU.CONSTRAINT_NAME, KCU.TABLE_NAME, KCU.COLUMN_NAME, '+ ' KCU.REFERENCED_TABLE_NAME, KCU.REFERENCED_COLUMN_NAME, '+ ' RC.UPDATE_RULE, RC.DELETE_RULE '+ @@ -129,7 +129,7 @@ assign(TableCompiler_MySQL.prototype, { }, dropFKRefs (runner, refs) { - const formatter = this.client.formatter(); + const formatter = this.client.formatter(this.tableBuilder); return Promise.all(refs.map(function (ref) { const constraintName = formatter.wrap(ref.CONSTRAINT_NAME); @@ -140,7 +140,7 @@ assign(TableCompiler_MySQL.prototype, { })); }, createFKRefs (runner, refs) { - const formatter = this.client.formatter(); + const formatter = this.client.formatter(this.tableBuilder); return Promise.all(refs.map(function (ref) { const tableName = formatter.wrap(ref.TABLE_NAME); diff --git a/src/dialects/oracle/index.js b/src/dialects/oracle/index.js index ce2288fe98..e3e3269f8d 100644 --- a/src/dialects/oracle/index.js +++ b/src/dialects/oracle/index.js @@ -41,7 +41,7 @@ assign(Client_Oracle.prototype, { }, formatter() { - return new Formatter(this) + return new Formatter(this, ...arguments) }, queryCompiler() { diff --git a/src/dialects/oracledb/index.js b/src/dialects/oracledb/index.js index 14e3917370..2dac5e1e8e 100644 --- a/src/dialects/oracledb/index.js +++ b/src/dialects/oracledb/index.js @@ -53,7 +53,7 @@ Client_Oracledb.prototype.columnCompiler = function() { return new ColumnCompiler(this, ...arguments) } Client_Oracledb.prototype.formatter = function() { - return new Oracledb_Formatter(this) + return new Oracledb_Formatter(this, ...arguments) } Client_Oracledb.prototype.transaction = function() { return new Transaction(this, ...arguments) diff --git a/src/formatter.js b/src/formatter.js index 61f40caa1b..de8705fca7 100644 --- a/src/formatter.js +++ b/src/formatter.js @@ -27,8 +27,9 @@ const operators = transform([ export default class Formatter { - constructor(client) { + constructor(client, builder) { this.client = client + this.builder = builder this.bindings = [] } @@ -119,7 +120,8 @@ export default class Formatter { } wrapAsIdentifier(value) { - return this.client.wrapIdentifier((value || '').trim()); + const queryContext = this.builder.queryContext(); + return this.client.wrapIdentifier((value || '').trim(), queryContext); } alias(first, second) { @@ -212,7 +214,7 @@ export default class Formatter { if (i === 0 && segments.length > 1) { wrapped.push(this.wrap((value || '').trim())); } else { - wrapped.push(this.client.wrapIdentifier((value || '').trim())); + wrapped.push(this.wrapAsIdentifier(value)); } } return wrapped.join('.'); diff --git a/src/helpers.js b/src/helpers.js index 6152c3b9b6..96ecd40db1 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -72,4 +72,16 @@ export function containsUndefined(mixed) { } return argContainsUndefined; -} \ No newline at end of file +} + +export function addQueryContext(Target) { + // Stores or returns (if called with no arguments) context passed to + // wrapIdentifier and postProcessResponse hooks + Target.prototype.queryContext = function(context) { + if (isUndefined(context)) { + return this._queryContext; + } + this._queryContext = context; + return this; + } +} diff --git a/src/query/builder.js b/src/query/builder.js index 0b5f7ea7e2..719bad5778 100644 --- a/src/query/builder.js +++ b/src/query/builder.js @@ -53,6 +53,9 @@ assign(Builder.prototype, { if (!isUndefined(this._options)) { cloned._options = clone(this._options); } + if (!isUndefined(this._queryContext)) { + cloned._queryContext = clone(this._queryContext); + } return cloned; }, @@ -1002,5 +1005,6 @@ Builder.prototype.del = Builder.prototype.delete // Attach all of the top level promise methods that should be chainable. require('../interface')(Builder); +helpers.addQueryContext(Builder); export default Builder; diff --git a/src/query/compiler.js b/src/query/compiler.js index f38c95bb61..1a3ee8150c 100644 --- a/src/query/compiler.js +++ b/src/query/compiler.js @@ -26,7 +26,7 @@ function QueryCompiler(client, builder) { this.timeout = builder._timeout || false; this.cancelOnTimeout = builder._cancelOnTimeout || false; this.grouped = groupBy(builder._statements, 'grouping'); - this.formatter = client.formatter() + this.formatter = client.formatter(builder) } const components = [ diff --git a/src/raw.js b/src/raw.js index f3e84a01cc..ad4789547d 100644 --- a/src/raw.js +++ b/src/raw.js @@ -14,8 +14,8 @@ import uuid from 'uuid'; const debugBindings = debug('knex:bindings') const fakeClient = { - formatter() { - return new Formatter(fakeClient) + formatter(builder) { + return new Formatter(fakeClient, builder) } } @@ -70,7 +70,7 @@ assign(Raw.prototype, { // Returns the raw sql for the query. toSQL(method, tz) { let obj - const formatter = this.client.formatter() + const formatter = this.client.formatter(this) if (Array.isArray(this.bindings)) { obj = replaceRawArrBindings(this, formatter) @@ -186,5 +186,6 @@ function replaceKeyBindings(raw, formatter) { // Allow the `Raw` object to be utilized with full access to the relevant // promise API. require('./interface')(Raw) +helpers.addQueryContext(Raw); export default Raw diff --git a/src/runner.js b/src/runner.js index 68c7ca0a99..e83e658a05 100644 --- a/src/runner.js +++ b/src/runner.js @@ -132,8 +132,9 @@ assign(Runner.prototype, { return queryPromise .then((resp) => { const processedResponse = this.client.processResponse(resp, runner); + const queryContext = this.builder.queryContext(); const postProcessedResponse = this.client - .postProcessResponse(processedResponse); + .postProcessResponse(processedResponse, queryContext); this.builder.emit( 'query-response', diff --git a/src/schema/builder.js b/src/schema/builder.js index 28c23f7568..bda8f454dc 100644 --- a/src/schema/builder.js +++ b/src/schema/builder.js @@ -2,6 +2,7 @@ import inherits from 'inherits'; import { EventEmitter } from 'events'; import { each, toArray } from 'lodash' +import { addQueryContext } from '../helpers'; // Constructor for the builder instance, typically called from // `knex.builder`, accepting the current `knex` instance, @@ -47,6 +48,7 @@ each([ }) require('../interface')(SchemaBuilder) +addQueryContext(SchemaBuilder); SchemaBuilder.prototype.withSchema = function(schemaName) { this._schema = schemaName; diff --git a/src/schema/columnbuilder.js b/src/schema/columnbuilder.js index 5b349b911c..a45e10ff63 100644 --- a/src/schema/columnbuilder.js +++ b/src/schema/columnbuilder.js @@ -1,5 +1,6 @@ -import { extend, each, toArray } from 'lodash' +import { extend, each, toArray } from 'lodash'; +import { addQueryContext } from '../helpers'; // The chainable interface off the original "column" method. export default function ColumnBuilder(client, tableBuilder, type, args) { @@ -41,6 +42,8 @@ each(modifiers, function(method) { }; }); +addQueryContext(ColumnBuilder); + ColumnBuilder.prototype.notNull = ColumnBuilder.prototype.notNullable = function notNullable() { return this.nullable(false) diff --git a/src/schema/columncompiler.js b/src/schema/columncompiler.js index 63c2c5ee2a..1dba273fc5 100644 --- a/src/schema/columncompiler.js +++ b/src/schema/columncompiler.js @@ -16,7 +16,7 @@ function ColumnCompiler(client, tableCompiler, columnBuilder) { this.grouped = groupBy(columnBuilder._statements, 'grouping'); this.modified = columnBuilder._modifiers; this.isIncrements = (this.type.indexOf('increments') !== -1); - this.formatter = client.formatter(); + this.formatter = client.formatter(columnBuilder); this.sequence = []; this.modifiers = []; } diff --git a/src/schema/compiler.js b/src/schema/compiler.js index 7f701ee2d2..61dda1db61 100644 --- a/src/schema/compiler.js +++ b/src/schema/compiler.js @@ -1,7 +1,7 @@ import { pushQuery, pushAdditional } from './helpers'; -import { assign } from 'lodash' +import { assign, isUndefined } from 'lodash' // The "SchemaCompiler" takes all of the query statements which have been // gathered in the "SchemaBuilder" and turns them into an array of @@ -10,7 +10,7 @@ function SchemaCompiler(client, builder) { this.builder = builder this.client = client this.schema = builder._schema; - this.formatter = client.formatter() + this.formatter = client.formatter(builder) this.sequence = [] } @@ -60,6 +60,12 @@ function buildTable(type) { return function(tableName, fn) { const builder = this.client.tableBuilder(type, tableName, fn); + // pass queryContext down to tableBuilder but do not overwrite it if already set + const queryContext = this.builder.queryContext(); + if (!isUndefined(queryContext) && isUndefined(builder.queryContext())) { + builder.queryContext(queryContext); + } + builder.setSchema(this.schema); const sql = builder.toSQL(); diff --git a/src/schema/helpers.js b/src/schema/helpers.js index b87b1b1282..0df3fd6ad6 100644 --- a/src/schema/helpers.js +++ b/src/schema/helpers.js @@ -1,5 +1,8 @@ import { isString, tail } from 'lodash' +import ColumnCompiler from './columncompiler' +import TableCompiler from './tablecompiler' +import SchemaCompiler from './compiler' // Push a new query onto the compiled "sequence" stack, // creating a new formatter, returning the compiler. @@ -12,7 +15,17 @@ export function pushQuery(query) { query.bindings = this.formatter.bindings; } this.sequence.push(query); - this.formatter = this.client.formatter(); + + let builder; + if (this instanceof ColumnCompiler) { + builder = this.columnBuilder; + } else if (this instanceof TableCompiler) { + builder = this.tableBuilder; + } else if (this instanceof SchemaCompiler) { + builder = this.builder; + } + + this.formatter = this.client.formatter(builder); } // Used in cases where we need to push some additional column specific statements. diff --git a/src/schema/tablebuilder.js b/src/schema/tablebuilder.js index 6f66a5e1ab..e7e00b684d 100644 --- a/src/schema/tablebuilder.js +++ b/src/schema/tablebuilder.js @@ -85,6 +85,8 @@ each(specialMethods, function(methods, dialect) { }); }); +helpers.addQueryContext(TableBuilder); + // Each of the column types that we can add, we create a new ColumnBuilder // instance and push it onto the statements array. const columnTypes = [ diff --git a/src/schema/tablecompiler.js b/src/schema/tablecompiler.js index 99aa5453af..54894629f2 100644 --- a/src/schema/tablecompiler.js +++ b/src/schema/tablecompiler.js @@ -4,16 +4,17 @@ // ------- import { pushAdditional, pushQuery } from './helpers'; import * as helpers from '../helpers'; -import { groupBy, reduce, map, first, tail, isEmpty, indexOf, isArray } from 'lodash' +import { groupBy, reduce, map, first, tail, isEmpty, indexOf, isArray, isUndefined } from 'lodash' function TableCompiler(client, tableBuilder) { this.client = client + this.tableBuilder = tableBuilder; this.method = tableBuilder._method; this.schemaNameRaw = tableBuilder._schemaName; this.tableNameRaw = tableBuilder._tableName; this.single = tableBuilder._single; this.grouped = groupBy(tableBuilder._statements, 'grouping'); - this.formatter = client.formatter(); + this.formatter = client.formatter(tableBuilder); this.sequence = []; this._formatting = client.config && client.config.formatting } @@ -143,9 +144,17 @@ TableCompiler.prototype.getColumns = function (method) { const columns = this.grouped.columns || []; method = method || 'add'; + const queryContext = this.tableBuilder.queryContext(); + return columns .filter(column => column.builder._method === method) - .map(column => this.client.columnCompiler(this, column.builder)); + .map(column => { + // pass queryContext down to columnBuilder but do not overwrite it if already set + if (!isUndefined(queryContext) && isUndefined(column.builder.queryContext())) { + column.builder.queryContext(queryContext); + } + return this.client.columnCompiler(this, column.builder); + }); }; TableCompiler.prototype.tableName = function () { diff --git a/test/integration/builder/additional.js b/test/integration/builder/additional.js index f2a66b9f36..6c859a472b 100644 --- a/test/integration/builder/additional.js +++ b/test/integration/builder/additional.js @@ -13,8 +13,9 @@ module.exports = function(knex) { describe("Custom response processing", () => { before('setup custom response handler', () => { - knex.client.config.postProcessResponse = (response) => { + knex.client.config.postProcessResponse = (response, queryContext) => { response.callCount = response.callCount ? (response.callCount + 1) : 1; + response.queryContext = queryContext; return response; }; }); @@ -29,11 +30,29 @@ module.exports = function(knex) { }); }); + it('should pass query context to the custom handler', () => { + return knex('accounts') + .queryContext('the context') + .limit(1) + .then(res => { + expect(res.queryContext).to.equal('the context'); + }); + }); + it('should process raw response', () => { return knex.raw('select * from ??', ['accounts']).then(res => { + expect(res.callCount).to.equal(1); }); }); + it('should pass query context for raw responses', () => { + return knex.raw('select * from ??', ['accounts']) + .queryContext('the context') + .then(res => { + expect(res.queryContext).to.equal('the context'); + }); + }); + it('should process response done in transaction', () => { return knex.transaction(trx => { return trx('accounts').limit(1).then(res => { @@ -44,6 +63,20 @@ module.exports = function(knex) { expect(res.callCount).to.equal(1); }); }); + + it('should pass query context for responses from transactions', () => { + return knex.transaction(trx => { + return trx('accounts') + .queryContext('the context') + .limit(1) + .then(res => { + expect(res.queryContext).to.equal('the context'); + return res; + }); + }).then(res => { + expect(res.queryContext).to.equal('the context'); + }); + }); }); describe('columnInfo with wrapIdentifier and postProcessResponse', () => { diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js index d11f61e792..829229ebbb 100644 --- a/test/integration/builder/transaction.js +++ b/test/integration/builder/transaction.js @@ -5,6 +5,7 @@ var Promise = testPromise; var Knex = require('../../../knex'); var _ = require('lodash'); +var sinon = require('sinon') module.exports = function(knex) { @@ -165,18 +166,18 @@ module.exports = function(knex) { if (knex.client.dialect === 'postgresql') { return knex.transaction(function(trx) { return trx.schema.createTable('test_schema_transactions', function(table) { - table.increments(); - table.string('name'); - table.timestamps(); - }).then(function() { - return trx('test_schema_transactions').insert({name: 'bob'}); - }).then(function() { - return trx('test_schema_transactions').count('*'); - }).then(function(resp) { - var _count = parseInt(resp[0].count, 10); - expect(_count).to.equal(1); - throw err; - }); + table.increments(); + table.string('name'); + table.timestamps(); + }).then(function() { + return trx('test_schema_transactions').insert({name: 'bob'}); + }).then(function() { + return trx('test_schema_transactions').count('*'); + }).then(function(resp) { + var _count = parseInt(resp[0].count, 10); + expect(_count).to.equal(1); + throw err; + }); }) .on('query', function(obj) { count++; @@ -272,8 +273,8 @@ module.exports = function(knex) { } return knex.transaction(function(trx) { - trx.select('*').from('accounts').then(trx.commit).catch(trx.rollback); - }) + trx.select('*').from('accounts').then(trx.commit).catch(trx.rollback); + }) .then(expectQueryEventToHaveBeenTriggered) .catch(expectQueryEventToHaveBeenTriggered); @@ -427,7 +428,35 @@ module.exports = function(knex) { .spread(function (ret1, ret2) { expect(ret1).to.equal(ret2); }); - }); + }); + + it('should pass the query context to wrapIdentifier', function () { + const originalWrapIdentifier = knex.client.config.wrapIdentifier; + const spy = sinon.spy().named('calledWithContext'); + + function restoreWrapIdentifier() { + knex.client.config.wrapIdentifier = originalWrapIdentifier; + } + knex.client.config.wrapIdentifier = (value, wrap, queryContext) => { + spy(queryContext); + return wrap(value); + }; + + return knex.transaction(function(trx) { + return trx + .select() + .from('accounts') + .queryContext({ foo: 'bar' }); + }).then(function() { + expect(spy.callCount).to.equal(1); + expect(spy.calledWith({ foo: 'bar' })).to.equal(true); + }).then(function() { + restoreWrapIdentifier(); + }).catch(function(e) { + restoreWrapIdentifier(); + throw e; + }); + }); }); }; diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js index 3e97f47ffd..f75ed96419 100644 --- a/test/unit/query/builder.js +++ b/test/unit/query/builder.js @@ -93,8 +93,12 @@ function testquery(chain, valuesToCheck, selectedClients) { describe("Custom identifier wrapping", function() { var customWrapperConfig = { - wrapIdentifier: (value, clientImpl) => { - return clientImpl(value + '_wrapper_was_here'); + wrapIdentifier: (value, clientImpl, context) => { + var suffix = '_wrapper_was_here'; + if (context && context.fancy) { + suffix = '_fancy_wrapper_was_here'; + } + return clientImpl(value + suffix); } }; @@ -116,7 +120,61 @@ describe("Custom identifier wrapping", function() { postgres: 'select "users_wrapper_was_here"."foo_wrapper_was_here" as "bar_wrapper_was_here" from "schema_wrapper_was_here"."users_wrapper_was_here"', sqlite3: 'select `users_wrapper_was_here`.`foo_wrapper_was_here` as `bar_wrapper_was_here` from `schema_wrapper_was_here`.`users_wrapper_was_here`' }, clientsWithCustomIdentifierWrapper); - }) + }); + + describe('queryContext', () => { + it('should pass the query context to the custom wrapper', () => { + testsql(qb().withSchema('schema').select('users.foo as bar').from('users').queryContext({ fancy: true }), { + mysql: 'select `users_fancy_wrapper_was_here`.`foo_fancy_wrapper_was_here` as `bar_fancy_wrapper_was_here` from `schema_fancy_wrapper_was_here`.`users_fancy_wrapper_was_here`', + oracle: 'select "users_fancy_wrapper_was_here"."foo_fancy_wrapper_was_here" "bar_fancy_wrapper_was_here" from "schema_fancy_wrapper_was_here"."users_fancy_wrapper_was_here"', + mssql: 'select [users_fancy_wrapper_was_here].[foo_fancy_wrapper_was_here] as [bar_fancy_wrapper_was_here] from [schema_fancy_wrapper_was_here].[users_fancy_wrapper_was_here]', + oracledb: 'select "users_fancy_wrapper_was_here"."foo_fancy_wrapper_was_here" "bar_fancy_wrapper_was_here" from "schema_fancy_wrapper_was_here"."users_fancy_wrapper_was_here"', + postgres: 'select "users_fancy_wrapper_was_here"."foo_fancy_wrapper_was_here" as "bar_fancy_wrapper_was_here" from "schema_fancy_wrapper_was_here"."users_fancy_wrapper_was_here"', + sqlite3: 'select `users_fancy_wrapper_was_here`.`foo_fancy_wrapper_was_here` as `bar_fancy_wrapper_was_here` from `schema_fancy_wrapper_was_here`.`users_fancy_wrapper_was_here`' + }, clientsWithCustomIdentifierWrapper); + }); + + it('should pass the query context for raw queries', () => { + testsql(qb().select(raw('??', [{ a: 'col1' }]).queryContext({ fancy: true })).from('users').queryContext({ fancy: true }), { + mysql: 'select `col1_fancy_wrapper_was_here` as `a_fancy_wrapper_was_here` from `users_fancy_wrapper_was_here`', + oracle: 'select "col1_fancy_wrapper_was_here" "a_fancy_wrapper_was_here" from "users_fancy_wrapper_was_here"', + mssql: 'select [col1_fancy_wrapper_was_here] as [a_fancy_wrapper_was_here] from [users_fancy_wrapper_was_here]', + oracledb: 'select "col1_fancy_wrapper_was_here" "a_fancy_wrapper_was_here" from "users_fancy_wrapper_was_here"', + postgres: 'select "col1_fancy_wrapper_was_here" as "a_fancy_wrapper_was_here" from "users_fancy_wrapper_was_here"', + sqlite3: 'select `col1_fancy_wrapper_was_here` as `a_fancy_wrapper_was_here` from `users_fancy_wrapper_was_here`' + }, clientsWithCustomIdentifierWrapper); + }); + + it('should allow chaining', () => { + var builder = qb(); + expect(builder.queryContext({ foo: 'foo' })).to.deep.equal(builder); + }); + + it('should return the query context if called with no arguments', () => { + expect(qb().queryContext({ foo: 'foo' }).queryContext()).to.deep.equal({ foo: 'foo' }); + }); + + describe('when a builder is cloned', () => { + it('should copy the query context', () => { + expect(qb().queryContext({ foo: 'foo' }).clone().queryContext()).to.deep.equal({ foo: 'foo' }); + }); + + it('should not modify the original query context if the clone is modified', () => { + var original = qb().queryContext({ foo: 'foo' }); + var clone = original.clone().queryContext({ foo: 'bar' }); + expect(original.queryContext()).to.deep.equal({ foo: 'foo' }); + expect(clone.queryContext()).to.deep.equal({ foo: 'bar' }); + }); + + it('should only shallow clone the query context', () => { + var original = qb().queryContext({ foo: { bar: 'baz' } }); + var clone = original.clone(); + clone.queryContext().foo.bar = 'quux'; + expect(original.queryContext()).to.deep.equal({ foo: { bar: 'quux' } }); + expect(clone.queryContext()).to.deep.equal({ foo: { bar: 'quux' } }); + }); + }); + }); }); describe("QueryBuilder", function() { diff --git a/test/unit/schema/mssql.js b/test/unit/schema/mssql.js index b487a09de2..82beab99d3 100644 --- a/test/unit/schema/mssql.js +++ b/test/unit/schema/mssql.js @@ -2,6 +2,7 @@ 'use strict'; +var sinon = require('sinon'); var MSSQL_Client = require('../../../lib/dialects/mssql'); var client = new MSSQL_Client(); @@ -555,4 +556,91 @@ describe("MSSQL SchemaBuilder", function() { expect(tableSql[0].sql).to.equal('CREATE TABLE [users] ([test] nvarchar(255), CONSTRAINT [testconstraintname] PRIMARY KEY ([test]))'); }); + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('table context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); + }); diff --git a/test/unit/schema/mysql.js b/test/unit/schema/mysql.js index 6a19797dfa..230f071fb1 100644 --- a/test/unit/schema/mysql.js +++ b/test/unit/schema/mysql.js @@ -2,6 +2,7 @@ 'use strict'; +var sinon = require('sinon'); var MySQL_Client = require('../../../lib/dialects/mysql'); var Maria_Client = require('../../../lib/dialects/maria'); var MySQL2_Client = require('../../../lib/dialects/mysql2'); @@ -422,7 +423,7 @@ describe(dialect + " SchemaBuilder", function() { equal(1, tableSql.length); expect(tableSql[0].sql).to.equal('alter table `users` add `foo` decimal(5, 2)'); }); - + it('test adding decimal, no precision', function() { expect(() => { tableSql = client.schemaBuilder().table('users', function() { @@ -607,6 +608,93 @@ describe(dialect + " SchemaBuilder", function() { expect(tableSql[1].sql).to.equal('alter table `users` add primary key `testconstraintname`(`test`)'); }); + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'schema context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'schema context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'schema context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); + }); diff --git a/test/unit/schema/oracle.js b/test/unit/schema/oracle.js index 1d85d2c212..5a732cff43 100644 --- a/test/unit/schema/oracle.js +++ b/test/unit/schema/oracle.js @@ -2,6 +2,7 @@ 'use strict'; +var sinon = require('sinon'); var Oracle_Client = require('../../../lib/dialects/oracle'); var client = new Oracle_Client({}) @@ -579,4 +580,91 @@ describe("Oracle SchemaBuilder", function() { expect(tableSql[1].sql).to.equal('alter table "users" add constraint "testconstraintname" primary key ("test")'); }); + + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('table context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); }); diff --git a/test/unit/schema/oracledb.js b/test/unit/schema/oracledb.js index d47057aa98..708e4ece14 100644 --- a/test/unit/schema/oracledb.js +++ b/test/unit/schema/oracledb.js @@ -2,6 +2,7 @@ 'use strict'; +var sinon = require('sinon'); var Oracle_Client = require('../../../lib/dialects/oracledb'); var client = new Oracle_Client({}) @@ -385,7 +386,7 @@ describe("OracleDb SchemaBuilder", function() { equal(1, tableSql.length); expect(tableSql[0].sql).to.equal('alter table "users" add "foo" decimal(5, 2)'); }); - + it("adding decimal, variable precision", function() { tableSql = client.schemaBuilder().table('users', function(table) { table.decimal('foo', null); @@ -569,4 +570,91 @@ describe("OracleDb SchemaBuilder", function() { expect(tableSql[1].sql).to.equal('alter table "users" add constraint "testconstraintname" primary key ("test")'); }); + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('table context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); + }); diff --git a/test/unit/schema/postgres.js b/test/unit/schema/postgres.js index 029a0c507a..c9dbe4124d 100644 --- a/test/unit/schema/postgres.js +++ b/test/unit/schema/postgres.js @@ -4,6 +4,7 @@ var tableSql; +var sinon = require('sinon'); var PG_Client = require('../../../lib/dialects/postgres'); var client = new PG_Client({}); var knex = require('../../../knex'); @@ -700,4 +701,91 @@ describe("PostgreSQL SchemaBuilder", function() { expect(tableSql[1].sql).to.equal('alter table "users" add constraint "testconstraintname" primary key ("test")'); }); + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('table context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); + }); diff --git a/test/unit/schema/sqlite3.js b/test/unit/schema/sqlite3.js index 69aabfbb78..c0bedc5134 100644 --- a/test/unit/schema/sqlite3.js +++ b/test/unit/schema/sqlite3.js @@ -4,6 +4,7 @@ var tableSql; +var sinon = require('sinon'); var SQLite3_Client = require('../../../lib/dialects/sqlite3'); var client = new SQLite3_Client({}) var SQLite3_DDL = require('../../../lib/dialects/sqlite3/schema/ddl') @@ -468,4 +469,91 @@ describe("SQLite SchemaBuilder", function() { }).toSQL(); }); }); + + describe('queryContext', function () { + let spy; + let originalWrapIdentifier; + + before(function () { + spy = sinon.spy(); + originalWrapIdentifier = client.config.wrapIdentifier; + client.config.wrapIdentifier = function (value, wrap, queryContext) { + spy(value, queryContext); + return wrap(value); + }; + }); + + beforeEach(function () { + spy.reset(); + }); + + after(function () { + client.config.wrapIdentifier = originalWrapIdentifier; + }); + + it('SchemaCompiler passes queryContext to wrapIdentifier via TableCompiler', function () { + client + .schemaBuilder() + .queryContext('table context') + .createTable('users', function (table) { + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('TableCompiler passes queryContext to wrapIdentifier', function () { + client + .schemaBuilder() + .createTable('users', function (table) { + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', undefined]); + }); + + it('TableCompiler allows overwriting queryContext from SchemaCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id'); + table.string('email'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'table context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'table context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + + it('ColumnCompiler allows overwriting queryContext from TableCompiler', function () { + client + .schemaBuilder() + .queryContext('schema context') + .createTable('users', function (table) { + table.queryContext('table context'); + table.increments('id').queryContext('id context'); + table.string('email').queryContext('email context'); + }) + .toSQL(); + + expect(spy.callCount).to.equal(3); + expect(spy.firstCall.args).to.deep.equal(['id', 'id context']); + expect(spy.secondCall.args).to.deep.equal(['email', 'email context']); + expect(spy.thirdCall.args).to.deep.equal(['users', 'table context']); + }); + }); });