forge-sql-orm 2.1.14 → 2.1.15
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 +290 -20
- package/dist/core/ForgeSQLORM.d.ts +16 -7
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.js +73 -15
- package/dist/core/ForgeSQLORM.js.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +13 -4
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/utils/forgeDriver.d.ts +3 -2
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriver.js +20 -16
- package/dist/utils/forgeDriver.js.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +27 -1
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +215 -10
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/webtriggers/index.d.ts +1 -0
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist/webtriggers/index.js +1 -0
- package/dist/webtriggers/index.js.map +1 -1
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +60 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.js +55 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.js.map +1 -0
- package/package.json +10 -9
- package/src/core/ForgeSQLORM.ts +78 -14
- package/src/core/ForgeSQLQueryBuilder.ts +13 -3
- package/src/utils/forgeDriver.ts +34 -19
- package/src/utils/metadataContextUtils.ts +267 -10
- package/src/webtriggers/index.ts +1 -0
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +69 -0
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
[](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml)
|
|
11
11
|
[](https://coveralls.io/github/vzakharchenko/forge-sql-orm?branch=master)
|
|
12
12
|
[](https://deepscan.io/dashboard#view=project&tid=26652&pid=29272&bid=940614)
|
|
13
|
+
[](https://snyk.io/test/github/vzakharchenko/forge-sql-orm)
|
|
13
14
|
|
|
14
15
|
**Forge-SQL-ORM** is an ORM designed for working with [@forge/sql](https://developer.atlassian.com/platform/forge/storage-reference/sql-tutorial/) in **Atlassian Forge**. It is built on top of [Drizzle ORM](https://orm.drizzle.team) and provides advanced capabilities for working with relational databases inside Forge.
|
|
15
16
|
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
- ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
|
|
23
24
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
24
25
|
- ✅ **Advanced Query Methods**: `selectFrom()`, `selectDistinctFrom()`, `selectCacheableFrom()`, `selectDistinctCacheableFrom()` for all-column queries with field aliasing
|
|
25
|
-
- ✅ **Query Execution with Metadata**: `executeWithMetadata()` method for capturing detailed execution metrics including database execution time, response size, and query analysis capabilities with performance monitoring
|
|
26
|
+
- ✅ **Query Execution with Metadata**: `executeWithMetadata()` method for capturing detailed execution metrics including database execution time, response size, and query analysis capabilities with performance monitoring. Supports two modes for query plan printing: TopSlowest mode (default) and SummaryTable mode
|
|
26
27
|
- ✅ **Raw SQL Execution**: `execute()`, `executeCacheable()`, `executeDDL()`, and `executeDDLActions()` methods for direct SQL queries with local and global caching
|
|
27
28
|
- ✅ **Common Table Expressions (CTEs)**: `with()` method for complex queries with subqueries
|
|
28
29
|
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
@@ -347,6 +348,11 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
347
348
|
console.debug(`[Performance Debug fetch] High DB time: ${totalDbExecutionTime} ms`);
|
|
348
349
|
}
|
|
349
350
|
},
|
|
351
|
+
{
|
|
352
|
+
// Optional: Configure query plan printing behavior
|
|
353
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
354
|
+
topQueries: 3, // Print top 3 slowest queries
|
|
355
|
+
},
|
|
350
356
|
);
|
|
351
357
|
} catch (e) {
|
|
352
358
|
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
@@ -356,6 +362,19 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
356
362
|
});
|
|
357
363
|
```
|
|
358
364
|
|
|
365
|
+
**Query Plan Printing Options:**
|
|
366
|
+
|
|
367
|
+
The `printQueriesWithPlan` function supports two modes:
|
|
368
|
+
|
|
369
|
+
1. **TopSlowest Mode (default)**: Prints execution plans for the slowest queries from the current resolver invocation
|
|
370
|
+
- `mode`: Set to `'TopSlowest'` (default)
|
|
371
|
+
- `topQueries`: Number of top slowest queries to analyze (default: 1)
|
|
372
|
+
|
|
373
|
+
2. **SummaryTable Mode**: Uses `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
374
|
+
- `mode`: Set to `'SummaryTable'`
|
|
375
|
+
- `summaryTableWindowTime`: Time window in milliseconds (default: 15000ms)
|
|
376
|
+
- Only works if queries are executed within the specified time window
|
|
377
|
+
|
|
359
378
|
### 5. Rovo Integration (Secure Analytics)
|
|
360
379
|
|
|
361
380
|
```typescript
|
|
@@ -452,6 +471,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
452
471
|
|
|
453
472
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
454
473
|
},
|
|
474
|
+
{
|
|
475
|
+
// Optional: Configure query plan printing
|
|
476
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
477
|
+
topQueries: 2, // Print top 2 slowest queries
|
|
478
|
+
},
|
|
455
479
|
);
|
|
456
480
|
|
|
457
481
|
// DDL operations for schema modifications
|
|
@@ -589,7 +613,12 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
589
613
|
}
|
|
590
614
|
|
|
591
615
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
592
|
-
}
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
// Optional: Configure query plan printing
|
|
619
|
+
mode: 'TopSlowest', // Print top slowest queries (default)
|
|
620
|
+
topQueries: 1, // Print top slowest query
|
|
621
|
+
},
|
|
593
622
|
);
|
|
594
623
|
```
|
|
595
624
|
|
|
@@ -993,23 +1022,23 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
|
|
|
993
1022
|
|
|
994
1023
|
### When to Use Each Approach
|
|
995
1024
|
|
|
996
|
-
| Method | Use Case
|
|
997
|
-
| ---------------------------------------------------------------------- |
|
|
998
|
-
| `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations
|
|
999
|
-
| `insertAndEvictCache()` | Simple inserts without conflicts
|
|
1000
|
-
| `updateAndEvictCache()` | Simple updates without conflicts
|
|
1001
|
-
| `deleteAndEvictCache()` | Simple deletes without conflicts
|
|
1002
|
-
| `insert/update/delete` | Basic Drizzle operations
|
|
1003
|
-
| `selectFrom()` | All-column queries with field aliasing
|
|
1004
|
-
| `selectDistinctFrom()` | Distinct all-column queries with field aliasing
|
|
1005
|
-
| `selectCacheableFrom()` | All-column queries with field aliasing and caching
|
|
1006
|
-
| `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching
|
|
1007
|
-
| `execute()` | Raw SQL queries with local caching
|
|
1008
|
-
| `executeCacheable()` | Raw SQL queries with local and global caching
|
|
1009
|
-
| `executeWithMetadata()` |
|
|
1010
|
-
| `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.)
|
|
1011
|
-
| `executeDDLActions()` | Execute regular SQL queries in DDL operation context
|
|
1012
|
-
| `with()` | Common Table Expressions (CTEs)
|
|
1025
|
+
| Method | Use Case | Versioning | Cache Management |
|
|
1026
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------- |
|
|
1027
|
+
| `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations | ❌ No | Cache Context |
|
|
1028
|
+
| `insertAndEvictCache()` | Simple inserts without conflicts | ❌ No | ✅ Yes |
|
|
1029
|
+
| `updateAndEvictCache()` | Simple updates without conflicts | ❌ No | ✅ Yes |
|
|
1030
|
+
| `deleteAndEvictCache()` | Simple deletes without conflicts | ❌ No | ✅ Yes |
|
|
1031
|
+
| `insert/update/delete` | Basic Drizzle operations | ❌ No | ❌ No |
|
|
1032
|
+
| `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
|
|
1033
|
+
| `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
|
|
1034
|
+
| `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
1035
|
+
| `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
1036
|
+
| `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
|
|
1037
|
+
| `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
|
|
1038
|
+
| `executeWithMetadata()` | Resolver-level profiling with execution metrics and configurable query plan printing (TopSlowest or SummaryTable mode) | ❌ No | Local Cache |
|
|
1039
|
+
| `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.) | ❌ No | No Caching |
|
|
1040
|
+
| `executeDDLActions()` | Execute regular SQL queries in DDL operation context | ❌ No | No Caching |
|
|
1041
|
+
| `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
|
|
1013
1042
|
|
|
1014
1043
|
where Cache context - allows you to batch cache invalidation events and bypass cache reads for affected tables.
|
|
1015
1044
|
|
|
@@ -1413,7 +1442,12 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
1413
1442
|
}
|
|
1414
1443
|
|
|
1415
1444
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1416
|
-
}
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
// Optional: Configure query plan printing
|
|
1448
|
+
mode: 'TopSlowest', // Print top slowest queries (default)
|
|
1449
|
+
topQueries: 1, // Print top slowest query
|
|
1450
|
+
},
|
|
1417
1451
|
);
|
|
1418
1452
|
|
|
1419
1453
|
// Using executeDDL() for DDL operations (CREATE, ALTER, DROP, etc.)
|
|
@@ -1781,6 +1815,11 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
1781
1815
|
|
|
1782
1816
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1783
1817
|
},
|
|
1818
|
+
{
|
|
1819
|
+
// Optional: Configure query plan printing
|
|
1820
|
+
topQueries: 1, // Print top slowest query (default)
|
|
1821
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
1822
|
+
},
|
|
1784
1823
|
);
|
|
1785
1824
|
|
|
1786
1825
|
// Insert operation - evicts local cache for users table
|
|
@@ -1982,6 +2021,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
1982
2021
|
|
|
1983
2022
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1984
2023
|
},
|
|
2024
|
+
{
|
|
2025
|
+
// Optional: Configure query plan printing
|
|
2026
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2027
|
+
topQueries: 1, // Print top slowest query
|
|
2028
|
+
},
|
|
1985
2029
|
);
|
|
1986
2030
|
```
|
|
1987
2031
|
|
|
@@ -2622,6 +2666,227 @@ The error analysis mechanism:
|
|
|
2622
2666
|
|
|
2623
2667
|
> **💡 Tip**: The automatic error analysis only triggers for timeout and OOM errors. Other errors are logged normally without plan analysis.
|
|
2624
2668
|
|
|
2669
|
+
### Resolver-Level Performance Monitoring
|
|
2670
|
+
|
|
2671
|
+
The `executeWithMetadata()` method provides resolver-level profiling with configurable query plan printing. It aggregates metrics across all database operations within a resolver and supports two modes for query plan analysis.
|
|
2672
|
+
|
|
2673
|
+
#### Basic Usage
|
|
2674
|
+
|
|
2675
|
+
```typescript
|
|
2676
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2677
|
+
async () => {
|
|
2678
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2679
|
+
const orders = await forgeSQL
|
|
2680
|
+
.selectFrom(ordersTable)
|
|
2681
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
2682
|
+
return { users, orders };
|
|
2683
|
+
},
|
|
2684
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2685
|
+
const threshold = 500; // ms baseline for this resolver
|
|
2686
|
+
|
|
2687
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2688
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
2689
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2690
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
2691
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2695
|
+
},
|
|
2696
|
+
);
|
|
2697
|
+
```
|
|
2698
|
+
|
|
2699
|
+
#### Query Plan Printing Options
|
|
2700
|
+
|
|
2701
|
+
The `printQueriesWithPlan` function supports two modes, configurable via the optional `options` parameter:
|
|
2702
|
+
|
|
2703
|
+
**1. TopSlowest Mode (default)**: Prints execution plans for the slowest queries from the current resolver invocation
|
|
2704
|
+
|
|
2705
|
+
```typescript
|
|
2706
|
+
// Full configuration example
|
|
2707
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2708
|
+
async () => {
|
|
2709
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2710
|
+
return users;
|
|
2711
|
+
},
|
|
2712
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2713
|
+
if (totalDbExecutionTime > 1000) {
|
|
2714
|
+
await printQueriesWithPlan(); // Will print top 3 slowest queries with execution plans
|
|
2715
|
+
}
|
|
2716
|
+
},
|
|
2717
|
+
{
|
|
2718
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2719
|
+
topQueries: 3, // Number of top slowest queries to analyze (default: 1)
|
|
2720
|
+
showSlowestPlans: true, // Show execution plans (default: true)
|
|
2721
|
+
},
|
|
2722
|
+
);
|
|
2723
|
+
|
|
2724
|
+
// Minimal configuration - only specify what you need
|
|
2725
|
+
const result2 = await forgeSQL.executeWithMetadata(
|
|
2726
|
+
async () => {
|
|
2727
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2728
|
+
return users;
|
|
2729
|
+
},
|
|
2730
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2731
|
+
if (totalDbExecutionTime > 1000) {
|
|
2732
|
+
await printQueriesWithPlan(); // Will print top 3 slowest queries (all other options use defaults)
|
|
2733
|
+
}
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
topQueries: 3, // Only specify topQueries, mode and showSlowestPlans use defaults
|
|
2737
|
+
},
|
|
2738
|
+
);
|
|
2739
|
+
|
|
2740
|
+
// Disable execution plans - only show SQL and execution time
|
|
2741
|
+
const result3 = await forgeSQL.executeWithMetadata(
|
|
2742
|
+
async () => {
|
|
2743
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2744
|
+
return users;
|
|
2745
|
+
},
|
|
2746
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2747
|
+
if (totalDbExecutionTime > 1000) {
|
|
2748
|
+
await printQueriesWithPlan(); // Will print SQL and time only, no execution plans
|
|
2749
|
+
}
|
|
2750
|
+
},
|
|
2751
|
+
{
|
|
2752
|
+
showSlowestPlans: false, // Disable execution plan printing
|
|
2753
|
+
},
|
|
2754
|
+
);
|
|
2755
|
+
|
|
2756
|
+
// Use all defaults - pass empty object or omit options parameter
|
|
2757
|
+
const result4 = await forgeSQL.executeWithMetadata(
|
|
2758
|
+
async () => {
|
|
2759
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2760
|
+
return users;
|
|
2761
|
+
},
|
|
2762
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2763
|
+
if (totalDbExecutionTime > 1000) {
|
|
2764
|
+
await printQueriesWithPlan(); // Uses all defaults: TopSlowest mode, topQueries: 1, showSlowestPlans: true
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
{}, // Empty object - all options use defaults
|
|
2768
|
+
);
|
|
2769
|
+
```
|
|
2770
|
+
|
|
2771
|
+
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
|
2772
|
+
read_file
|
|
2773
|
+
|
|
2774
|
+
**2. SummaryTable Mode**: Uses `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
2775
|
+
|
|
2776
|
+
```typescript
|
|
2777
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2778
|
+
async () => {
|
|
2779
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2780
|
+
return users;
|
|
2781
|
+
},
|
|
2782
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2783
|
+
if (totalDbExecutionTime > 1000) {
|
|
2784
|
+
await printQueriesWithPlan(); // Will use CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
2785
|
+
}
|
|
2786
|
+
},
|
|
2787
|
+
{
|
|
2788
|
+
mode: "SummaryTable", // Use SummaryTable mode
|
|
2789
|
+
summaryTableWindowTime: 10000, // Time window in milliseconds (default: 15000ms)
|
|
2790
|
+
},
|
|
2791
|
+
);
|
|
2792
|
+
```
|
|
2793
|
+
|
|
2794
|
+
#### Configuration Options
|
|
2795
|
+
|
|
2796
|
+
All options are **optional**. If not specified, default values are used. You can pass only the options you need to customize.
|
|
2797
|
+
|
|
2798
|
+
| Option | Type | Default | Description |
|
|
2799
|
+
| ------------------------ | -------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2800
|
+
| `mode` | `'TopSlowest' \| 'SummaryTable'` | `'TopSlowest'` | Query plan printing mode. `'TopSlowest'` prints execution plans for the slowest queries from the current resolver. `'SummaryTable'` uses `CLUSTER_STATEMENTS_SUMMARY` when within time window |
|
|
2801
|
+
| `summaryTableWindowTime` | `number` | `15000` | Time window in milliseconds for summary table queries. Only used when `mode` is `'SummaryTable'` |
|
|
2802
|
+
| `topQueries` | `number` | `1` | Number of top slowest queries to analyze when `mode` is `'TopSlowest'` |
|
|
2803
|
+
| `showSlowestPlans` | `boolean` | `true` | Whether to show execution plans for slowest queries in TopSlowest mode. If `false`, only SQL and execution time are printed |
|
|
2804
|
+
| `normalizeQuery` | `boolean` | `true` | Whether to normalize SQL queries by replacing parameter values with `?` placeholders. Set to `false` to disable normalization if it causes issues with complex queries |
|
|
2805
|
+
|
|
2806
|
+
**Examples:**
|
|
2807
|
+
|
|
2808
|
+
```typescript
|
|
2809
|
+
// Use all defaults - omit options or pass empty object
|
|
2810
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn); // or { }
|
|
2811
|
+
|
|
2812
|
+
// Customize only what you need
|
|
2813
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { topQueries: 3 });
|
|
2814
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { mode: "SummaryTable" });
|
|
2815
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { showSlowestPlans: false });
|
|
2816
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { normalizeQuery: false }); // Disable query normalization
|
|
2817
|
+
|
|
2818
|
+
// Combine multiple options
|
|
2819
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, {
|
|
2820
|
+
mode: "TopSlowest",
|
|
2821
|
+
topQueries: 5,
|
|
2822
|
+
showSlowestPlans: false,
|
|
2823
|
+
normalizeQuery: true, // Enable query normalization (default)
|
|
2824
|
+
});
|
|
2825
|
+
```
|
|
2826
|
+
|
|
2827
|
+
#### How It Works
|
|
2828
|
+
|
|
2829
|
+
1. **TopSlowest Mode** (default):
|
|
2830
|
+
- Collects all queries executed within the resolver
|
|
2831
|
+
- Sorts them by execution time (slowest first)
|
|
2832
|
+
- Prints execution plans for the top N queries (configurable via `topQueries`)
|
|
2833
|
+
- If `showSlowestPlans` is `false`, only prints SQL and execution time without plans
|
|
2834
|
+
- Works immediately after query execution
|
|
2835
|
+
|
|
2836
|
+
2. **SummaryTable Mode**:
|
|
2837
|
+
- Attempts to use `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
2838
|
+
- Only works if queries are executed within the specified time window (`summaryTableWindowTime`)
|
|
2839
|
+
- If the time window expires, falls back to TopSlowest mode
|
|
2840
|
+
- Provides aggregated statistics from TiDB's system tables
|
|
2841
|
+
|
|
2842
|
+
#### Example: Real-World Resolver
|
|
2843
|
+
|
|
2844
|
+
```typescript
|
|
2845
|
+
resolver.define("fetch", async (req: Request) => {
|
|
2846
|
+
try {
|
|
2847
|
+
return await forgeSQL.executeWithMetadata(
|
|
2848
|
+
async () => {
|
|
2849
|
+
const users = await forgeSQL.selectFrom(demoUsers);
|
|
2850
|
+
const orders = await forgeSQL
|
|
2851
|
+
.selectFrom(demoOrders)
|
|
2852
|
+
.where(eq(demoOrders.userId, demoUsers.id));
|
|
2853
|
+
return { users, orders };
|
|
2854
|
+
},
|
|
2855
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2856
|
+
const threshold = 500; // ms baseline for this resolver
|
|
2857
|
+
|
|
2858
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2859
|
+
console.warn(
|
|
2860
|
+
`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`,
|
|
2861
|
+
);
|
|
2862
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2863
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
2864
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2865
|
+
}
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2869
|
+
topQueries: 2, // Print top 2 slowest queries
|
|
2870
|
+
},
|
|
2871
|
+
);
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
2874
|
+
console.error(error, e);
|
|
2875
|
+
throw error;
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
```
|
|
2879
|
+
|
|
2880
|
+
#### Benefits
|
|
2881
|
+
|
|
2882
|
+
- **Resolver-Level Profiling**: Aggregates metrics across all database operations in a resolver
|
|
2883
|
+
- **Configurable Analysis**: Choose between TopSlowest mode or SummaryTable mode
|
|
2884
|
+
- **Automatic Plan Formatting**: Execution plans are formatted in a readable format
|
|
2885
|
+
- **Performance Thresholds**: Set custom thresholds for performance warnings
|
|
2886
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
2887
|
+
|
|
2888
|
+
> **💡 Tip**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis when using SummaryTable mode, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
2889
|
+
|
|
2625
2890
|
### Slow Query Monitoring
|
|
2626
2891
|
|
|
2627
2892
|
Forge-SQL-ORM provides a scheduler trigger (`slowQuerySchedulerTrigger`) that automatically monitors and analyzes slow queries on an hourly basis. This trigger queries TiDB's slow query log system table and provides detailed performance information including SQL query text, memory usage, execution time, and execution plans.
|
|
@@ -2912,6 +3177,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
2912
3177
|
|
|
2913
3178
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2914
3179
|
},
|
|
3180
|
+
{
|
|
3181
|
+
// Optional: Configure query plan printing
|
|
3182
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
3183
|
+
topQueries: 1, // Print top slowest query
|
|
3184
|
+
},
|
|
2915
3185
|
);
|
|
2916
3186
|
|
|
2917
3187
|
// ✅ DDL operations for schema modifications
|
|
@@ -8,6 +8,7 @@ import { MySqlTable } from "drizzle-orm/mysql-core/table";
|
|
|
8
8
|
import { MySqlDeleteBase, MySqlInsertBuilder, MySqlUpdateBuilder } from "drizzle-orm/mysql-core/query-builders";
|
|
9
9
|
import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
10
10
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
11
|
+
import { MetadataQueryOptions } from "../utils/metadataContextUtils";
|
|
11
12
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
12
13
|
/**
|
|
13
14
|
* Public class that acts as a wrapper around the private ForgeSQLORMImpl.
|
|
@@ -29,7 +30,15 @@ declare class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
29
30
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
30
31
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
31
32
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
32
|
-
* @param onMetadata.
|
|
33
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
34
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
35
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
36
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
37
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
38
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
39
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
40
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
41
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
33
42
|
* @returns Promise with the query result
|
|
34
43
|
*
|
|
35
44
|
* @example
|
|
@@ -41,12 +50,12 @@ declare class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
41
50
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
42
51
|
* return { users, orders };
|
|
43
52
|
* },
|
|
44
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
53
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
45
54
|
* const threshold = 500; // ms baseline for this resolver
|
|
46
55
|
*
|
|
47
56
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
48
57
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
49
|
-
* await
|
|
58
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
50
59
|
* } else if (totalDbExecutionTime > threshold) {
|
|
51
60
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
52
61
|
* }
|
|
@@ -69,12 +78,12 @@ declare class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
69
78
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
70
79
|
* return { users, orders };
|
|
71
80
|
* },
|
|
72
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
81
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
73
82
|
* const threshold = 500; // ms baseline for this resolver
|
|
74
83
|
*
|
|
75
84
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
76
85
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
77
|
-
* await
|
|
86
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
78
87
|
* } else if (totalDbExecutionTime > threshold) {
|
|
79
88
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
80
89
|
* }
|
|
@@ -90,9 +99,9 @@ declare class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
90
99
|
* });
|
|
91
100
|
* ```
|
|
92
101
|
*
|
|
93
|
-
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `
|
|
102
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
94
103
|
*/
|
|
95
|
-
executeWithMetadata<T>(query: () => Promise<T>, onMetadata: (totalDbExecutionTime: number, totalResponseSize: number, printQueriesWithPlan: () => Promise<void>) => Promise<void> | void): Promise<T>;
|
|
104
|
+
executeWithMetadata<T>(query: () => Promise<T>, onMetadata: (totalDbExecutionTime: number, totalResponseSize: number, printQueriesWithPlan: () => Promise<void>) => Promise<void> | void, options?: MetadataQueryOptions): Promise<T>;
|
|
96
105
|
selectCacheable<TSelection extends SelectedFields>(fields: TSelection, cacheTTL?: number): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
|
|
97
106
|
selectDistinctCacheable<TSelection extends SelectedFields>(fields: TSelection, cacheTTL?: number): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
|
|
98
107
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ForgeSQLORM.d.ts","sourceRoot":"","sources":["../../src/core/ForgeSQLORM.ts"],"names":[],"mappings":"AACA,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAEL,mBAAmB,EACnB,2BAA2B,EAC3B,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAEvB,0BAA0B,EAC1B,kCAAkC,EAClC,yBAAyB,EACzB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ForgeSQLORM.d.ts","sourceRoot":"","sources":["../../src/core/ForgeSQLORM.ts"],"names":[],"mappings":"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"}
|
package/dist/core/ForgeSQLORM.js
CHANGED
|
@@ -73,7 +73,15 @@ class ForgeSQLORMImpl {
|
|
|
73
73
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
74
74
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
75
75
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
76
|
-
* @param onMetadata.
|
|
76
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
77
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
78
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
79
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
80
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
81
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
82
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
83
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
84
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
77
85
|
* @returns Promise with the query result
|
|
78
86
|
*
|
|
79
87
|
* @example
|
|
@@ -85,12 +93,12 @@ class ForgeSQLORMImpl {
|
|
|
85
93
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
86
94
|
* return { users, orders };
|
|
87
95
|
* },
|
|
88
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
96
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
89
97
|
* const threshold = 500; // ms baseline for this resolver
|
|
90
98
|
*
|
|
91
99
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
92
100
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
93
|
-
* await
|
|
101
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
94
102
|
* } else if (totalDbExecutionTime > threshold) {
|
|
95
103
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
96
104
|
* }
|
|
@@ -113,12 +121,12 @@ class ForgeSQLORMImpl {
|
|
|
113
121
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
114
122
|
* return { users, orders };
|
|
115
123
|
* },
|
|
116
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
124
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
117
125
|
* const threshold = 500; // ms baseline for this resolver
|
|
118
126
|
*
|
|
119
127
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
120
128
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
121
|
-
* await
|
|
129
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
122
130
|
* } else if (totalDbExecutionTime > threshold) {
|
|
123
131
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
124
132
|
* }
|
|
@@ -134,9 +142,49 @@ class ForgeSQLORMImpl {
|
|
|
134
142
|
* });
|
|
135
143
|
* ```
|
|
136
144
|
*
|
|
137
|
-
* @
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* // Using TopSlowest mode with custom topQueries
|
|
148
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
149
|
+
* async () => {
|
|
150
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
151
|
+
* return users;
|
|
152
|
+
* },
|
|
153
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
154
|
+
* if (totalDbExecutionTime > 1000) {
|
|
155
|
+
* await printQueriesWithPlan(); // Will print top 3 slowest queries
|
|
156
|
+
* }
|
|
157
|
+
* },
|
|
158
|
+
* {
|
|
159
|
+
* mode: 'TopSlowest', // Print top slowest queries (default)
|
|
160
|
+
* topQueries: 3, // Print top 3 slowest queries
|
|
161
|
+
* }
|
|
162
|
+
* );
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* // Using SummaryTable mode for query analysis
|
|
168
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
169
|
+
* async () => {
|
|
170
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
171
|
+
* return users;
|
|
172
|
+
* },
|
|
173
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
174
|
+
* if (totalDbExecutionTime > 1000) {
|
|
175
|
+
* await printQueriesWithPlan(); // Will use CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
176
|
+
* }
|
|
177
|
+
* },
|
|
178
|
+
* {
|
|
179
|
+
* mode: 'SummaryTable', // Use summary tables mode
|
|
180
|
+
* summaryTableWindowTime: 10000, // 10 second window
|
|
181
|
+
* }
|
|
182
|
+
* );
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
138
186
|
*/
|
|
139
|
-
async executeWithMetadata(query, onMetadata) {
|
|
187
|
+
async executeWithMetadata(query, onMetadata, options) {
|
|
140
188
|
return metadataContextUtils_1.metadataQueryContext.run({
|
|
141
189
|
totalDbExecutionTime: 0,
|
|
142
190
|
totalResponseSize: 0,
|
|
@@ -145,6 +193,8 @@ class ForgeSQLORMImpl {
|
|
|
145
193
|
printQueriesWithPlan: async () => {
|
|
146
194
|
return;
|
|
147
195
|
},
|
|
196
|
+
options: options,
|
|
197
|
+
statistics: [],
|
|
148
198
|
}, async () => {
|
|
149
199
|
const result = await query();
|
|
150
200
|
const metadata = await (0, metadataContextUtils_1.getLastestMetadata)();
|
|
@@ -741,7 +791,15 @@ class ForgeSQLORM {
|
|
|
741
791
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
742
792
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
743
793
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
744
|
-
* @param onMetadata.
|
|
794
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
795
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
796
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
797
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
798
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
799
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
800
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
801
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
802
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
745
803
|
* @returns Promise with the query result
|
|
746
804
|
*
|
|
747
805
|
* @example
|
|
@@ -753,12 +811,12 @@ class ForgeSQLORM {
|
|
|
753
811
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
754
812
|
* return { users, orders };
|
|
755
813
|
* },
|
|
756
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
814
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
757
815
|
* const threshold = 500; // ms baseline for this resolver
|
|
758
816
|
*
|
|
759
817
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
760
818
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
761
|
-
* await
|
|
819
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
762
820
|
* } else if (totalDbExecutionTime > threshold) {
|
|
763
821
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
764
822
|
* }
|
|
@@ -781,12 +839,12 @@ class ForgeSQLORM {
|
|
|
781
839
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
782
840
|
* return { users, orders };
|
|
783
841
|
* },
|
|
784
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
842
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
785
843
|
* const threshold = 500; // ms baseline for this resolver
|
|
786
844
|
*
|
|
787
845
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
788
846
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
789
|
-
* await
|
|
847
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
790
848
|
* } else if (totalDbExecutionTime > threshold) {
|
|
791
849
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
792
850
|
* }
|
|
@@ -802,10 +860,10 @@ class ForgeSQLORM {
|
|
|
802
860
|
* });
|
|
803
861
|
* ```
|
|
804
862
|
*
|
|
805
|
-
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `
|
|
863
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
806
864
|
*/
|
|
807
|
-
async executeWithMetadata(query, onMetadata) {
|
|
808
|
-
return this.ormInstance.executeWithMetadata(query, onMetadata);
|
|
865
|
+
async executeWithMetadata(query, onMetadata, options) {
|
|
866
|
+
return this.ormInstance.executeWithMetadata(query, onMetadata, options);
|
|
809
867
|
}
|
|
810
868
|
selectCacheable(fields, cacheTTL) {
|
|
811
869
|
return this.ormInstance.selectCacheable(fields, cacheTTL);
|