mirror of
https://github.com/knex/knex.git
synced 2025-11-02 10:49:39 +00:00
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:
parent
57a67cdadc
commit
ee217ce2a1
@ -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
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}`)
|
||||
})
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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, '``')}\`` : '*')
|
||||
},
|
||||
|
||||
@ -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
|
||||
})
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 };
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -69,8 +69,6 @@ assign(QueryCompiler.prototype, {
|
||||
);
|
||||
}
|
||||
|
||||
defaults.bindings = this.client.prepBindings(defaults.bindings, tz);
|
||||
|
||||
return assign(defaults, val);
|
||||
},
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
102
src/raw.js
102
src/raw.js
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Use this shim module rather than "bluebird/js/main/promise"
|
||||
// when bundling for client
|
||||
export default () => require('bluebird')
|
||||
@ -33,6 +33,5 @@ describe('Query Building Tests', function() {
|
||||
|
||||
describe('Integration Tests', function() {
|
||||
this.timeout(process.env.KNEX_TEST_TIMEOUT || 5000);
|
||||
|
||||
require('./integration')
|
||||
})
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user