89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
/**
|
|
* SQL Server Dialect Implementation
|
|
*
|
|
* Encapsulates SQL Server-specific SQL syntax for:
|
|
* - Table name quoting (bracket notation [schema].[table])
|
|
* - Named parameter placeholders (@p0, @p1, ...)
|
|
* - MERGE ... USING upsert
|
|
* - OFFSET/FETCH pagination
|
|
*/
|
|
|
|
import type { SqlDialect } from '../../../types/sql-dialect.types'
|
|
|
|
export class SqlServerDialect implements SqlDialect {
|
|
readonly dbType = 'sqlserver' as const
|
|
|
|
quoteTableName(schema: string, table: string): string {
|
|
return `[${schema}].[${table}]`
|
|
}
|
|
|
|
param(index: number): string {
|
|
return `@p${index}`
|
|
}
|
|
|
|
params(count: number): string {
|
|
return Array.from({ length: count }, (_, i) => `@p${i}`).join(',')
|
|
}
|
|
|
|
currentTimestamp(): string {
|
|
return 'GETDATE()'
|
|
}
|
|
|
|
upsert(params: {
|
|
table: string
|
|
keyColumns: string[]
|
|
allColumns: string[]
|
|
startParamIndex: number
|
|
}): { sql: string; nextParamIndex: number } {
|
|
const { table, keyColumns, allColumns, startParamIndex } = params
|
|
|
|
const valueParams = allColumns.map((_, i) => `@p${startParamIndex + i}`).join(', ')
|
|
const sourceColumns = allColumns.join(', ')
|
|
|
|
const joinCondition = keyColumns.map((col) => `target.${col} = source.${col}`).join(' AND ')
|
|
|
|
const nonKeyColumns = allColumns.filter((col) => !keyColumns.includes(col))
|
|
const updateSet = nonKeyColumns.map((col) => `target.${col} = source.${col}`).join(', ')
|
|
|
|
const insertColumns = allColumns.join(', ')
|
|
const insertValues = allColumns.map((col) => `source.${col}`).join(', ')
|
|
|
|
const sql = [
|
|
`MERGE ${table} AS target`,
|
|
`USING (VALUES (${valueParams})) AS source (${sourceColumns})`,
|
|
`ON ${joinCondition}`,
|
|
`WHEN MATCHED THEN UPDATE SET ${updateSet}`,
|
|
`WHEN NOT MATCHED THEN INSERT (${insertColumns}) VALUES (${insertValues});`
|
|
].join(' ')
|
|
|
|
return {
|
|
sql,
|
|
nextParamIndex: startParamIndex + allColumns.length
|
|
}
|
|
}
|
|
|
|
paginate(params: { sql: string; limit: number; offset?: number; paramIndex: number }): {
|
|
sql: string
|
|
nextParamIndex: number
|
|
} {
|
|
const { sql, offset, paramIndex } = params
|
|
void params.limit // used by caller to push param values
|
|
|
|
if (offset !== undefined) {
|
|
return {
|
|
sql: `${sql} OFFSET @p${paramIndex} ROWS FETCH NEXT @p${paramIndex + 1} ROWS ONLY`,
|
|
nextParamIndex: paramIndex + 2
|
|
}
|
|
}
|
|
|
|
return {
|
|
sql: `${sql} OFFSET 0 ROWS FETCH NEXT @p${paramIndex} ROWS ONLY`,
|
|
nextParamIndex: paramIndex + 1
|
|
}
|
|
}
|
|
|
|
maxBatchRows(columnsPerRow: number): number {
|
|
return Math.floor(2000 / columnsPerRow)
|
|
}
|
|
}
|