knex/src/dialects/mssql/query/compiler.js

229 lines
6.4 KiB
JavaScript
Raw Normal View History

2016-03-02 17:07:05 +01:00
// MSSQL Query Compiler
// ------
import inherits from 'inherits';
import QueryCompiler from '../../../query/compiler';
2016-03-02 17:07:05 +01:00
import { assign, isEmpty, compact } from 'lodash'
2016-03-02 17:07:05 +01:00
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'
];
2016-03-02 17:07:05 +01:00
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(' ');
},
2016-03-02 17:07:05 +01:00
// 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) + ' '
: '';
2016-03-02 17:07:05 +01:00
if (Array.isArray(insertValues)) {
if (insertValues.length === 0) {
return ''
}
} else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
return {
sql: sql + returningSql + this._emptyInsertValue,
returning
2016-03-02 17:07:05 +01:00
};
}
const insertData = this._prepInsert(insertValues);
2016-03-02 17:07:05 +01:00
if (typeof insertData === 'string') {
sql += insertData;
} else {
if (insertData.columns.length) {
sql += `(${this.formatter.columnize(insertData.columns)}`
sql += `) ${returningSql}values (`
let i = -1
2016-03-02 17:07:05 +01:00
while (++i < insertData.values.length) {
if (i !== 0) sql += '), ('
sql += this.formatter.parameterize(
insertData.values[i], this.client.valueForUndefined
)
2016-03-02 17:07:05 +01:00
}
sql += ')';
} else if (insertValues.length === 1 && insertValues[0]) {
sql += returningSql + this._emptyInsertValue
} else {
sql = ''
}
}
return {
sql,
returning
2016-03-02 17:07:05 +01:00
};
},
// 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;
2016-03-02 17:07:05 +01:00
return {
sql: this.with() + `update ${top ? top + ' ' : ''}${this.tableName}` +
(join ? ` ${join}` : '') +
2016-03-02 17:07:05 +01:00
' set ' + updates.join(', ') +
(returning ? ` ${this._returning('update', returning)}` : '') +
(where ? ` ${where}` : '') +
(order ? ` ${order}` : '') +
2016-03-02 17:07:05 +01:00
(!returning ? this._returning('rowcount', '@@rowcount') : ''),
returning: returning || '@@rowcount'
};
},
// Compiles a `delete` query.
del() {
2016-03-02 17:07:05 +01:00
// Make sure tableName is processed by the formatter first.
const { tableName } = this;
const wheres = this.where();
const { returning } = this.single;
2016-03-02 17:07:05 +01:00
return {
sql: this.with() + `delete from ${tableName}` +
(returning ? ` ${this._returning('del', returning)}` : '') +
(wheres ? ` ${wheres}` : '') +
2016-03-02 17:07:05 +01:00
(!returning ? this._returning('rowcount', '@@rowcount') : ''),
returning: returning || '@@rowcount'
};
},
// Compiles the columns in the query, specifying if an item was distinct.
columns() {
let distinct = false;
2016-03-02 17:07:05 +01:00
if (this.onlyUnions()) return ''
const columns = this.grouped.columns || []
let i = -1, sql = [];
2016-03-02 17:07:05 +01:00
if (columns) {
while (++i < columns.length) {
const stmt = columns[i];
2016-03-02 17:07:05 +01:00
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 ' : ''}` +
2016-03-02 17:07:05 +01:00
(top ? top + ' ' : '') +
sql.join(', ') + (this.tableName ? ` from ${this.tableName}` : '');
2016-03-02 17:07:05 +01:00
},
_returning(method, value) {
2016-03-02 17:07:05 +01:00
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'
: '';
2016-03-02 17:07:05 +01:00
}
},
// Compiles a `truncate` query.
truncate() {
return `truncate table ${this.tableName}`;
2016-03-02 17:07:05 +01:00
},
forUpdate() {
2016-03-02 17:07:05 +01:00
return 'with (READCOMMITTEDLOCK)';
},
forShare() {
2016-03-02 17:07:05 +01:00
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'`;
}
2016-03-02 17:07:05 +01:00
return {
sql,
bindings: bindings,
output(resp) {
const out = resp.reduce(function(columns, val) {
2016-03-02 17:07:05 +01:00
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;
2016-03-02 17:07:05 +01:00
if (noLimit || !noOffset) return '';
return `top (${this.formatter.parameter(this.single.limit)})`;
2016-03-02 17:07:05 +01:00
},
limit() {
2016-03-02 17:07:05 +01:00
return '';
},
offset() {
const noLimit = !this.single.limit && this.single.limit !== 0;
const noOffset = !this.single.offset;
2016-03-02 17:07:05 +01:00
if (noOffset) return '';
let offset = `offset ${noOffset ? '0' : this.formatter.parameter(this.single.offset)} rows`;
2016-03-02 17:07:05 +01:00
if (!noLimit) {
offset += ` fetch next ${this.formatter.parameter(this.single.limit)} rows only`;
2016-03-02 17:07:05 +01:00
}
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;