Files
evento/node_modules/@mikro-orm/sql/dialects/mssql/MsSqlNativeQueryBuilder.js
2026-03-18 14:55:56 -03:00

203 lines
7.3 KiB
JavaScript

import { LockMode, QueryFlag, RawQueryFragment, Utils } from '@mikro-orm/core';
import { NativeQueryBuilder } from '../../query/NativeQueryBuilder.js';
import { QueryType } from '../../query/enums.js';
/** @internal */
export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
compile() {
if (!this.type) {
throw new Error('No query type provided');
}
this.parts.length = 0;
this.params.length = 0;
if (this.options.flags?.has(QueryFlag.IDENTITY_INSERT)) {
this.parts.push(`set identity_insert ${this.getTableName()} on;`);
}
const { prefix, suffix } = this.appendOutputTable();
if (prefix) {
this.parts.push(prefix);
}
if (this.options.comment) {
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
}
this.compileCtes();
if (this.options.onConflict && !Utils.isEmpty(Utils.asArray(this.options.data)[0])) {
this.compileUpsert();
} else {
switch (this.type) {
case QueryType.SELECT:
case QueryType.COUNT:
this.compileSelect();
break;
case QueryType.INSERT:
this.compileInsert();
break;
case QueryType.UPDATE:
this.compileUpdate();
break;
case QueryType.DELETE:
this.compileDelete();
break;
case QueryType.TRUNCATE:
this.compileTruncate();
break;
}
if (suffix) {
this.parts[this.parts.length - 1] += ';';
this.parts.push(suffix);
} else if ([QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE].includes(this.type)) {
this.parts[this.parts.length - 1] += '; select @@rowcount;';
}
}
if (this.options.flags?.has(QueryFlag.IDENTITY_INSERT)) {
this.parts.push(`set identity_insert ${this.getTableName()} off;`);
}
return this.combineParts();
}
compileInsert() {
if (!this.options.data) {
throw new Error('No data provided');
}
this.parts.push('insert');
this.addHintComment();
this.parts.push(`into ${this.getTableName()}`);
if (Object.keys(this.options.data).length === 0) {
this.addOutputClause('inserted');
this.parts.push('default values');
return;
}
const parts = this.processInsertData();
if (this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
this.parts[this.parts.length - 2] += ' into #out ';
}
this.parts.push(parts.join(', '));
}
appendOutputTable() {
if (!this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
return { prefix: '', suffix: '' };
}
const returningFields = this.options.returning;
const selections = returningFields.map(field => `[t].${this.platform.quoteIdentifier(field)}`).join(',');
return {
prefix: `select top(0) ${selections} into #out from ${this.getTableName()} as t left join ${this.getTableName()} on 0 = 1;`,
suffix: `select ${selections} from #out as t; drop table #out`,
};
}
compileUpsert() {
const clause = this.options.onConflict;
const dataAsArray = Utils.asArray(this.options.data);
const keys = Object.keys(dataAsArray[0]);
const values = keys.map(() => '?');
const parts = [];
for (const data of dataAsArray) {
for (const key of keys) {
this.params.push(data[key]);
}
parts.push(`(${values.join(', ')})`);
}
this.parts.push(`merge into ${this.getTableName()}`);
this.parts.push(`using (values ${parts.join(', ')}) as tsource(${keys.map(key => this.quote(key)).join(', ')})`);
if (clause.fields instanceof RawQueryFragment) {
this.parts.push(clause.fields.sql);
this.params.push(...clause.fields.params);
} else if (clause.fields.length > 0) {
const fields = clause.fields.map(field => {
const col = this.quote(field);
return `${this.getTableName()}.${col} = tsource.${col}`;
});
this.parts.push(`on ${fields.join(' and ')}`);
}
const sourceColumns = keys.map(field => `tsource.${this.quote(field)}`).join(', ');
const destinationColumns = keys.map(field => this.quote(field)).join(', ');
this.parts.push(`when not matched then insert (${destinationColumns}) values (${sourceColumns})`);
if (!clause.ignore) {
this.parts.push('when matched');
if (clause.where) {
this.parts.push(`and ${clause.where.sql}`);
this.params.push(...clause.where.params);
}
this.parts.push('then update set');
if (!clause.merge || Array.isArray(clause.merge)) {
const parts = (clause.merge || keys)
.filter(field => !Array.isArray(clause.fields) || !clause.fields.includes(field))
.map(column => `${this.quote(column)} = tsource.${this.quote(column)}`);
this.parts.push(parts.join(', '));
} else if (typeof clause.merge === 'object') {
const parts = Object.entries(clause.merge).map(([key, value]) => {
this.params.push(value);
return `${this.getTableName()}.${this.quote(key)} = ?`;
});
this.parts.push(parts.join(', '));
}
}
this.addOutputClause('inserted');
this.parts[this.parts.length - 1] += ';';
}
compileSelect() {
this.parts.push('select');
if (this.options.limit != null && this.options.offset == null) {
this.parts.push(`top (?)`);
this.params.push(this.options.limit);
}
this.addHintComment();
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
this.addLockClause();
if (this.options.joins) {
for (const join of this.options.joins) {
this.parts.push(join.sql);
this.params.push(...join.params);
}
}
if (this.options.where?.sql.trim()) {
this.parts.push(`where ${this.options.where.sql}`);
this.params.push(...this.options.where.params);
}
if (this.options.groupBy) {
const fields = this.options.groupBy.map(field => this.quote(field));
this.parts.push(`group by ${fields.join(', ')}`);
}
if (this.options.having) {
this.parts.push(`having ${this.options.having.sql}`);
this.params.push(...this.options.having.params);
}
if (this.options.orderBy) {
this.parts.push(`order by ${this.options.orderBy}`);
}
if (this.options.offset != null) {
/* v8 ignore next */
if (!this.options.orderBy) {
throw new Error('Order by clause is required for pagination');
}
this.parts.push(`offset ? rows`);
this.params.push(this.options.offset);
if (this.options.limit != null) {
this.parts.push(`fetch next ? rows only`);
this.params.push(this.options.limit);
}
}
}
addLockClause() {
if (
!this.options.lockMode ||
![LockMode.PESSIMISTIC_READ, LockMode.PESSIMISTIC_WRITE].includes(this.options.lockMode)
) {
return;
}
const map = {
[LockMode.PESSIMISTIC_READ]: 'with (holdlock)',
[LockMode.PESSIMISTIC_WRITE]: 'with (updlock)',
};
if (this.options.lockMode !== LockMode.OPTIMISTIC) {
this.parts.push(map[this.options.lockMode]);
}
}
compileTruncate() {
const tableName = this.getTableName();
const sql = `delete from ${tableName}; declare @count int = case @@rowcount when 0 then 1 else 0 end; dbcc checkident ('${tableName.replace(/[[\]]/g, '')}', reseed, @count)`;
this.parts.push(sql);
}
/** MSSQL has no RECURSIVE keyword — CTEs are implicitly recursive. */
getCteKeyword(_hasRecursive) {
return 'with';
}
}