forge-sql-orm 2.1.22 → 2.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +6 -2
  2. package/dist/core/ForgeSQLAnalyseOperations.d.ts +4 -0
  3. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
  4. package/dist/core/ForgeSQLAnalyseOperations.js +17 -21
  5. package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts +16 -0
  7. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLCrudOperations.js +60 -28
  9. package/dist/core/ForgeSQLCrudOperations.js.map +1 -1
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts +15 -28
  11. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  12. package/dist/core/ForgeSQLQueryBuilder.js +20 -47
  13. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  14. package/dist/core/Rovo.d.ts +32 -0
  15. package/dist/core/Rovo.d.ts.map +1 -1
  16. package/dist/core/Rovo.js +116 -67
  17. package/dist/core/Rovo.js.map +1 -1
  18. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  19. package/dist/lib/drizzle/extensions/additionalActions.js +168 -118
  20. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
  21. package/dist/utils/cacheTableUtils.d.ts +0 -8
  22. package/dist/utils/cacheTableUtils.d.ts.map +1 -1
  23. package/dist/utils/cacheTableUtils.js +183 -126
  24. package/dist/utils/cacheTableUtils.js.map +1 -1
  25. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  26. package/dist/utils/forgeDriverProxy.js +31 -20
  27. package/dist/utils/forgeDriverProxy.js.map +1 -1
  28. package/dist/utils/sqlHints.d.ts.map +1 -1
  29. package/dist/utils/sqlHints.js +19 -29
  30. package/dist/utils/sqlHints.js.map +1 -1
  31. package/dist/utils/sqlUtils.d.ts +0 -29
  32. package/dist/utils/sqlUtils.d.ts.map +1 -1
  33. package/dist/utils/sqlUtils.js +107 -78
  34. package/dist/utils/sqlUtils.js.map +1 -1
  35. package/package.json +13 -13
  36. package/src/core/ForgeSQLAnalyseOperations.ts +18 -21
  37. package/src/core/ForgeSQLCrudOperations.ts +83 -33
  38. package/src/core/ForgeSQLQueryBuilder.ts +59 -154
  39. package/src/core/Rovo.ts +158 -98
  40. package/src/lib/drizzle/extensions/additionalActions.ts +287 -382
  41. package/src/utils/cacheTableUtils.ts +202 -144
  42. package/src/utils/forgeDriverProxy.ts +39 -21
  43. package/src/utils/sqlHints.ts +21 -26
  44. package/src/utils/sqlUtils.ts +151 -101
@@ -1,6 +1,17 @@
1
+ // qlty-ignore: +qlty:file-complexity
1
2
  import { Parser } from "node-sql-parser";
2
3
  import { ForgeSqlOrmOptions } from "../core/ForgeSQLQueryBuilder";
3
4
 
5
+ /**
6
+ * Extracts table name from backticks_quote_string type.
7
+ */
8
+ function extractTableNameFromBackticks(value: any): string | null {
9
+ if (value.type === "backticks_quote_string" && typeof value.value === "string") {
10
+ return value.value === "dual" ? null : value.value.toLowerCase();
11
+ }
12
+ return null;
13
+ }
14
+
4
15
  /**
5
16
  * Extracts table name from object value.
6
17
  */
@@ -9,22 +20,25 @@ function extractTableNameFromObject(value: any, context?: string): string | null
9
20
  if (Array.isArray(value)) {
10
21
  return null;
11
22
  }
23
+
12
24
  // Handle backticks_quote_string type only for node.table context
13
- if (
14
- context?.includes("node.table") &&
15
- value.type === "backticks_quote_string" &&
16
- typeof value.value === "string"
17
- ) {
18
- return value.value === "dual" ? null : value.value.toLowerCase();
25
+ if (context?.includes("node.table")) {
26
+ const fromBackticks = extractTableNameFromBackticks(value);
27
+ if (fromBackticks !== null) {
28
+ return fromBackticks;
29
+ }
19
30
  }
31
+
20
32
  // Try value.name first (most common)
21
33
  if (typeof value.name === "string") {
22
34
  return value.name === "dual" ? null : value.name.toLowerCase();
23
35
  }
36
+
24
37
  // Try value.table if it's a nested structure
25
38
  if (value.table) {
26
39
  return normalizeTableName(value.table, context);
27
40
  }
41
+
28
42
  // Log when we encounter an object that we can't extract table name from
29
43
  // eslint-disable-next-line no-console
30
44
  console.warn(
@@ -93,6 +107,22 @@ function isLikelyAlias(node: any): boolean {
93
107
  return isColumnRefAlias(node) || isExplicitAlias(node) || isShortNameAlias(node);
94
108
  }
95
109
 
110
+ /**
111
+ * Extracts table name from table/dual node.
112
+ */
113
+ function extractTableNameFromTableNode(node: any): string | null {
114
+ const fromTable = node.table
115
+ ? normalizeTableName(node.table, `node.type=${node.type}, node.table`)
116
+ : null;
117
+ if (fromTable) {
118
+ return fromTable;
119
+ }
120
+ const fromName = node.name
121
+ ? normalizeTableName(node.name, `node.type=${node.type}, node.name`)
122
+ : null;
123
+ return fromName;
124
+ }
125
+
96
126
  /**
97
127
  * Extracts table name from table node.
98
128
  *
@@ -100,38 +130,18 @@ function isLikelyAlias(node: any): boolean {
100
130
  * @returns Table name in lowercase or null if not applicable
101
131
  */
102
132
  function extractTableName(node: any): string | null {
103
- if (!node) {
104
- return null;
105
- }
106
-
107
- // Early return for likely aliases
108
- if (isLikelyAlias(node)) {
133
+ if (!node || isLikelyAlias(node)) {
109
134
  return null;
110
135
  }
111
136
 
112
137
  // Handle table node directly
113
138
  if (node.type === "table" || node.type === "dual") {
114
- const fromTable = node.table
115
- ? normalizeTableName(node.table, `node.type=${node.type}, node.table`)
116
- : null;
117
- if (fromTable) {
118
- return fromTable;
119
- }
120
- const fromName = node.name
121
- ? normalizeTableName(node.name, `node.type=${node.type}, node.name`)
122
- : null;
123
- if (fromName) {
124
- return fromName;
125
- }
126
- return null;
139
+ return extractTableNameFromTableNode(node);
127
140
  }
128
141
 
129
142
  // Handle table reference in different formats
130
143
  if (node.table) {
131
- const tableName = normalizeTableName(node.table, `node.table (type: ${node.type})`);
132
- if (tableName) {
133
- return tableName;
134
- }
144
+ return normalizeTableName(node.table, `node.table (type: ${node.type})`);
135
145
  }
136
146
 
137
147
  return null;
@@ -205,6 +215,30 @@ function processFromAndJoin(node: any, tables: Set<string>): void {
205
215
  }
206
216
  }
207
217
 
218
+ /**
219
+ * Processes a single column for table extraction.
220
+ */
221
+ function processSingleColumn(col: any, tables: Set<string>): void {
222
+ if (!col) {
223
+ return;
224
+ }
225
+
226
+ // If the column itself is a subquery
227
+ if (col.type === "subquery" || col.type === "select") {
228
+ extractTablesFromNode(col, tables);
229
+ }
230
+
231
+ // Process expression (may contain subqueries)
232
+ if (col.expr) {
233
+ extractTablesFromNode(col.expr, tables);
234
+ }
235
+
236
+ // Process AST (alternative structure for subqueries)
237
+ if (col.ast) {
238
+ extractTablesFromNode(col.ast, tables);
239
+ }
240
+ }
241
+
208
242
  /**
209
243
  * Processes SELECT columns that may contain subqueries.
210
244
  */
@@ -215,24 +249,7 @@ function processSelectColumns(node: any, tables: Set<string>): void {
215
249
  }
216
250
 
217
251
  if (Array.isArray(columns)) {
218
- columns.forEach((col: any) => {
219
- if (!col) return;
220
-
221
- // If the column itself is a subquery
222
- if (col.type === "subquery" || col.type === "select") {
223
- extractTablesFromNode(col, tables);
224
- }
225
-
226
- // Process expression (may contain subqueries)
227
- if (col.expr) {
228
- extractTablesFromNode(col.expr, tables);
229
- }
230
-
231
- // Process AST (alternative structure for subqueries)
232
- if (col.ast) {
233
- extractTablesFromNode(col.ast, tables);
234
- }
235
- });
252
+ columns.forEach((col: any) => processSingleColumn(col, tables));
236
253
  } else if (typeof columns === "object") {
237
254
  extractTablesFromNode(columns, tables);
238
255
  }
@@ -265,14 +282,16 @@ function processUnionNode(unionNode: any, tables: Set<string>): void {
265
282
  return;
266
283
  }
267
284
 
268
- const isUnionType =
269
- unionNode.type === "select" ||
270
- unionNode.type === "union" ||
271
- unionNode.type === "union_all" ||
272
- unionNode.type === "union_distinct" ||
273
- unionNode.type === "intersect" ||
274
- unionNode.type === "except" ||
275
- unionNode.type === "minus";
285
+ const unionTypes = [
286
+ "select",
287
+ "union",
288
+ "union_all",
289
+ "union_distinct",
290
+ "intersect",
291
+ "except",
292
+ "minus",
293
+ ];
294
+ const isUnionType = unionTypes.includes(unionNode.type);
276
295
 
277
296
  if (isUnionType) {
278
297
  extractTablesFromNode(unionNode, tables);
@@ -304,13 +323,15 @@ function processUnion(node: any, tables: Set<string>): void {
304
323
  * Processes UNION/INTERSECT/EXCEPT operation nodes.
305
324
  */
306
325
  function processUnionOperation(node: any, tables: Set<string>): void {
307
- const isUnionOperation =
308
- node.type === "union" ||
309
- node.type === "union_all" ||
310
- node.type === "union_distinct" ||
311
- node.type === "intersect" ||
312
- node.type === "except" ||
313
- node.type === "minus";
326
+ const unionOperationTypes = [
327
+ "union",
328
+ "union_all",
329
+ "union_distinct",
330
+ "intersect",
331
+ "except",
332
+ "minus",
333
+ ];
334
+ const isUnionOperation = unionOperationTypes.includes(node.type);
314
335
 
315
336
  if (!isUnionOperation) {
316
337
  return;
@@ -342,27 +363,25 @@ function processNext(node: any, tables: Set<string>): void {
342
363
  /**
343
364
  * Recursively processes all object properties for any remaining nested structures.
344
365
  */
345
- function processRecursively(node: any, tables: Set<string>): void {
346
- const isLikelyAlias =
347
- (node.type === "column_ref" && !node.table) ||
348
- (node.name &&
349
- !node.table &&
350
- node.type !== "table" &&
351
- node.type !== "dual" &&
352
- node.name.length <= 2);
353
-
354
- if (isLikelyAlias || Array.isArray(node)) {
355
- return;
356
- }
366
+ /**
367
+ * Processes array values recursively.
368
+ */
369
+ function processArrayValues(values: any[], tables: Set<string>): void {
370
+ values.forEach((item: any) => {
371
+ if (item && typeof item === "object") {
372
+ extractTablesFromNode(item, tables);
373
+ }
374
+ });
375
+ }
357
376
 
377
+ /**
378
+ * Processes object values recursively.
379
+ */
380
+ function processObjectValues(node: any, tables: Set<string>): void {
358
381
  Object.values(node).forEach((value) => {
359
382
  if (value && typeof value === "object") {
360
383
  if (Array.isArray(value)) {
361
- value.forEach((item: any) => {
362
- if (item && typeof item === "object") {
363
- extractTablesFromNode(item, tables);
364
- }
365
- });
384
+ processArrayValues(value, tables);
366
385
  } else {
367
386
  extractTablesFromNode(value, tables);
368
387
  }
@@ -370,6 +389,72 @@ function processRecursively(node: any, tables: Set<string>): void {
370
389
  });
371
390
  }
372
391
 
392
+ function processRecursively(node: any, tables: Set<string>): void {
393
+ const isColumnRefAlias = node.type === "column_ref" && !node.table;
394
+ const hasName = Boolean(node.name);
395
+ const hasNoTable = !node.table;
396
+ const isNotTableType = node.type !== "table" && node.type !== "dual";
397
+ const isShortName = hasName && node.name.length <= 2;
398
+ const isShortNameAlias = hasName && hasNoTable && isNotTableType && isShortName;
399
+ const isLikelyAlias = isColumnRefAlias || isShortNameAlias;
400
+
401
+ if (isLikelyAlias || Array.isArray(node)) {
402
+ return;
403
+ }
404
+
405
+ processObjectValues(node, tables);
406
+ }
407
+
408
+ /**
409
+ * Processes DML statement types (UPDATE, INSERT, DELETE).
410
+ */
411
+ function processDmlStatements(node: any, tables: Set<string>): void {
412
+ if (node.type === "update" && node.table) {
413
+ extractTablesFromNode(node.table, tables);
414
+ } else if (node.type === "insert" && node.table) {
415
+ extractTablesFromNode(node.table, tables);
416
+ } else if (node.type === "delete" && node.from) {
417
+ extractTablesFromNode(node.from, tables);
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Processes SELECT-specific clauses (WHERE, HAVING, ORDER BY, GROUP BY).
423
+ */
424
+ function processSelectClauses(node: any, tables: Set<string>): void {
425
+ if (node.where) {
426
+ extractTablesFromNode(node.where, tables);
427
+ }
428
+ if (node.having) {
429
+ extractTablesFromNode(node.having, tables);
430
+ }
431
+ processOrderByOrGroupBy(node.orderby || node.order_by, tables);
432
+ processOrderByOrGroupBy(node.groupby || node.group_by, tables);
433
+ }
434
+
435
+ /**
436
+ * Processes subquery and SELECT statement types.
437
+ */
438
+ function processSubqueryAndSelect(node: any, tables: Set<string>): void {
439
+ if (node.type === "subquery" || node.type === "select") {
440
+ if (node.ast) {
441
+ extractTablesFromNode(node.ast, tables);
442
+ }
443
+ if (node.from) {
444
+ extractTablesFromNode(node.from, tables);
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Processes set operations (UNION, INTERSECT, EXCEPT).
451
+ */
452
+ function processSetOperations(node: any, tables: Set<string>): void {
453
+ processUnion(node, tables);
454
+ processUnionOperation(node, tables);
455
+ processNext(node, tables);
456
+ }
457
+
373
458
  /**
374
459
  * Recursively extracts table names from SQL AST node.
375
460
  * Handles regular tables, CTEs, subqueries, and complex query structures.
@@ -391,58 +476,20 @@ function extractTablesFromNode(node: any, tables: Set<string>): void {
391
476
  // Extract tables from FROM and JOIN clauses
392
477
  processFromAndJoin(node, tables);
393
478
 
394
- // Handle subqueries explicitly
395
- if (node.type === "subquery" || node.type === "select") {
396
- if (node.ast) {
397
- extractTablesFromNode(node.ast, tables);
398
- }
399
- if (node.from) {
400
- extractTablesFromNode(node.from, tables);
401
- }
402
- }
479
+ // Handle subqueries and SELECT statements
480
+ processSubqueryAndSelect(node, tables);
403
481
 
404
- // Extract tables from WHERE clause (may contain subqueries)
405
- if (node.where) {
406
- extractTablesFromNode(node.where, tables);
407
- }
482
+ // Process SELECT-specific clauses
483
+ processSelectClauses(node, tables);
408
484
 
409
485
  // Extract tables from SELECT columns (may contain subqueries)
410
486
  processSelectColumns(node, tables);
411
487
 
412
- // Extract tables from HAVING clause (may contain subqueries)
413
- if (node.having) {
414
- extractTablesFromNode(node.having, tables);
415
- }
416
-
417
- // Extract tables from ORDER BY clause (may contain subqueries)
418
- processOrderByOrGroupBy(node.orderby || node.order_by, tables);
419
-
420
- // Extract tables from GROUP BY clause (may contain subqueries)
421
- processOrderByOrGroupBy(node.groupby || node.group_by, tables);
488
+ // Process DML statements (UPDATE, INSERT, DELETE)
489
+ processDmlStatements(node, tables);
422
490
 
423
- // Extract tables from UPDATE statement
424
- if (node.type === "update" && node.table) {
425
- extractTablesFromNode(node.table, tables);
426
- }
427
-
428
- // Extract tables from INSERT statement
429
- if (node.type === "insert" && node.table) {
430
- extractTablesFromNode(node.table, tables);
431
- }
432
-
433
- // Extract tables from DELETE statement
434
- if (node.type === "delete" && node.from) {
435
- extractTablesFromNode(node.from, tables);
436
- }
437
-
438
- // Extract tables from UNION operations
439
- processUnion(node, tables);
440
-
441
- // Handle node types for UNION/INTERSECT/EXCEPT operations
442
- processUnionOperation(node, tables);
443
-
444
- // Handle _next property (alternative UNION structure)
445
- processNext(node, tables);
491
+ // Process set operations (UNION, INTERSECT, EXCEPT)
492
+ processSetOperations(node, tables);
446
493
 
447
494
  // Recursively process all object properties
448
495
  processRecursively(node, tables);
@@ -456,6 +503,33 @@ function extractTablesFromNode(node: any, tables: Set<string>): void {
456
503
  * @param options - ForgeSQL ORM options for logging
457
504
  * @returns Comma-separated string of unique table names in backticks
458
505
  */
506
+ /**
507
+ * Formats table names as backticked comma-separated string.
508
+ */
509
+ function formatBacktickedValues(tables: Set<string>): string {
510
+ return Array.from(tables)
511
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base", numeric: true }))
512
+ .map((table) => `\`${table}\``)
513
+ .join(",");
514
+ }
515
+
516
+ /**
517
+ * Extracts table names using regex fallback.
518
+ */
519
+ function extractTablesWithRegex(sql: string): Set<string> {
520
+ const regex = /`([^`]+)`/g;
521
+ const matches = new Set<string>();
522
+ let match;
523
+
524
+ while ((match = regex.exec(sql.toLowerCase())) !== null) {
525
+ if (!match[1].startsWith("a_")) {
526
+ matches.add(match[1]);
527
+ }
528
+ }
529
+
530
+ return matches;
531
+ }
532
+
459
533
  export function extractBacktickedValues(sql: string, options: ForgeSqlOrmOptions): string {
460
534
  // Try to use node-sql-parser first
461
535
  try {
@@ -471,11 +545,7 @@ export function extractBacktickedValues(sql: string, options: ForgeSqlOrmOptions
471
545
  });
472
546
 
473
547
  if (tables.size > 0) {
474
- // Sort to ensure consistent order for the same input
475
- const backtickedValues = Array.from(tables)
476
- .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base", numeric: true }))
477
- .map((table) => `\`${table}\``)
478
- .join(",");
548
+ const backtickedValues = formatBacktickedValues(tables);
479
549
  if (options.logCache) {
480
550
  // eslint-disable-next-line no-console
481
551
  console.warn(`Extracted backticked values: ${backtickedValues}`);
@@ -494,18 +564,6 @@ export function extractBacktickedValues(sql: string, options: ForgeSqlOrmOptions
494
564
  }
495
565
 
496
566
  // Fallback to regex-based extraction (original logic)
497
- const regex = /`([^`]+)`/g;
498
- const matches = new Set<string>();
499
- let match;
500
-
501
- while ((match = regex.exec(sql.toLowerCase())) !== null) {
502
- if (!match[1].startsWith("a_")) {
503
- matches.add(`\`${match[1]}\``);
504
- }
505
- }
506
-
507
- // Sort to ensure consistent order for the same input
508
- return Array.from(matches)
509
- .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base", numeric: true }))
510
- .join(",");
567
+ const matches = extractTablesWithRegex(sql);
568
+ return formatBacktickedValues(matches);
511
569
  }
@@ -16,6 +16,42 @@ const QUERY_ERROR_CODES = {
16
16
  */
17
17
  const STATEMENTS_SUMMARY_DELAY_MS = 200;
18
18
 
19
+ /**
20
+ * Checks if error is a timeout or out-of-memory error.
21
+ */
22
+ function isQueryError(error: any): { isTimeout: boolean; isOutOfMemory: boolean } {
23
+ const isTimeout = error?.code === QUERY_ERROR_CODES.TIMEOUT;
24
+ const isOutOfMemory = error?.context?.debug?.errno === QUERY_ERROR_CODES.OUT_OF_MEMORY_ERRNO;
25
+ return { isTimeout, isOutOfMemory };
26
+ }
27
+
28
+ /**
29
+ * Handles timeout or out-of-memory errors by analyzing the query.
30
+ */
31
+ async function handleQueryError(
32
+ queryStartTime: number,
33
+ forgeSqlOperation: ForgeSqlOperation,
34
+ isTimeout: boolean,
35
+ ): Promise<void> {
36
+ // Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
37
+ await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
38
+
39
+ const queryEndTime = Date.now();
40
+ const queryDuration = queryEndTime - queryStartTime;
41
+ const errorType: "OOM" | "TIMEOUT" = isTimeout ? "TIMEOUT" : "OOM";
42
+
43
+ if (isTimeout) {
44
+ // eslint-disable-next-line no-console
45
+ console.error(` TIMEOUT detected - Query exceeded time limit`);
46
+ } else {
47
+ // eslint-disable-next-line no-console
48
+ console.error(`OUT OF MEMORY detected - Query exceeded memory limit`);
49
+ }
50
+
51
+ // Analyze the failed query using CLUSTER_STATEMENTS_SUMMARY
52
+ await handleErrorsWithPlan(forgeSqlOperation, queryDuration, errorType);
53
+ }
54
+
19
55
  /**
20
56
  * Creates a proxy for the forgeDriver that injects SQL hints and handles query analysis
21
57
  * @param forgeSqlOperation - The ForgeSQL operation instance
@@ -51,28 +87,10 @@ export function createForgeDriverProxy(
51
87
  // Execute the query with injected hints
52
88
  return await forgeDriver(modifiedQuery, params, method);
53
89
  } catch (error: any) {
54
- // Check if this is a timeout or out-of-memory error that we want to analyze
55
- const isTimeoutError = error.code === QUERY_ERROR_CODES.TIMEOUT;
56
- const isOutOfMemoryError =
57
- error?.context?.debug?.errno === QUERY_ERROR_CODES.OUT_OF_MEMORY_ERRNO;
58
-
59
- if (isTimeoutError || isOutOfMemoryError) {
60
- // Wait for CLUSTER_STATEMENTS_SUMMARY to be populated with our failed query data
61
- await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
90
+ const { isTimeout, isOutOfMemory } = isQueryError(error);
62
91
 
63
- const queryEndTime = Date.now();
64
- const queryDuration = queryEndTime - queryStartTime;
65
- let errorType: "OOM" | "TIMEOUT" = "TIMEOUT";
66
- if (isTimeoutError) {
67
- // eslint-disable-next-line no-console
68
- console.error(` TIMEOUT detected - Query exceeded time limit`);
69
- } else {
70
- // eslint-disable-next-line no-console
71
- console.error(`OUT OF MEMORY detected - Query exceeded memory limit`);
72
- errorType = "OOM";
73
- }
74
- // Analyze the failed query using CLUSTER_STATEMENTS_SUMMARY
75
- await handleErrorsWithPlan(forgeSqlOperation, queryDuration, errorType);
92
+ if (isTimeout || isOutOfMemory) {
93
+ await handleQueryError(queryStartTime, forgeSqlOperation, isTimeout);
76
94
  }
77
95
 
78
96
  // Log SQL error details if requested
@@ -26,38 +26,33 @@ export function injectSqlHints(query: string, hints?: SqlHints): string {
26
26
  // Normalize the query for easier matching
27
27
  const normalizedQuery = query.trim().toUpperCase();
28
28
 
29
- // Get the appropriate hints based on query type
29
+ // Map query types to their hints
30
+ const queryTypeMap: Record<string, string[] | undefined> = {
31
+ SELECT: hints.select,
32
+ INSERT: hints.insert,
33
+ UPDATE: hints.update,
34
+ DELETE: hints.delete,
35
+ };
36
+
37
+ // Find matching query type and get hints
30
38
  let queryHints: string[] | undefined;
31
-
32
- if (normalizedQuery.startsWith("SELECT")) {
33
- queryHints = hints.select;
34
- } else if (normalizedQuery.startsWith("INSERT")) {
35
- queryHints = hints.insert;
36
- } else if (normalizedQuery.startsWith("UPDATE")) {
37
- queryHints = hints.update;
38
- } else if (normalizedQuery.startsWith("DELETE")) {
39
- queryHints = hints.delete;
39
+ let queryPrefix: string | null = null;
40
+
41
+ for (const [type, typeHints] of Object.entries(queryTypeMap)) {
42
+ if (normalizedQuery.startsWith(type)) {
43
+ queryPrefix = type;
44
+ queryHints = typeHints;
45
+ break;
46
+ }
40
47
  }
41
48
 
42
49
  // If no hints for this query type, return original query
43
- if (!queryHints || queryHints.length === 0) {
50
+ if (!queryHints || queryHints.length === 0 || !queryPrefix) {
44
51
  return query;
45
52
  }
46
53
 
47
- // Join all hints with spaces
54
+ // Join all hints with spaces and inject into query
48
55
  const hintsString = queryHints.join(" ");
49
-
50
- // Inject hints into the query
51
- if (normalizedQuery.startsWith("SELECT")) {
52
- return `SELECT /*+ ${hintsString} */ ${query.substring(6)}`;
53
- } else if (normalizedQuery.startsWith("INSERT")) {
54
- return `INSERT /*+ ${hintsString} */ ${query.substring(6)}`;
55
- } else if (normalizedQuery.startsWith("UPDATE")) {
56
- return `UPDATE /*+ ${hintsString} */ ${query.substring(6)}`;
57
- } else if (normalizedQuery.startsWith("DELETE")) {
58
- return `DELETE /*+ ${hintsString} */ ${query.substring(6)}`;
59
- }
60
-
61
- // If no match found, return original query
62
- return query;
56
+ const prefixLength = queryPrefix.length;
57
+ return `${queryPrefix} /*+ ${hintsString} */ ${query.substring(prefixLength)}`;
63
58
  }