Build native sql for a dialect without to string (#2237)

* Exposed also positionBinding method to client base class and refactored calls to em

* Added toSQL().toNative() getter which returns dialect specfic sql and bindings.

* Refactored toSQL method implementation
This commit is contained in:
Mikael Lepistö 2017-09-27 13:12:40 +03:00 committed by GitHub
parent 491cc788c2
commit 15639d03db
9 changed files with 71 additions and 31 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
if [ -n "$(which docker)" ]; then
if [ -n "$(docker info)" ]; then
DOCKER_IMAGES=("mysql:5.7" "postgres:9.6")
for image in ${DOCKER_IMAGES[@]}; do
if [ -z "$(docker images -q ${image})" ]; then

View File

@ -133,6 +133,7 @@ assign(Client.prototype, {
query(connection, obj) {
if (typeof obj === 'string') obj = {sql: obj}
obj.sql = this.positionBindings(obj.sql);
obj.bindings = this.prepBindings(obj.bindings)
debugQuery(obj.sql)
this.emit('query', assign({__knexUid: connection.__knexUid}, obj))
@ -146,9 +147,10 @@ assign(Client.prototype, {
stream(connection, obj, stream, options) {
if (typeof obj === 'string') obj = {sql: obj}
obj.sql = this.positionBindings(obj.sql);
obj.bindings = this.prepBindings(obj.bindings)
this.emit('query', assign({__knexUid: connection.__knexUid}, obj))
debugQuery(obj.sql)
obj.bindings = this.prepBindings(obj.bindings)
debugBindings(obj.bindings)
return this._stream(connection, obj, stream, options)
},
@ -157,6 +159,10 @@ assign(Client.prototype, {
return bindings;
},
positionBindings(sql) {
return sql;
},
wrapIdentifier(value) {
if (this.config.wrapIdentifier) {
return this.config.wrapIdentifier(value, this.wrapIdentifierImpl);

View File

@ -114,8 +114,6 @@ assign(Client_MSSQL.prototype, {
_stream(connection, obj, stream, options) {
options = options || {}
if (!obj || typeof obj === 'string') obj = {sql: obj}
// convert ? params into positional bindings (@p1)
obj.sql = this.positionBindings(obj.sql);
return new Promise((resolver, rejecter) => {
stream.on('error', (err) => {
rejecter(err)
@ -142,8 +140,6 @@ assign(Client_MSSQL.prototype, {
_query(connection, obj) {
const client = this;
if (!obj || typeof obj === 'string') obj = {sql: obj}
// convert ? params into positional bindings (@p1)
obj.sql = this.positionBindings(obj.sql);
return new Promise((resolver, rejecter) => {
const { sql } = obj
if (!sql) return resolver()

View File

@ -116,7 +116,6 @@ assign(Client_Oracle.prototype, {
},
_stream(connection, obj, stream, options) {
obj.sql = this.positionBindings(obj.sql);
return new Promise(function (resolver, rejecter) {
stream.on('error', (err) => {
if (isConnectionError(err)) {
@ -134,9 +133,6 @@ assign(Client_Oracle.prototype, {
// and any other necessary prep work.
_query(connection, obj) {
// convert ? params into positional bindings (:1)
obj.sql = this.positionBindings(obj.sql);
if (!obj.sql) throw new Error('The query is empty');
return connection.executeAsync(obj.sql, obj.bindings).then(function(response) {

View File

@ -233,10 +233,6 @@ Client_Oracledb.prototype.destroyRawConnection = function(connection) {
// Runs the query on the specified connection, providing the bindings
// and any other necessary prep work.
Client_Oracledb.prototype._query = function(connection, obj) {
// Convert ? params into positional bindings (:1)
obj.sql = this.positionBindings(obj.sql);
obj.bindings = this.prepBindings(obj.bindings) || [];
return new Promise(function(resolver, rejecter) {
if (!obj.sql) {
return rejecter(new Error('The query is empty'));

View File

@ -173,7 +173,7 @@ assign(Client_PG.prototype, {
_stream(connection, obj, stream, options) {
const PGQueryStream = process.browser ? undefined : require('pg-query-stream');
const sql = obj.sql = this.positionBindings(obj.sql)
const sql = obj.sql;
return new Promise(function(resolver, rejecter) {
const queryStream = connection.query(new PGQueryStream(sql, obj.bindings, options));
queryStream.on('error', function(error) { stream.emit('error', error); });
@ -192,7 +192,7 @@ assign(Client_PG.prototype, {
// Runs the query on the specified connection, providing the bindings
// and any other necessary prep work.
_query(connection, obj) {
let sql = obj.sql = this.positionBindings(obj.sql)
let sql = obj.sql;
if (obj.options) sql = extend({text: sql}, obj.options);
return new Promise(function(resolver, rejecter) {
connection.query(sql, obj.bindings, function(err, response) {

View File

@ -44,36 +44,42 @@ assign(QueryCompiler.prototype, {
this._undefinedInWhereClause = false;
method = method || this.method
let val = this[method]()
const defaults = {
const val = this[method]() || '';
const query = {
method,
options: reduce(this.options, assign, {}),
timeout: this.timeout,
cancelOnTimeout: this.cancelOnTimeout,
bindings: this.formatter.bindings,
__knexQueryUid: uuid.v4()
bindings: this.formatter.bindings || [],
__knexQueryUid: uuid.v4(),
toNative: () => ({
sql: this.client.positionBindings(query.sql),
bindings: this.client.prepBindings(query.bindings)
})
};
if (isString(val)) {
val = {sql: val};
}
defaults.bindings = defaults.bindings || [];
if (isString(val)) {
query.sql = val;
} else {
assign(query, val);
}
if (method === 'select' || method === 'first') {
if(this.single.as) {
defaults.as = this.single.as;
query.as = this.single.as;
}
}
if(this._undefinedInWhereClause) {
debugBindings(defaults.bindings)
debugBindings(query.bindings)
throw new Error(
`Undefined binding(s) detected when compiling ` +
`${method.toUpperCase()} query: ${val.sql}`
`${method.toUpperCase()} query: ${query.sql}`
);
}
return assign(defaults, val);
return query;
},
// Compiles the `select` statement, or nested sub-selects by calling each of

View File

@ -21,9 +21,9 @@ function canRunDockerTests() {
const isLinux = os.platform() === 'linux';
const isDarwin = os.platform() === 'darwin'
// dont even try on windows / osx for now
let hasDocker = false;
let hasDockerStarted = false;
if (isLinux || isDarwin) {
hasDocker = proc.execSync('docker -v 1>/dev/null 2>&1 ; echo $?').toString('utf-8') === '0\n';
hasDockerStarted = proc.execSync('docker info 1>/dev/null 2>&1 ; echo $?').toString('utf-8') === '0\n';
}
return hasDocker;
return hasDockerStarted;
}

View File

@ -69,6 +69,17 @@ function testsql(chain, valuesToCheck, selectedClients) {
})
}
function testNativeSql(chain, valuesToCheck, selectedClients) {
selectedClients = selectedClients || clients;
Object.keys(valuesToCheck).forEach(function(key) {
var newChain = chain.clone();
newChain.client = selectedClients[key];
var sqlAndBindings = newChain.toSQL().toNative();
var checkValue = valuesToCheck[key];
verifySqlResult(key, checkValue, sqlAndBindings);
})
}
function testquery(chain, valuesToCheck, selectedClients) {
selectedClients = selectedClients || clients;
Object.keys(valuesToCheck).forEach(function(key) {
@ -4317,6 +4328,35 @@ describe("QueryBuilder", function() {
});
});
it("should return dialect specific sql and bindings with toSQL().toNative()", function() {
testNativeSql(qb().from('table').where('isIt', true), {
mssql: {
sql: 'select * from [table] where [isIt] = @p0',
bindings: [true]
},
mysql: {
sql: 'select * from `table` where `isIt` = ?',
bindings: [true]
},
sqlite3: {
sql: 'select * from `table` where `isIt` = ?',
bindings: [true]
},
postgres: {
sql: 'select * from "table" where "isIt" = $1',
bindings: [true]
},
oracledb: {
sql: 'select * from "table" where "isIt" = :1',
bindings: [1]
},
oracle: {
sql: 'select * from "table" where "isIt" = :1',
bindings: [1]
}
});
});
it("nested and chained wrapped 'with' clause", function() {
testsql(qb().with('firstWithClause', function() {
this.with('firstWithSubClause', function() {