forge-sql-orm 2.1.23 → 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.
- package/README.md +1 -0
- package/dist/core/ForgeSQLAnalyseOperations.d.ts +4 -0
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.js +17 -21
- package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts +16 -0
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.js +60 -28
- package/dist/core/ForgeSQLCrudOperations.js.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +15 -28
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js +20 -47
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/Rovo.d.ts +32 -0
- package/dist/core/Rovo.d.ts.map +1 -1
- package/dist/core/Rovo.js +116 -67
- package/dist/core/Rovo.js.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +168 -118
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
- package/dist/utils/cacheTableUtils.d.ts +0 -8
- package/dist/utils/cacheTableUtils.d.ts.map +1 -1
- package/dist/utils/cacheTableUtils.js +183 -126
- package/dist/utils/cacheTableUtils.js.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.js +31 -20
- package/dist/utils/forgeDriverProxy.js.map +1 -1
- package/dist/utils/sqlHints.d.ts.map +1 -1
- package/dist/utils/sqlHints.js +19 -29
- package/dist/utils/sqlHints.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +0 -29
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +107 -78
- package/dist/utils/sqlUtils.js.map +1 -1
- package/package.json +8 -8
- package/src/core/ForgeSQLAnalyseOperations.ts +18 -21
- package/src/core/ForgeSQLCrudOperations.ts +83 -33
- package/src/core/ForgeSQLQueryBuilder.ts +59 -154
- package/src/core/Rovo.ts +158 -98
- package/src/lib/drizzle/extensions/additionalActions.ts +287 -382
- package/src/utils/cacheTableUtils.ts +202 -144
- package/src/utils/forgeDriverProxy.ts +39 -21
- package/src/utils/sqlHints.ts +21 -26
- 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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
|
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
|
|
395
|
-
|
|
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
|
-
//
|
|
405
|
-
|
|
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
|
-
//
|
|
413
|
-
|
|
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
|
-
//
|
|
424
|
-
|
|
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
|
-
|
|
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
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
package/src/utils/sqlHints.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
}
|