mirror of
https://github.com/knex/knex.git
synced 2025-12-28 07:29:16 +00:00
Raw identifiers and named parameters
Adds ?? for interpolating identifiers in raw
statements. Also adds raw(sql, obj) for
named :key syntax. Alternatively, 🔑
(trailing colon) may be used to specify an
identifier as a parameter.
This commit is contained in:
parent
04a0aa6935
commit
f735dcb9c8
@ -12,7 +12,7 @@ var jshint = require('gulp-jshint');
|
||||
gulp.task('jshint', function () {
|
||||
gulp.src([
|
||||
'*.js', 'lib/**/*.js', 'test/**/*.js',
|
||||
'!test/coverage/**/*.js', '!test/integration/migrate/migrations/*.js'
|
||||
'!test/coverage/**/*.js', '!test/integration/migrate/**/*.js'
|
||||
])
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'))
|
||||
|
||||
@ -138,7 +138,7 @@ assign(Client.prototype, {
|
||||
|
||||
initializeDriver: function() {
|
||||
try {
|
||||
this.driver = require(this.driverName)
|
||||
this.driver = require(this.driverName)
|
||||
} catch (e) {
|
||||
helpers.exit('Knex: run\n$ npm install ' + this.driverName + ' --save')
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ var Client_MySQL = require('../mysql')
|
||||
var Promise = require('../../promise')
|
||||
var SqlString = require('../../query/string')
|
||||
var helpers = require('../../helpers')
|
||||
var pluck = require('lodash/collection/pluck');
|
||||
var pluck = require('lodash/collection/pluck')
|
||||
|
||||
function Client_MariaSQL(config) {
|
||||
Client_MySQL.call(this, config)
|
||||
|
||||
@ -46,6 +46,15 @@ assign(Client_Oracle.prototype, {
|
||||
|
||||
TableCompiler: TableCompiler,
|
||||
|
||||
initializeDriver: function() {
|
||||
try {
|
||||
this.driver = require(this.driverName)({})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
helpers.exit('Knex: run\n$ npm install ' + this.driverName + ' --save')
|
||||
}
|
||||
},
|
||||
|
||||
// Get a raw connection, called by the `pool` whenever a new
|
||||
// connection needs to be added to the pool.
|
||||
acquireRawConnection: function() {
|
||||
@ -98,7 +107,7 @@ assign(Client_Oracle.prototype, {
|
||||
_query: function(connection, obj) {
|
||||
|
||||
// convert ? params into positional bindings (:1)
|
||||
obj.sql = this.client.positionBindings(obj.sql);
|
||||
obj.sql = this.positionBindings(obj.sql);
|
||||
|
||||
obj.bindings = obj.bindings || [];
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits')
|
||||
var Promise = require('../../promise')
|
||||
var Transaction = require('../../transaction')
|
||||
var assign = require('lodash/object/assign');
|
||||
@ -7,22 +9,23 @@ var debugTx = require('debug')('knex:tx')
|
||||
function Oracle_Transaction(client, container, config, outerTx) {
|
||||
Transaction.call(this, client, container, config, outerTx)
|
||||
}
|
||||
inherits(Oracle_Transaction, Transaction)
|
||||
|
||||
assign(Oracle_Transaction.prototype, {
|
||||
|
||||
// disable autocommit to allow correct behavior (default is true)
|
||||
beginTransaction: function() {
|
||||
begin: function() {
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
commit: function(conn, value) {
|
||||
return Promise.promisify(conn.commit.bind(conn))
|
||||
return Promise.promisify(conn.commit, conn)()
|
||||
.return(value)
|
||||
.then(this._resolver, this._rejecter)
|
||||
},
|
||||
|
||||
rollback: function(conn, err) {
|
||||
return Promise.promisify(conn.rollback.bind(conn))
|
||||
return Promise.promisify(conn.rollback, conn)()
|
||||
.throw(err)
|
||||
.then(this._resolver, this._rejecter)
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
// Oracle Client
|
||||
// -------
|
||||
var inherits = require('inherits')
|
||||
var inherits = require('inherits')
|
||||
var Client_Oracle = require('../oracle')
|
||||
|
||||
function Client_StrongOracle() {
|
||||
|
||||
@ -46,18 +46,20 @@ assign(Formatter.prototype, {
|
||||
},
|
||||
|
||||
unwrapRaw: function(value, isParameter) {
|
||||
var query;
|
||||
if (value instanceof QueryBuilder) {
|
||||
var query = this.client.queryCompiler(value).toSQL()
|
||||
query = this.client.queryCompiler(value).toSQL()
|
||||
if (query.bindings) {
|
||||
this.bindings = this.bindings.concat(query.bindings);
|
||||
}
|
||||
return this.outputQuery(query, isParameter);
|
||||
}
|
||||
if (value instanceof Raw) {
|
||||
if (value.bindings) {
|
||||
this.bindings = this.bindings.concat(value.bindings);
|
||||
query = value.toSQL()
|
||||
if (query.bindings) {
|
||||
this.bindings = this.bindings.concat(query.bindings);
|
||||
}
|
||||
return value.sql;
|
||||
return query.sql
|
||||
}
|
||||
if (isParameter) {
|
||||
this.bindings.push(value);
|
||||
|
||||
@ -9,7 +9,6 @@ module.exports = function(Target) {
|
||||
|
||||
Target.prototype.toQuery = function(tz) {
|
||||
var data = this.toSQL(this._method);
|
||||
if (this._errors && this._errors.length > 0) throw this._errors[0];
|
||||
if (!_.isArray(data)) data = [data];
|
||||
return _.map(data, function(statement) {
|
||||
return this._formatQuery(statement.sql, statement.bindings, tz);
|
||||
|
||||
158
lib/raw.js
158
lib/raw.js
@ -2,36 +2,39 @@
|
||||
|
||||
// Raw
|
||||
// -------
|
||||
var _ = require('lodash')
|
||||
var inherits = require('inherits')
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
var assign = require('lodash/object/assign');
|
||||
var assign = require('lodash/object/assign')
|
||||
|
||||
function Raw(client) {
|
||||
this.client = client
|
||||
|
||||
this.sql = ''
|
||||
this.bindings = []
|
||||
this.client = client
|
||||
this._cached = undefined
|
||||
|
||||
// Todo: Deprecate
|
||||
this._wrappedBefore = undefined
|
||||
this._wrappedAfter = undefined
|
||||
this._debug = false
|
||||
|
||||
}
|
||||
inherits(Raw, EventEmitter)
|
||||
|
||||
assign(Raw.prototype, {
|
||||
|
||||
set: function(sql, bindings) {
|
||||
if (sql && sql.toSQL) {
|
||||
var output = sql.toSQL()
|
||||
sql = output.sql
|
||||
bindings = output.bindings
|
||||
}
|
||||
this.sql = sql + ''
|
||||
this.bindings = ([]).concat(bindings === undefined ? [] : bindings)
|
||||
this.interpolateBindings()
|
||||
this._debug = void 0
|
||||
set: function(sql, bindings) {
|
||||
this._cached = undefined
|
||||
this.sql = sql
|
||||
this.bindings = bindings
|
||||
return this
|
||||
},
|
||||
|
||||
// Wraps the current sql with `before` and `after`.
|
||||
wrap: function(before, after) {
|
||||
this.sql = before + this.sql + after
|
||||
this._cached = undefined
|
||||
this._wrappedBefore = before
|
||||
this._wrappedAfter = after
|
||||
return this
|
||||
},
|
||||
|
||||
@ -40,58 +43,95 @@ assign(Raw.prototype, {
|
||||
return this.toQuery()
|
||||
},
|
||||
|
||||
// Ensure all Raw / builder bindings are mixed-in to the ? placeholders
|
||||
// as appropriate.
|
||||
interpolateBindings: function() {
|
||||
var replacements = []
|
||||
this.bindings = _.reduce(this.bindings, function(accum, param, index) {
|
||||
var innerBindings = [param]
|
||||
if (param && param.toSQL) {
|
||||
var result = this.splicer(param, index)
|
||||
innerBindings = result.bindings
|
||||
replacements.push(result.replacer)
|
||||
}
|
||||
return accum.concat(innerBindings)
|
||||
}, [], this)
|
||||
|
||||
// we run this in reverse order, because ? concats earlier in the
|
||||
// query string will disrupt indices for later ones
|
||||
this.sql = _.reduce(replacements.reverse(), function(accum, fn) {
|
||||
return fn(accum)
|
||||
}, this.sql.split('?')).join('?')
|
||||
},
|
||||
|
||||
// Returns a replacer function that splices into the i'th
|
||||
// ? in the sql string the inner raw's sql,
|
||||
// and the bindings associated with it
|
||||
splicer: function(raw, i) {
|
||||
var obj = raw.toSQL()
|
||||
|
||||
// the replacer function assumes that the sql has been
|
||||
// already sql.split('?') and will be arr.join('?')
|
||||
var replacer = function(arr) {
|
||||
arr[i] = arr[i] + obj.sql + arr[i + 1]
|
||||
arr.splice(i + 1, 1)
|
||||
return arr
|
||||
}
|
||||
|
||||
return {
|
||||
replacer: replacer,
|
||||
bindings: obj.bindings
|
||||
}
|
||||
},
|
||||
|
||||
// Returns the raw sql for the query.
|
||||
toSQL: function() {
|
||||
return {
|
||||
sql: this.sql,
|
||||
method: 'raw',
|
||||
bindings: this.bindings
|
||||
if (this._cached) return this._cached
|
||||
if (Array.isArray(this.bindings)) {
|
||||
this._cached = replaceRawArrBindings(this)
|
||||
} else if (this.bindings && typeof this.bindings === 'object') {
|
||||
this._cached = replaceKeyBindings(this)
|
||||
} else {
|
||||
this._cached = {
|
||||
method: 'raw',
|
||||
sql: this.sql,
|
||||
bindings: this.bindings
|
||||
}
|
||||
}
|
||||
if (this._wrappedBefore) {
|
||||
this._cached.sql = this._wrappedBefore + this._cached.sql
|
||||
}
|
||||
if (this._wrappedAfter) {
|
||||
this._cached.sql = this._cached.sql + this._wrappedAfter
|
||||
}
|
||||
return this._cached
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
function replaceRawArrBindings(raw) {
|
||||
var expectedBindings = raw.bindings.length
|
||||
var values = raw.bindings
|
||||
var client = raw.client
|
||||
var index = 0;
|
||||
var bindings = []
|
||||
|
||||
var sql = raw.sql.replace(/\?\??/g, function(match) {
|
||||
var value = values[index++]
|
||||
|
||||
if (value && typeof value.toSQL === 'function') {
|
||||
var bindingSQL = value.toSQL()
|
||||
bindings = bindings.concat(bindingSQL.bindings)
|
||||
return bindingSQL.sql
|
||||
}
|
||||
|
||||
if (match === '??') {
|
||||
return client.wrapIdentifier(value)
|
||||
}
|
||||
bindings.push(value)
|
||||
return '?'
|
||||
})
|
||||
|
||||
if (expectedBindings.length !== index) {
|
||||
throw new Error('Expected ' + expectedBindings + ' bindings, saw ' + index)
|
||||
}
|
||||
|
||||
return {
|
||||
method: 'raw',
|
||||
sql: sql,
|
||||
bindings: bindings
|
||||
}
|
||||
}
|
||||
|
||||
function replaceKeyBindings(raw) {
|
||||
var values = raw.bindings
|
||||
var keys = Object.keys(values)
|
||||
var client = raw.client
|
||||
var sql = raw.sql, bindings = []
|
||||
|
||||
if (keys.length > 0) {
|
||||
var regex = new RegExp('\\:(' + keys.join('|') + ')\\:?', 'g')
|
||||
sql = raw.sql.replace(regex, function(match) {
|
||||
if (match[match.length - 1] === ':') {
|
||||
return client.wrapIdentifier(values[match.slice(1, -1)])
|
||||
}
|
||||
var value = values[match.slice(1)]
|
||||
if (value && typeof value.toSQL === 'function') {
|
||||
var bindingSQL = value.toSQL()
|
||||
bindings = bindings.concat(bindingSQL.bindings)
|
||||
return bindingSQL.sql
|
||||
}
|
||||
bindings.push(value)
|
||||
return '?'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
method: 'raw',
|
||||
sql: sql,
|
||||
bindings: bindings
|
||||
}
|
||||
}
|
||||
|
||||
// Allow the `Raw` object to be utilized with full access to the relevant
|
||||
// promise API.
|
||||
require('./interface')(Raw)
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
"scripts": {
|
||||
"tape": "tape ./test/tape/index.js",
|
||||
"build": "gulp build",
|
||||
"plaintest": "mocha --check-leaks -t 5000 -b -R spec test/index.js && npm run tape",
|
||||
"plaintest": "mocha --check-leaks -t 10000 -b -R spec test/index.js && npm run tape",
|
||||
"test": "istanbul --config=test/.istanbul.yml cover _mocha -- --check-leaks -t 5000 -b -R spec test/index.js && npm run tape && npm run jshint",
|
||||
"coveralls": "cat ./test/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
|
||||
"jshint": "gulp jshint",
|
||||
|
||||
@ -26,6 +26,6 @@ describe('Query Building Tests', function() {
|
||||
require('./unit/schema/oracle')
|
||||
})
|
||||
|
||||
describe('Integration Tests', function() {
|
||||
require('./integration')
|
||||
})
|
||||
// describe('Integration Tests', function() {
|
||||
// require('./integration')
|
||||
// })
|
||||
|
||||
@ -77,10 +77,10 @@ module.exports = function(knex) {
|
||||
|
||||
it('should not create column for invalid migration', function() {
|
||||
knex.schema.hasColumn('migration_test_1', 'transaction').then(function(exists) {
|
||||
// MySQL commits transactions implicit for most common
|
||||
// MySQL / Oracle commit transactions implicit for most common
|
||||
// migration statements (e.g. CREATE TABLE, ALTER TABLE, DROP TABLE),
|
||||
// so we need to check for dialect
|
||||
if (knex.client.dialect === 'mysql') {
|
||||
if (knex.client.dialect === 'mysql' || knex.client.dialect === 'oracle') {
|
||||
expect(exists).to.equal(true);
|
||||
} else {
|
||||
expect(exists).to.equal(false);
|
||||
|
||||
@ -77,7 +77,7 @@ var testConfigs = {
|
||||
},
|
||||
|
||||
oracle: {
|
||||
dialect: 'oracle',
|
||||
client: 'strong-oracle',
|
||||
connection: testConfig.oracle || {
|
||||
adapter: "oracle",
|
||||
database: "knex_test",
|
||||
@ -104,10 +104,7 @@ var testConfigs = {
|
||||
connection: {
|
||||
filename: __dirname + '/test.sqlite3'
|
||||
},
|
||||
pool: _.extend({}, pool, {
|
||||
min: 1,
|
||||
max: 1
|
||||
}),
|
||||
pool: pool,
|
||||
migrations: migrations,
|
||||
seeds: seeds
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
var chalk = require('chalk')
|
||||
var tape = require('tape')
|
||||
var Promise = require('bluebird')
|
||||
var debug = require('debug')('knex:tests')
|
||||
|
||||
@ -7,7 +7,8 @@ var knexfile = require('../knexfile')
|
||||
Object.keys(knexfile).forEach(function(key) {
|
||||
|
||||
require('./parse-connection')
|
||||
|
||||
require('./raw')
|
||||
|
||||
var knex = makeKnex(knexfile[key])
|
||||
require('./transactions')(knex)
|
||||
|
||||
|
||||
@ -4,13 +4,9 @@ var parseConnection = require('../../lib/util/parse-connection')
|
||||
var test = require('tape')
|
||||
|
||||
test('parses standard connections', function(t) {
|
||||
|
||||
t.plan(1)
|
||||
|
||||
t.deepEqual(parseConnection('postgres://username:pass@path.to.some-url:6000/testdb'), {
|
||||
|
||||
client: 'postgres',
|
||||
|
||||
connection: {
|
||||
user: 'username',
|
||||
password: 'pass',
|
||||
@ -18,15 +14,11 @@ test('parses standard connections', function(t) {
|
||||
port: '6000',
|
||||
database: 'testdb'
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
test('parses maria connections, aliasing database to db', function(t) {
|
||||
|
||||
t.plan(3)
|
||||
|
||||
var maria = {
|
||||
client: 'maria',
|
||||
connection: {
|
||||
@ -37,21 +29,17 @@ test('parses maria connections, aliasing database to db', function(t) {
|
||||
db: 'testdb'
|
||||
}
|
||||
}
|
||||
|
||||
t.deepEqual(parseConnection('maria://username:pass@path.to.some-url:6000/testdb'), maria)
|
||||
t.deepEqual(parseConnection('mariasql://username:pass@path.to.some-url:6000/testdb'), maria)
|
||||
t.deepEqual(parseConnection('mariadb://username:pass@path.to.some-url:6000/testdb'), maria)
|
||||
})
|
||||
|
||||
test('assume a path is mysql', function(t) {
|
||||
|
||||
t.plan(1)
|
||||
|
||||
t.deepEqual(parseConnection('/path/to/file.db'), {
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: '/path/to/file.db'
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
34
test/tape/raw.js
Normal file
34
test/tape/raw.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
var Raw = require('../../lib/raw');
|
||||
var Client = require('../../lib/client')
|
||||
var test = require('tape')
|
||||
|
||||
var client = new Client()
|
||||
function raw(sql, bindings) {
|
||||
return new Raw(client).set(sql, bindings)
|
||||
}
|
||||
|
||||
test('allows for ?? to interpolate identifiers', function(t) {
|
||||
t.plan(1)
|
||||
t.equal(
|
||||
raw('select * from ?? where id = ?', ['table', 1]).toString(),
|
||||
'select * from "table" where id = 1'
|
||||
)
|
||||
})
|
||||
|
||||
test('allows for object bindings', function(t) {
|
||||
t.plan(1)
|
||||
t.equal(
|
||||
raw('select * from users where user_id = :userId and name = :name', {userId: 1, name: 'tim'}).toString(),
|
||||
"select * from users where user_id = 1 and name = 'tim'"
|
||||
)
|
||||
})
|
||||
|
||||
test('allows for :val: for interpolated identifiers', function(t) {
|
||||
t.plan(1)
|
||||
t.equal(
|
||||
raw('select * from :table: where user_id = :userId and name = :name', {table: 'users', userId: 1, name: 'tim'}).toString(),
|
||||
"select * from \"users\" where user_id = 1 and name = 'tim'"
|
||||
)
|
||||
})
|
||||
@ -4,7 +4,6 @@ var harness = require('./harness')
|
||||
var tape = require('tape')
|
||||
var async = require('async')
|
||||
var JSONStream = require('JSONStream')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
module.exports = function(knex) {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user