knex/src/dialects/mssql/query/compiler.js
statyan 99ac60af37 Fix incorrect MSSQL query building with lock used, with clause and Number with decimal part conversion (#1707)
* Fixed knex #1577
Aslo corrected tests for primarykey

* Fix for installing from github

* Fix for installing from github

* Fix for installing from github

* Move LOCK hint for MSSQL after join component of select

* Add missing import statement

* Added "WITH" clause to select, update, insert and delete queries.
Fixed forUpdate lock query building
Add detection of decimal value of Numeric parameter and set proper sql.Type for it. (Fix for #1604)

* Change let to const for variable declaration

* Fix MSSQL non-numeric parameters binding
2016-11-25 18:54:48 -06:00

229 lines
6.4 KiB
JavaScript

// MSSQL Query Compiler
// ------
import inherits from 'inherits';
import QueryCompiler from '../../../query/compiler';
import { assign, isEmpty, compact } from 'lodash'
function QueryCompiler_MSSQL(client, builder) {
QueryCompiler.call(this, client, builder)
}
inherits(QueryCompiler_MSSQL, QueryCompiler)
const components = [
'columns', 'join', 'lock', 'where', 'union', 'group',
'having', 'order', 'limit', 'offset'
];
assign(QueryCompiler_MSSQL.prototype, {
_emptyInsertValue: 'default values',
select() {
const sql = this.with();
const statements = components.map(component =>
this[component](this)
);
return sql + compact(statements).join(' ');
},
// Compiles an "insert" query, allowing for multiple
// inserts using a single query statement.
insert() {
const insertValues = this.single.insert || [];
let sql = this.with() + `insert into ${this.tableName} `;
const { returning } = this.single;
const returningSql = returning
? this._returning('insert', returning) + ' '
: '';
if (Array.isArray(insertValues)) {
if (insertValues.length === 0) {
return ''
}
} else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
return {
sql: sql + returningSql + this._emptyInsertValue,
returning
};
}
const insertData = this._prepInsert(insertValues);
if (typeof insertData === 'string') {
sql += insertData;
} else {
if (insertData.columns.length) {
sql += `(${this.formatter.columnize(insertData.columns)}`
sql += `) ${returningSql}values (`
let i = -1
while (++i < insertData.values.length) {
if (i !== 0) sql += '), ('
sql += this.formatter.parameterize(
insertData.values[i], this.client.valueForUndefined
)
}
sql += ')';
} else if (insertValues.length === 1 && insertValues[0]) {
sql += returningSql + this._emptyInsertValue
} else {
sql = ''
}
}
return {
sql,
returning
};
},
// Compiles an `update` query, allowing for a return value.
update() {
const updates = this._prepUpdate(this.single.update);
const join = this.join();
const where = this.where();
const order = this.order();
const top = this.top();
const { returning } = this.single;
return {
sql: this.with() + `update ${top ? top + ' ' : ''}${this.tableName}` +
(join ? ` ${join}` : '') +
' set ' + updates.join(', ') +
(returning ? ` ${this._returning('update', returning)}` : '') +
(where ? ` ${where}` : '') +
(order ? ` ${order}` : '') +
(!returning ? this._returning('rowcount', '@@rowcount') : ''),
returning: returning || '@@rowcount'
};
},
// Compiles a `delete` query.
del() {
// Make sure tableName is processed by the formatter first.
const { tableName } = this;
const wheres = this.where();
const { returning } = this.single;
return {
sql: this.with() + `delete from ${tableName}` +
(returning ? ` ${this._returning('del', returning)}` : '') +
(wheres ? ` ${wheres}` : '') +
(!returning ? this._returning('rowcount', '@@rowcount') : ''),
returning: returning || '@@rowcount'
};
},
// Compiles the columns in the query, specifying if an item was distinct.
columns() {
let distinct = false;
if (this.onlyUnions()) return ''
const columns = this.grouped.columns || []
let i = -1, sql = [];
if (columns) {
while (++i < columns.length) {
const stmt = columns[i];
if (stmt.distinct) distinct = true
if (stmt.type === 'aggregate') {
sql.push(this.aggregate(stmt))
}
else if (stmt.value && stmt.value.length > 0) {
sql.push(this.formatter.columnize(stmt.value))
}
}
}
if (sql.length === 0) sql = ['*'];
const top = this.top();
return `select ${distinct ? 'distinct ' : ''}` +
(top ? top + ' ' : '') +
sql.join(', ') + (this.tableName ? ` from ${this.tableName}` : '');
},
_returning(method, value) {
switch (method) {
case 'update':
case 'insert':
return value
? `output ${this.formatter.columnizeWithPrefix('inserted.', value)}`
: '';
case 'del':
return value
? `output ${this.formatter.columnizeWithPrefix('deleted.', value)}`
: '';
case 'rowcount':
return value
? ';select @@rowcount'
: '';
}
},
// Compiles a `truncate` query.
truncate() {
return `truncate table ${this.tableName}`;
},
forUpdate() {
return 'with (READCOMMITTEDLOCK)';
},
forShare() {
return 'with (NOLOCK)';
},
// Compiles a `columnInfo` query.
columnInfo() {
const column = this.single.columnInfo;
let sql =`select * from information_schema.columns where table_name = ? and table_catalog = ?`;
const bindings = [this.single.table, this.client.database()];
if (this.single.schema) {
sql += ' and table_schema = ?';
bindings.push(this.single.schema);
} else {
sql += ` and table_schema = 'dbo'`;
}
return {
sql,
bindings: bindings,
output(resp) {
const out = resp.reduce(function(columns, val) {
columns[val.COLUMN_NAME] = {
defaultValue: val.COLUMN_DEFAULT,
type: val.DATA_TYPE,
maxLength: val.CHARACTER_MAXIMUM_LENGTH,
nullable: (val.IS_NULLABLE === 'YES')
};
return columns
}, {})
return column && out[column] || out;
}
};
},
top() {
const noLimit = !this.single.limit && this.single.limit !== 0;
const noOffset = !this.single.offset;
if (noLimit || !noOffset) return '';
return `top (${this.formatter.parameter(this.single.limit)})`;
},
limit() {
return '';
},
offset() {
const noLimit = !this.single.limit && this.single.limit !== 0;
const noOffset = !this.single.offset;
if (noOffset) return '';
let offset = `offset ${noOffset ? '0' : this.formatter.parameter(this.single.offset)} rows`;
if (!noLimit) {
offset += ` fetch next ${this.formatter.parameter(this.single.limit)} rows only`;
}
return offset;
},
})
// Set the QueryBuilder & QueryCompiler on the client object,
// in case anyone wants to modify things to suit their own purposes.
export default QueryCompiler_MSSQL;