Fix PG string escaping behavior (#1661)

* Modify test suite to test #1602
We shouldn’t be testing the “default” client class. Replace any
usages with postgresql

* Simplify knex.raw, deprecate global Knex.raw usage

* Remove unused bluebird shim

* Remove old / unused test comments

* Don't capture Pool2 error event

* Fix pg string escaping & parameterization #1602, #1548
This commit is contained in:
Tim Griesser 2016-09-12 18:01:47 -04:00 committed by GitHub
parent 57a67cdadc
commit ee217ce2a1
17 changed files with 460 additions and 670 deletions

View File

@ -5,6 +5,9 @@
- Upgrade to Babel 6, #1617
- Reference Bluebird module directly, remove deprecated .exec method, #1618
- Remove documentation files from main repo
- Fix broken behavior on WebSQL build, #1638
- Oracle id sequence now handles manual inserts, #906
- Cleanup PG escaping, fix #1602, #1548
# 0.11.10 - 9 Aug, 2016

View File

@ -87,7 +87,6 @@
"web": "https://github.com/tgriesser"
},
"browser": {
"bluebird/js/main/promise": "./lib/util/bluebird.js",
"./lib/migrate/index.js": "./lib/util/noop.js",
"./lib/bin/cli.js": "./lib/util/noop.js",
"./lib/seed/index.js": "./lib/util/noop.js",

View File

@ -20,8 +20,8 @@ import ColumnCompiler from './schema/columncompiler';
import Pool2 from 'pool2';
import inherits from 'inherits';
import { EventEmitter } from 'events';
import SqlString from './query/string';
import { makeEscape } from './query/string'
import { assign, uniqueId, cloneDeep } from 'lodash'
const debug = require('debug')('knex:client')
@ -107,8 +107,6 @@ assign(Client.prototype, {
return new this.Runner(this, connection)
},
SqlString,
Transaction,
transaction(container, config, outerTx) {
@ -122,12 +120,34 @@ assign(Client.prototype, {
return raw.set.apply(raw, arguments)
},
_formatQuery(sql, bindings, timeZone) {
bindings = bindings == null ? [] : [].concat(bindings);
let index = 0;
return sql.replace(/\\?\?/g, (match) => {
if (match === '\\?') {
return '?'
}
if (index === bindings.length) {
return match
}
const value = bindings[index++];
return this._escapeBinding(value, {timeZone})
})
},
_escapeBinding: makeEscape({
escapeString(str) {
return `'${str.replace(/'/g, "''")}'`
}
}),
query(connection, obj) {
if (typeof obj === 'string') obj = {sql: obj}
this.emit('query', assign({__knexUid: connection.__knexUid}, obj))
obj.bindings = this.prepBindings(obj.bindings)
debugQuery(obj.sql)
return this._query.call(this, connection, obj).catch((err) => {
err.message = SqlString.format(obj.sql, obj.bindings) + ' - ' + err.message
this.emit('query', assign({__knexUid: connection.__knexUid}, obj))
return this._query(connection, obj).catch((err) => {
err.message = this._formatQuery(obj.sql, obj.bindings) + ' - ' + err.message
this.emit('query-error', err, assign({__knexUid: connection.__knexUid}, obj))
throw err
})
@ -137,7 +157,8 @@ assign(Client.prototype, {
if (typeof obj === 'string') obj = {sql: obj}
this.emit('query', assign({__knexUid: connection.__knexUid}, obj))
debugQuery(obj.sql)
return this._stream.call(this, connection, obj, stream, options)
obj.bindings = this.prepBindings(obj.bindings)
return this._stream(connection, obj, stream, options)
},
prepBindings(bindings) {
@ -156,14 +177,9 @@ assign(Client.prototype, {
}
},
Pool: Pool2,
initializePool(config) {
if (this.pool) this.destroy()
this.pool = new this.Pool(assign(this.poolDefaults(config.pool || {}), config.pool))
this.pool.on('error', function(err) {
helpers.error(`Pool2 - ${err}`)
})
this.pool = new Pool2(assign(this.poolDefaults(config.pool || {}), config.pool))
this.pool.on('warn', function(msg) {
helpers.warn(`Pool2 - ${msg}`)
})

View File

@ -4,7 +4,6 @@
import inherits from 'inherits';
import Client_MySQL from '../mysql';
import Promise from 'bluebird';
import SqlString from '../../query/string';
import * as helpers from '../../helpers';
import Transaction from './transaction';
@ -78,9 +77,9 @@ assign(Client_MariaSQL.prototype, {
// and any other necessary prep work.
_query(connection, obj) {
const tz = this.connectionSettings.timezone || 'local';
return new Promise(function(resolver, rejecter) {
return new Promise((resolver, rejecter) => {
if (!obj.sql) return resolver()
const sql = SqlString.format(obj.sql, obj.bindings, tz);
const sql = this._formatQuery(obj.sql, obj.bindings, tz)
connection.query(sql, function (err, rows) {
if (err) {
return rejecter(err);

View File

@ -14,6 +14,7 @@ import TableCompiler from './schema/tablecompiler';
import ColumnCompiler from './schema/columncompiler';
import { assign, map } from 'lodash'
import { makeEscape } from '../../query/string'
// Always initialize with the "QueryBuilder" and "QueryCompiler"
// objects, which extend the base 'lib/query/builder' and
@ -43,6 +44,8 @@ assign(Client_MySQL.prototype, {
Transaction,
_escapeBinding: makeEscape(),
wrapIdentifier(value) {
return (value !== '*' ? `\`${value.replace(/`/g, '``')}\`` : '*')
},

View File

@ -8,7 +8,7 @@ import Formatter from './formatter';
import Client from '../../client';
import Promise from 'bluebird';
import * as helpers from '../../helpers';
import SqlString from '../../query/string';
import {bufferToString} from '../../query/string';
import Transaction from './transaction';
import QueryCompiler from './query/compiler';
@ -61,7 +61,7 @@ assign(Client_Oracle.prototype, {
return value ? 1 : 0
}
else if (Buffer.isBuffer(value)) {
return SqlString.bufferToString(value)
return bufferToString(value)
}
return value
})

View File

@ -5,12 +5,12 @@ import { assign, map, extend } from 'lodash'
import inherits from 'inherits';
import Client from '../../client';
import Promise from 'bluebird';
import * as utils from './utils';
import QueryCompiler from './query/compiler';
import ColumnCompiler from './schema/columncompiler';
import TableCompiler from './schema/tablecompiler';
import SchemaCompiler from './schema/compiler';
import {makeEscape} from '../../query/string'
function Client_PG(config) {
Client.apply(this, arguments)
@ -42,6 +42,43 @@ assign(Client_PG.prototype, {
return require('pg')
},
_escapeBinding: makeEscape({
escapeArray(val, esc) {
return '{' + val.map(esc).join(',') + '}'
},
escapeString(str) {
let hasBackslash = false
let escaped = '\''
for (let i = 0; i < str.length; i++) {
const c = str[i]
if (c === '\'') {
escaped += c + c
} else if (c === '\\') {
escaped += c + c
hasBackslash = true
} else {
escaped += c
}
}
escaped += '\''
if (hasBackslash === true) {
escaped = 'E' + escaped
}
return escaped
},
escapeObject(val, timezone, prepareValue, seen = []) {
if (val && typeof val.toPostgres === 'function') {
seen = seen || [];
if (seen.indexOf(val) !== -1) {
throw new Error(`circular reference detected while preparing "${val}" for query`);
}
seen.push(val);
return prepareValue(val.toPostgres(prepareValue), seen);
}
return JSON.stringify(val);
}
}),
wrapIdentifier(value) {
if (value === '*') return value;
const matched = value.match(/(.*?)(\[[0-9]\])/);
@ -49,13 +86,6 @@ assign(Client_PG.prototype, {
return `"${value.replace(/"/g, '""')}"`;
},
// Prep the bindings as needed by PostgreSQL.
prepBindings(bindings, tz) {
return map(bindings, (binding) => {
return utils.prepareValue(binding, tz, this.valueForUndefined)
});
},
// Get a raw connection, called by the `pool` whenever a new
// connection needs to be added to the pool.
acquireRawConnection() {

View File

@ -1,100 +0,0 @@
function dateToString(date) {
function pad(number, digits) {
number = number.toString();
while (number.length < digits) {
number = `0${number}`;
}
return number;
}
let offset = -date.getTimezoneOffset();
let ret = pad(date.getFullYear(), 4) + '-' +
pad(date.getMonth() + 1, 2) + '-' +
pad(date.getDate(), 2) + 'T' +
pad(date.getHours(), 2) + ':' +
pad(date.getMinutes(), 2) + ':' +
pad(date.getSeconds(), 2) + '.' +
pad(date.getMilliseconds(), 3);
if (offset < 0) {
ret += "-";
offset *= -1;
} else {
ret += "+";
}
return ret + pad(Math.floor(offset / 60), 2) + ":" + pad(offset % 60, 2);
}
let prepareObject;
let arrayString;
// converts values from javascript types
// to their 'raw' counterparts for use as a postgres parameter
// note: you can override this function to provide your own conversion mechanism
// for complex types, etc...
const prepareValue = function (val, seen /*, valueForUndefined*/) {
if (val instanceof Buffer) {
return val;
}
if (val instanceof Date) {
return dateToString(val);
}
if (Array.isArray(val)) {
return arrayString(val);
}
if (val === null) {
return null;
}
if (typeof val === 'object') {
return prepareObject(val, seen);
}
return val.toString();
};
prepareObject = function prepareObject(val, seen) {
if (val && typeof val.toPostgres === 'function') {
seen = seen || [];
if (seen.indexOf(val) !== -1) {
throw new Error(`circular reference detected while preparing "${val}" for query`);
}
seen.push(val);
return prepareValue(val.toPostgres(prepareValue), seen);
}
return JSON.stringify(val);
};
// convert a JS array to a postgres array literal
// uses comma separator so won't work for types like box that use
// a different array separator.
arrayString = function arrayString(val) {
return '{' + val.map(function (elem) {
if (elem === null || elem === undefined) {
return 'NULL';
}
if (Array.isArray(elem)) {
return arrayString(elem);
}
return JSON.stringify(prepareValue(elem));
}).join(',') + '}';
};
function normalizeQueryConfig(config, values, callback) {
//can take in strings or config objects
config = (typeof config === 'string') ? { text: config } : config;
if (values) {
if (typeof values === 'function') {
config.callback = values;
} else {
config.values = values;
}
}
if (callback) {
config.callback = callback;
}
return config;
}
export { prepareValue, normalizeQueryConfig };

View File

@ -45,12 +45,9 @@ Knex.VERSION = require('../package.json').version
// Run a "raw" query, though we can't do anything with it other than put
// it in a query statement.
Knex.raw = (sql, bindings) => new Raw({}).set(sql, bindings)
// Create a new "knex" instance with the appropriate configured client.
Knex.initialize = function(config) {
warn('knex.initialize is deprecated, pass your config object directly to the knex module')
return new Knex(config)
Knex.raw = (sql, bindings) => {
warn('global Knex.raw is deprecated, use knex.raw (chain off an initialized knex object)')
return new Raw().set(sql, bindings)
}
// Bluebird

View File

@ -8,15 +8,10 @@ export default function(Target) {
let data = this.toSQL(this._method, tz);
if (!isArray(data)) data = [data];
return map(data, (statement) => {
return this._formatQuery(statement.sql, statement.bindings, tz);
return this.client._formatQuery(statement.sql, statement.bindings, tz);
}).join(';\n');
};
// Format the query as sql, prepping bindings as necessary.
Target.prototype._formatQuery = function(sql, bindings, tz) {
return this.client.SqlString.format(sql, bindings, tz);
};
// Create a new instance of the `Runner`, passing in the current object.
Target.prototype.then = function(/* onFulfilled, onRejected */) {
const result = this.client.runner(this).run()
@ -28,7 +23,6 @@ export default function(Target) {
Target.prototype.options = function(opts) {
this._options = this._options || [];
this._options.push(clone(opts) || {});
this._cached = undefined
return this;
};

View File

@ -69,8 +69,6 @@ assign(QueryCompiler.prototype, {
);
}
defaults.bindings = this.client.prepBindings(defaults.bindings, tz);
return assign(defaults, val);
},

View File

@ -1,134 +1,161 @@
import * as helpers from '../helpers';
/*eslint max-len: 0, no-var:0 */
const SqlString = {};
export { SqlString as default };
export const charsRegex = /[\0\b\t\n\r\x1a\"\'\\]/g; // eslint-disable-line no-control-regex
export const charsMap = {
'\0': '\\0',
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\r': '\\r',
'\x1a': '\\Z',
'"': '\\"',
'\'': '\\\'',
'\\': '\\\\'
}
SqlString.escape = function(val, timeZone) {
// Can't do require on top of file because Raw has not yet been initialized
// when this file is executed for the first time.
const Raw = require('../raw')
function wrapEscape(escapeFn) {
return function finalEscape(val, ctx = {}) {
return escapeFn(val, finalEscape, ctx)
}
}
if (val === null || val === undefined) {
return 'NULL';
export function makeEscape(config = {}) {
const finalEscapeDate = config.escapeDate || dateToString
const finalEscapeArray = config.escapeArray || arrayToList
const finalEscapeBuffer = config.escapeBuffer || bufferToString
const finalEscapeString = config.escapeString || escapeString
const finalEscapeObject = config.escapeObject || escapeObject
const finalWrap = config.wrap || wrapEscape
function escapeFn(val, finalEscape, ctx) {
if (val === undefined || val === null) {
return 'NULL';
}
switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false';
case 'number': return val+'';
case 'object':
if (val instanceof Date) {
val = finalEscapeDate(val, finalEscape, ctx);
} else if (Array.isArray(val)) {
return finalEscapeArray(val, finalEscape, ctx)
} else if (Buffer.isBuffer(val)) {
return finalEscapeBuffer(val, finalEscape, ctx);
} else {
return finalEscapeObject(val, finalEscape, ctx)
}
}
return finalEscapeString(val, finalEscape, ctx)
}
switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false';
case 'number': return val+'';
}
return finalWrap ? finalWrap(escapeFn) : escapeFn
}
if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || 'local');
export function escapeObject(val, finalEscape, ctx) {
if (typeof val.toSQL === 'function') {
return val.toSQL(ctx)
} else {
return JSON.stringify(val)
}
}
if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val);
}
if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone);
}
if (val instanceof Raw) {
return val;
}
if (typeof val === 'object') {
try {
val = JSON.stringify(val)
} catch (e) {
helpers.warn(e)
val = val + ''
export function arrayToList(array, finalEscape, ctx) {
var sql = '';
for (var i = 0; i < array.length; i++) {
var val = array[i];
if (Array.isArray(val)) {
sql += (i === 0 ? '' : ', ') + '(' + arrayToList(val, finalEscape, ctx) + ')';
} else {
sql += (i === 0 ? '' : ', ') + finalEscape(val, ctx);
}
}
return sql;
}
val = val.replace(/(\\\?)|[\0\n\r\b\t\\\'\x1a]/g, function(s) {
switch(s) {
case "\0": return "\\0";
case "\n": return "\\n";
case "\r": return "\\r";
case "\b": return "\\b";
case "\t": return "\\t";
case "\x1a": return "\\Z";
case "\\?": return "?";
case "\'": return "''";
default: return `\\${s}`;
}
});
return `'${val}'`;
};
export function bufferToString(buffer) {
return "X" + escapeString(buffer.toString('hex'));
}
SqlString.arrayToList = function(array, timeZone) {
const self = this;
return array.map(function(v) {
if (Array.isArray(v)) return `(${SqlString.arrayToList(v, timeZone)})`;
return self.escape(v, timeZone);
}).join(', ');
};
export function escapeString(val, finalEscape, ctx) {
var chunkIndex = charsRegex.lastIndex = 0;
var escapedVal = '';
var match;
SqlString.format = function(sql, values, timeZone) {
const self = this;
values = values == null ? [] : [].concat(values);
let index = 0;
return sql.replace(/\\?\?/g, function(match) {
if (match === '\\?') return match;
if (index === values.length) {
return match;
}
const value = values[index++];
return self.escape(value, timeZone)
}).replace('\\?', '?');
};
while ((match = charsRegex.exec(val))) {
escapedVal += val.slice(chunkIndex, match.index) + charsMap[match[0]];
chunkIndex = charsRegex.lastIndex;
}
SqlString.dateToString = function(date, timeZone) {
const dt = new Date(date);
if (chunkIndex === 0) {
// Nothing was escaped
return "'" + val + "'";
}
if (timeZone !== 'local') {
const tz = convertTimezone(timeZone);
if (chunkIndex < val.length) {
return "'" + escapedVal + val.slice(chunkIndex) + "'";
}
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
if (tz !== false) {
return "'" + escapedVal + "'";
}
export function dateToString(date, finalEscape, ctx) {
const timeZone = ctx.timeZone || 'local'
var dt = new Date(date);
var year;
var month;
var day;
var hour;
var minute;
var second;
var millisecond;
if (timeZone === 'local') {
year = dt.getFullYear();
month = dt.getMonth() + 1;
day = dt.getDate();
hour = dt.getHours();
minute = dt.getMinutes();
second = dt.getSeconds();
millisecond = dt.getMilliseconds();
} else {
var tz = convertTimezone(timeZone);
if (tz !== false && tz !== 0) {
dt.setTime(dt.getTime() + (tz * 60000));
}
year = dt.getUTCFullYear();
month = dt.getUTCMonth() + 1;
day = dt.getUTCDate();
hour = dt.getUTCHours();
minute = dt.getUTCMinutes();
second = dt.getUTCSeconds();
millisecond = dt.getUTCMilliseconds();
}
const year = dt.getFullYear();
const month = zeroPad(dt.getMonth() + 1, 2);
const day = zeroPad(dt.getDate(), 2);
const hour = zeroPad(dt.getHours(), 2);
const minute = zeroPad(dt.getMinutes(), 2);
const second = zeroPad(dt.getSeconds(), 2);
const millisecond = zeroPad(dt.getMilliseconds(), 3);
return (
year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' +
second + '.' + millisecond
);
};
SqlString.bufferToString = function bufferToString(buffer) {
return `X'${buffer.toString('hex')}'`;
// YYYY-MM-DD HH:mm:ss.mmm
return zeroPad(year, 4) + '-' + zeroPad(month, 2) + '-' + zeroPad(day, 2) + ' ' +
zeroPad(hour, 2) + ':' + zeroPad(minute, 2) + ':' + zeroPad(second, 2) + '.' +
zeroPad(millisecond, 3);
}
function zeroPad(number, length) {
number = number.toString();
while (number.length < length) {
number = `0${number}`;
number = '0' + number;
}
return number;
}
function convertTimezone(tz) {
if (tz === "Z") return 0;
if (tz === 'Z') {
return 0;
}
const m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
if (m) {
return (
(m[1] === '-' ? -1 : 1) *
(parseInt(m[2], 10) +
((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60
);
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
}
return false;
}

View File

@ -6,15 +6,21 @@ import * as helpers from './helpers';
import { EventEmitter } from 'events';
import { assign, reduce, isPlainObject, isObject, isUndefined, isNumber } from 'lodash'
import Formatter from './formatter'
import uuid from 'node-uuid';
function Raw(client) {
const fakeClient = {
formatter() {
return new Formatter(fakeClient)
}
}
function Raw(client = fakeClient) {
this.client = client
this.sql = ''
this.bindings = []
this._cached = undefined
// Todo: Deprecate
this._wrappedBefore = undefined
@ -26,7 +32,6 @@ inherits(Raw, EventEmitter)
assign(Raw.prototype, {
set(sql, bindings) {
this._cached = undefined
this.sql = sql
this.bindings = (
(isObject(bindings) && !bindings.toSQL) ||
@ -37,7 +42,7 @@ assign(Raw.prototype, {
},
timeout(ms, {cancel} = {}) {
if(isNumber(ms) && ms > 0) {
if (isNumber(ms) && ms > 0) {
this._timeout = ms;
if (cancel) {
this.client.assertCanCancelQuery();
@ -49,7 +54,6 @@ assign(Raw.prototype, {
// Wraps the current sql with `before` and `after`.
wrap(before, after) {
this._cached = undefined
this._wrappedBefore = before
this._wrappedAfter = after
return this
@ -62,53 +66,56 @@ assign(Raw.prototype, {
// Returns the raw sql for the query.
toSQL(method, tz) {
if (this._cached) return this._cached
let obj
const formatter = this.client.formatter()
if (Array.isArray(this.bindings)) {
this._cached = replaceRawArrBindings(this)
obj = replaceRawArrBindings(this, formatter)
} else if (this.bindings && isPlainObject(this.bindings)) {
this._cached = replaceKeyBindings(this)
obj = replaceKeyBindings(this, formatter)
} else {
this._cached = {
obj = {
method: 'raw',
sql: this.sql,
bindings: isUndefined(this.bindings) ? void 0 : [this.bindings]
bindings: isUndefined(this.bindings) ? [] : [this.bindings]
}
}
if (this._wrappedBefore) {
this._cached.sql = this._wrappedBefore + this._cached.sql
obj.sql = this._wrappedBefore + obj.sql
}
if (this._wrappedAfter) {
this._cached.sql = this._cached.sql + this._wrappedAfter
obj.sql = obj.sql + this._wrappedAfter
}
this._cached.options = reduce(this._options, assign, {})
if(this._timeout) {
this._cached.timeout = this._timeout;
obj.options = reduce(this._options, assign, {})
if (this._timeout) {
obj.timeout = this._timeout;
if (this._cancelOnTimeout) {
this._cached.cancelOnTimeout = this._cancelOnTimeout;
obj.cancelOnTimeout = this._cancelOnTimeout;
}
}
if(this.client && this.client.prepBindings) {
this._cached.bindings = this._cached.bindings || [];
if(helpers.containsUndefined(this._cached.bindings)) {
throw new Error(
`Undefined binding(s) detected when compiling RAW query: ` +
this._cached.sql
);
}
this._cached.bindings = this.client.prepBindings(this._cached.bindings, tz);
obj.bindings = obj.bindings || [];
if (helpers.containsUndefined(obj.bindings)) {
throw new Error(
`Undefined binding(s) detected when compiling RAW query: ` +
obj.sql
);
}
this._cached.__knexQueryUid = uuid.v4();
return this._cached
obj.__knexQueryUid = uuid.v4();
return obj
}
})
function replaceRawArrBindings(raw) {
function replaceRawArrBindings(raw, formatter) {
const expectedBindings = raw.bindings.length
const values = raw.bindings
const { client } = raw
let index = 0;
let bindings = []
const sql = raw.sql.replace(/\\?\?\??/g, function(match) {
if (match === '\\?') {
@ -117,17 +124,10 @@ function replaceRawArrBindings(raw) {
const value = values[index++]
if (value && typeof value.toSQL === 'function') {
const bindingSQL = value.toSQL()
bindings = bindings.concat(bindingSQL.bindings)
return bindingSQL.sql
}
if (match === '??') {
return client.formatter().columnize(value)
return formatter.columnize(value)
}
bindings.push(value)
return '?'
return formatter.parameter(value)
})
if (expectedBindings !== index) {
@ -137,14 +137,14 @@ function replaceRawArrBindings(raw) {
return {
method: 'raw',
sql,
bindings
bindings: formatter.bindings
}
}
function replaceKeyBindings(raw) {
function replaceKeyBindings(raw, formatter) {
const values = raw.bindings
const { client } = raw
let { sql } = raw, bindings = []
let { sql } = raw
const regex = /\\?(:\w+:?)/g
sql = raw.sql.replace(regex, function(full, part) {
@ -155,26 +155,22 @@ function replaceKeyBindings(raw) {
const key = full.trim();
const isIdentifier = key[key.length - 1] === ':'
const value = isIdentifier ? values[key.slice(1, -1)] : values[key.slice(1)]
if (value === undefined) {
bindings.push(value);
formatter.bindings.push(value);
return full;
}
if (value && typeof value.toSQL === 'function') {
const bindingSQL = value.toSQL()
bindings = bindings.concat(bindingSQL.bindings)
return full.replace(key, bindingSQL.sql)
}
if (isIdentifier) {
return full.replace(key, client.formatter().columnize(value))
return full.replace(key, formatter.columnize(value))
}
bindings.push(value)
return full.replace(key, '?')
return full.replace(key, formatter.parameter(value))
})
return {
method: 'raw',
sql,
bindings
bindings: formatter.bindings
}
}

View File

@ -1,3 +0,0 @@
// Use this shim module rather than "bluebird/js/main/promise"
// when bundling for client
export default () => require('bluebird')

View File

@ -33,6 +33,5 @@ describe('Query Building Tests', function() {
describe('Integration Tests', function() {
this.timeout(process.env.KNEX_TEST_TIMEOUT || 5000);
require('./integration')
})

View File

@ -84,8 +84,3 @@ test('raw bindings are optional, #853', function(t) {
})
test('raw with no client should still be able to run', function(t) {
t.plan(1)
t.equal(new Raw().set('select * from ?', [raw('table')]).toSQL().sql, 'select * from table')
});

File diff suppressed because it is too large Load Diff