refactor(db): use SqlDialect in ExtractorOperationHistoryDAO
Replace all isSqlServer checks, buildPlaceholders, and hardcoded table names with the SqlDialect abstraction. The dialect now handles parameter placeholders, table name quoting, current timestamp functions, and pagination across all supported database types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { create, type IDatabaseService } from './index'
|
import { create, type IDatabaseService } from './index'
|
||||||
|
import { createDialect, type SqlDialect } from './dialects'
|
||||||
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
||||||
import type {
|
import type {
|
||||||
OperationHistoryRecord,
|
OperationHistoryRecord,
|
||||||
@@ -36,8 +37,6 @@ function formatDateTime(value: unknown): string {
|
|||||||
* Configuration for ExtractorOperationHistory table
|
* Configuration for ExtractorOperationHistory table
|
||||||
*/
|
*/
|
||||||
export const EXTRACTOR_OPERATION_HISTORY_CONFIG = {
|
export const EXTRACTOR_OPERATION_HISTORY_CONFIG = {
|
||||||
TABLE_NAME_SQLSERVER: '[dbo].[ExtractorOperationHistory]',
|
|
||||||
TABLE_NAME_MYSQL: 'dbo_ExtractorOperationHistory',
|
|
||||||
COLUMNS: {
|
COLUMNS: {
|
||||||
ID: 'ID',
|
ID: 'ID',
|
||||||
BATCH_ID: 'BatchId',
|
BATCH_ID: 'BatchId',
|
||||||
@@ -57,15 +56,20 @@ export const EXTRACTOR_OPERATION_HISTORY_CONFIG = {
|
|||||||
*/
|
*/
|
||||||
export class ExtractorOperationHistoryDAO {
|
export class ExtractorOperationHistoryDAO {
|
||||||
private dbService: IDatabaseService | null = null
|
private dbService: IDatabaseService | null = null
|
||||||
|
private dialect: SqlDialect | null = null
|
||||||
|
|
||||||
|
private getDialect(): SqlDialect {
|
||||||
|
if (!this.dialect) {
|
||||||
|
this.dialect = createDialect(this.dbService!.type)
|
||||||
|
}
|
||||||
|
return this.dialect
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate table name based on database type
|
* Get the appropriate table name based on database type
|
||||||
*/
|
*/
|
||||||
private getTableName(): string {
|
private getTableName(): string {
|
||||||
const isSqlServer = this.dbService?.type === 'sqlserver'
|
return this.getDialect().quoteTableName('dbo', 'ExtractorOperationHistory')
|
||||||
return isSqlServer
|
|
||||||
? EXTRACTOR_OPERATION_HISTORY_CONFIG.TABLE_NAME_SQLSERVER
|
|
||||||
: EXTRACTOR_OPERATION_HISTORY_CONFIG.TABLE_NAME_MYSQL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,14 +84,6 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
return this.dbService
|
return this.dbService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build placeholders for IN clause based on database type
|
|
||||||
*/
|
|
||||||
private buildPlaceholders(count: number, isSqlServer: boolean): string {
|
|
||||||
return isSqlServer
|
|
||||||
? Array.from({ length: count }, (_, idx) => `@p${idx}`).join(',')
|
|
||||||
: Array.from({ length: count }, () => '?').join(',')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== INSERT ====================
|
// ==================== INSERT ====================
|
||||||
|
|
||||||
@@ -119,7 +115,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
log.info('Batch records insertion started', {
|
log.info('Batch records insertion started', {
|
||||||
tableName,
|
tableName,
|
||||||
@@ -133,49 +129,26 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
try {
|
try {
|
||||||
if (isSqlServer) {
|
const sqlString = `
|
||||||
const sqlString = `
|
INSERT INTO ${tableName}
|
||||||
INSERT INTO ${tableName}
|
(BatchId, UserId, Username, ProductionId, OrderNumber, OperationTime, Status)
|
||||||
(BatchId, UserId, Username, ProductionId, OrderNumber, OperationTime, Status)
|
VALUES
|
||||||
VALUES
|
(${dialect.param(0)}, ${dialect.param(1)}, ${dialect.param(2)}, ${dialect.param(3)}, ${dialect.param(4)}, ${dialect.currentTimestamp()}, 'pending')
|
||||||
(@p0, @p1, @p2, @p3, @p4, GETDATE(), 'pending')
|
`
|
||||||
`
|
await trackDuration(
|
||||||
await trackDuration(
|
async () =>
|
||||||
async () =>
|
await dbService.query(sqlString, [
|
||||||
await dbService.query(sqlString, [
|
batchId,
|
||||||
batchId,
|
userId,
|
||||||
userId,
|
username,
|
||||||
username,
|
record.productionId || null,
|
||||||
record.productionId || null,
|
record.orderNumber
|
||||||
record.orderNumber
|
]),
|
||||||
]),
|
{
|
||||||
{
|
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
||||||
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
context: { tableName, operationType: 'INSERT', batchId }
|
||||||
context: { tableName, operationType: 'INSERT', batchId }
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const sqlString = `
|
|
||||||
INSERT INTO ${tableName}
|
|
||||||
(BatchId, UserId, Username, ProductionId, OrderNumber, OperationTime, Status)
|
|
||||||
VALUES
|
|
||||||
(?, ?, ?, ?, ?, NOW(), 'pending')
|
|
||||||
`
|
|
||||||
await trackDuration(
|
|
||||||
async () =>
|
|
||||||
await dbService.query(sqlString, [
|
|
||||||
batchId,
|
|
||||||
userId,
|
|
||||||
username,
|
|
||||||
record.productionId || null,
|
|
||||||
record.orderNumber
|
|
||||||
]),
|
|
||||||
{
|
|
||||||
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
|
||||||
context: { tableName, operationType: 'INSERT', batchId }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Error inserting individual record', {
|
log.error('Error inserting individual record', {
|
||||||
tableName,
|
tableName,
|
||||||
@@ -221,12 +194,12 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
UPDATE ${tableName}
|
UPDATE ${tableName}
|
||||||
SET Status = ${isSqlServer ? '@p0' : '?'}
|
SET Status = ${dialect.param(0)}
|
||||||
WHERE BatchId = ${isSqlServer ? '@p1' : '?'}
|
WHERE BatchId = ${dialect.param(1)}
|
||||||
`
|
`
|
||||||
const params = [status, batchId]
|
const params = [status, batchId]
|
||||||
|
|
||||||
@@ -274,7 +247,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
let sqlString: string
|
let sqlString: string
|
||||||
let params: (string | number | null)[]
|
let params: (string | number | null)[]
|
||||||
@@ -282,20 +255,20 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
if (recordCount !== undefined) {
|
if (recordCount !== undefined) {
|
||||||
sqlString = `
|
sqlString = `
|
||||||
UPDATE ${tableName}
|
UPDATE ${tableName}
|
||||||
SET Status = ${isSqlServer ? '@p0' : '?'},
|
SET Status = ${dialect.param(0)},
|
||||||
ErrorMessage = ${isSqlServer ? '@p1' : '?'},
|
ErrorMessage = ${dialect.param(1)},
|
||||||
RecordCount = ${isSqlServer ? '@p2' : '?'}
|
RecordCount = ${dialect.param(2)}
|
||||||
WHERE BatchId = ${isSqlServer ? '@p3' : '?'}
|
WHERE BatchId = ${dialect.param(3)}
|
||||||
AND OrderNumber = ${isSqlServer ? '@p4' : '?'}
|
AND OrderNumber = ${dialect.param(4)}
|
||||||
`
|
`
|
||||||
params = [status, errorMessage || null, recordCount, batchId, orderNumber]
|
params = [status, errorMessage || null, recordCount, batchId, orderNumber]
|
||||||
} else {
|
} else {
|
||||||
sqlString = `
|
sqlString = `
|
||||||
UPDATE ${tableName}
|
UPDATE ${tableName}
|
||||||
SET Status = ${isSqlServer ? '@p0' : '?'},
|
SET Status = ${dialect.param(0)},
|
||||||
ErrorMessage = ${isSqlServer ? '@p1' : '?'}
|
ErrorMessage = ${dialect.param(1)}
|
||||||
WHERE BatchId = ${isSqlServer ? '@p2' : '?'}
|
WHERE BatchId = ${dialect.param(2)}
|
||||||
AND OrderNumber = ${isSqlServer ? '@p3' : '?'}
|
AND OrderNumber = ${dialect.param(3)}
|
||||||
`
|
`
|
||||||
params = [status, errorMessage || null, batchId, orderNumber]
|
params = [status, errorMessage || null, batchId, orderNumber]
|
||||||
}
|
}
|
||||||
@@ -331,7 +304,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
let sqlString = `
|
let sqlString = `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -350,11 +323,10 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
const params: (number | string)[] = []
|
const params: (number | string)[] = []
|
||||||
|
|
||||||
if (userId !== undefined) {
|
if (userId !== undefined) {
|
||||||
sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? `
|
sqlString += ` WHERE UserId = ${dialect.param(params.length)} `
|
||||||
params.push(userId)
|
params.push(userId)
|
||||||
} else if (options?.usernames && options.usernames.length > 0) {
|
} else if (options?.usernames && options.usernames.length > 0) {
|
||||||
const placeholders = this.buildPlaceholders(options.usernames.length, isSqlServer)
|
sqlString += ` WHERE Username IN (${dialect.params(options.usernames.length)}) `
|
||||||
sqlString += ` WHERE Username IN (${placeholders}) `
|
|
||||||
params.push(...options.usernames)
|
params.push(...options.usernames)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,24 +339,19 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
const safeLimit = Math.floor(options.limit)
|
const safeLimit = Math.floor(options.limit)
|
||||||
const safeOffset = options.offset !== undefined ? Math.floor(options.offset) : undefined
|
const safeOffset = options.offset !== undefined ? Math.floor(options.offset) : undefined
|
||||||
|
|
||||||
if (isSqlServer) {
|
const result = dialect.paginate({
|
||||||
const offsetIndex = params.length
|
sql: sqlString,
|
||||||
|
limit: safeLimit,
|
||||||
|
offset: safeOffset,
|
||||||
|
paramIndex: params.length
|
||||||
|
})
|
||||||
|
sqlString = result.sql
|
||||||
|
|
||||||
|
if (dialect.dbType === 'sqlserver') {
|
||||||
if (safeOffset !== undefined) {
|
if (safeOffset !== undefined) {
|
||||||
params.push(safeOffset)
|
params.push(safeOffset)
|
||||||
}
|
}
|
||||||
params.push(safeLimit)
|
params.push(safeLimit)
|
||||||
|
|
||||||
if (safeOffset !== undefined) {
|
|
||||||
sqlString += ` OFFSET @p${offsetIndex} ROWS FETCH NEXT @p${offsetIndex + 1} ROWS ONLY`
|
|
||||||
} else {
|
|
||||||
sqlString += ` OFFSET 0 ROWS FETCH NEXT @p${offsetIndex} ROWS ONLY`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (safeOffset !== undefined) {
|
|
||||||
sqlString += ` LIMIT ${safeLimit} OFFSET ${safeOffset}`
|
|
||||||
} else {
|
|
||||||
sqlString += ` LIMIT ${safeLimit}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,9 +392,9 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
const placeholder = isSqlServer ? '@p0' : '?'
|
const placeholder = dialect.param(0)
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT
|
SELECT
|
||||||
ID,
|
ID,
|
||||||
@@ -483,9 +450,9 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
const placeholder = isSqlServer ? '@p0' : '?'
|
const placeholder = dialect.param(0)
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT
|
SELECT
|
||||||
BatchId,
|
BatchId,
|
||||||
@@ -554,7 +521,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
// First check if the batch exists and if the user has permission
|
// First check if the batch exists and if the user has permission
|
||||||
const batchStats = await this.getBatchStats(batchId)
|
const batchStats = await this.getBatchStats(batchId)
|
||||||
@@ -569,7 +536,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the batch
|
// Delete the batch
|
||||||
const placeholder = isSqlServer ? '@p0' : '?'
|
const placeholder = dialect.param(0)
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
DELETE FROM ${tableName}
|
DELETE FROM ${tableName}
|
||||||
WHERE BatchId = ${placeholder}
|
WHERE BatchId = ${placeholder}
|
||||||
@@ -612,9 +579,9 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
const placeholder = isSqlServer ? '@p0' : '?'
|
const placeholder = dialect.param(0)
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
DELETE FROM ${tableName}
|
DELETE FROM ${tableName}
|
||||||
WHERE UserId = ${placeholder}
|
WHERE UserId = ${placeholder}
|
||||||
@@ -648,9 +615,9 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
const placeholder = isSqlServer ? '@p0' : '?'
|
const placeholder = dialect.param(0)
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
@@ -684,7 +651,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const isSqlServer = dbService.type === 'sqlserver'
|
const dialect = this.getDialect()
|
||||||
|
|
||||||
let sqlString = `
|
let sqlString = `
|
||||||
SELECT COUNT(DISTINCT BatchId) as count
|
SELECT COUNT(DISTINCT BatchId) as count
|
||||||
@@ -694,11 +661,10 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
const params: (number | string)[] = []
|
const params: (number | string)[] = []
|
||||||
|
|
||||||
if (userId !== undefined) {
|
if (userId !== undefined) {
|
||||||
sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? `
|
sqlString += ` WHERE UserId = ${dialect.param(params.length)} `
|
||||||
params.push(userId)
|
params.push(userId)
|
||||||
} else if (usernames && usernames.length > 0) {
|
} else if (usernames && usernames.length > 0) {
|
||||||
const placeholders = this.buildPlaceholders(usernames.length, isSqlServer)
|
sqlString += ` WHERE Username IN (${dialect.params(usernames.length)}) `
|
||||||
sqlString += ` WHERE Username IN (${placeholders}) `
|
|
||||||
params.push(...usernames)
|
params.push(...usernames)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,6 +691,7 @@ export class ExtractorOperationHistoryDAO {
|
|||||||
if (this.dbService) {
|
if (this.dbService) {
|
||||||
await this.dbService.disconnect()
|
await this.dbService.disconnect()
|
||||||
this.dbService = null
|
this.dbService = null
|
||||||
|
this.dialect = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user