forge-sql-orm 2.1.14 → 2.1.16
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 +294 -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 +15 -7
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +2 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.js.map +1 -1
- package/dist/core/Rovo.d.ts +40 -0
- package/dist/core/Rovo.d.ts.map +1 -1
- package/dist/core/Rovo.js +164 -138
- package/dist/core/Rovo.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +72 -22
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
- package/dist/utils/cacheTableUtils.d.ts +11 -0
- package/dist/utils/cacheTableUtils.d.ts.map +1 -0
- package/dist/utils/cacheTableUtils.js +450 -0
- package/dist/utils/cacheTableUtils.js.map +1 -0
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.js +3 -22
- package/dist/utils/cacheUtils.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 +24 -27
- 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 +237 -10
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +1 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +217 -119
- package/dist/utils/sqlUtils.js.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.js +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 +11 -10
- package/src/core/ForgeSQLORM.ts +78 -14
- package/src/core/ForgeSQLQueryBuilder.ts +15 -5
- package/src/core/ForgeSQLSelectOperations.ts +2 -1
- package/src/core/Rovo.ts +209 -167
- package/src/index.ts +1 -3
- package/src/lib/drizzle/extensions/additionalActions.ts +98 -42
- package/src/utils/cacheTableUtils.ts +511 -0
- package/src/utils/cacheUtils.ts +3 -25
- package/src/utils/forgeDriver.ts +38 -29
- package/src/utils/metadataContextUtils.ts +290 -10
- package/src/utils/sqlUtils.ts +298 -142
- package/src/webtriggers/applyMigrationsWebTrigger.ts +1 -1
- package/src/webtriggers/index.ts +1 -0
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +69 -0
package/README.md
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
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)
|
|
14
|
+
|
|
15
|
+
[](https://sonarcloud.io/summary/new_code?id=vzakharchenko_forge-sql-orm)
|
|
16
|
+
[](https://sonarcloud.io/summary/new_code?id=vzakharchenko_forge-sql-orm)
|
|
17
|
+
[](https://sonarcloud.io/summary/new_code?id=vzakharchenko_forge-sql-orm)
|
|
13
18
|
|
|
14
19
|
**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
20
|
|
|
@@ -22,7 +27,7 @@
|
|
|
22
27
|
- ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
|
|
23
28
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
24
29
|
- ✅ **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
|
|
30
|
+
- ✅ **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
31
|
- ✅ **Raw SQL Execution**: `execute()`, `executeCacheable()`, `executeDDL()`, and `executeDDLActions()` methods for direct SQL queries with local and global caching
|
|
27
32
|
- ✅ **Common Table Expressions (CTEs)**: `with()` method for complex queries with subqueries
|
|
28
33
|
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
@@ -347,6 +352,11 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
347
352
|
console.debug(`[Performance Debug fetch] High DB time: ${totalDbExecutionTime} ms`);
|
|
348
353
|
}
|
|
349
354
|
},
|
|
355
|
+
{
|
|
356
|
+
// Optional: Configure query plan printing behavior
|
|
357
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
358
|
+
topQueries: 3, // Print top 3 slowest queries
|
|
359
|
+
},
|
|
350
360
|
);
|
|
351
361
|
} catch (e) {
|
|
352
362
|
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
@@ -356,6 +366,19 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
356
366
|
});
|
|
357
367
|
```
|
|
358
368
|
|
|
369
|
+
**Query Plan Printing Options:**
|
|
370
|
+
|
|
371
|
+
The `printQueriesWithPlan` function supports two modes:
|
|
372
|
+
|
|
373
|
+
1. **TopSlowest Mode (default)**: Prints execution plans for the slowest queries from the current resolver invocation
|
|
374
|
+
- `mode`: Set to `'TopSlowest'` (default)
|
|
375
|
+
- `topQueries`: Number of top slowest queries to analyze (default: 1)
|
|
376
|
+
|
|
377
|
+
2. **SummaryTable Mode**: Uses `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
378
|
+
- `mode`: Set to `'SummaryTable'`
|
|
379
|
+
- `summaryTableWindowTime`: Time window in milliseconds (default: 15000ms)
|
|
380
|
+
- Only works if queries are executed within the specified time window
|
|
381
|
+
|
|
359
382
|
### 5. Rovo Integration (Secure Analytics)
|
|
360
383
|
|
|
361
384
|
```typescript
|
|
@@ -452,6 +475,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
452
475
|
|
|
453
476
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
454
477
|
},
|
|
478
|
+
{
|
|
479
|
+
// Optional: Configure query plan printing
|
|
480
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
481
|
+
topQueries: 2, // Print top 2 slowest queries
|
|
482
|
+
},
|
|
455
483
|
);
|
|
456
484
|
|
|
457
485
|
// DDL operations for schema modifications
|
|
@@ -589,7 +617,12 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
589
617
|
}
|
|
590
618
|
|
|
591
619
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
592
|
-
}
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
// Optional: Configure query plan printing
|
|
623
|
+
mode: 'TopSlowest', // Print top slowest queries (default)
|
|
624
|
+
topQueries: 1, // Print top slowest query
|
|
625
|
+
},
|
|
593
626
|
);
|
|
594
627
|
```
|
|
595
628
|
|
|
@@ -993,23 +1026,23 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
|
|
|
993
1026
|
|
|
994
1027
|
### When to Use Each Approach
|
|
995
1028
|
|
|
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)
|
|
1029
|
+
| Method | Use Case | Versioning | Cache Management |
|
|
1030
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------- |
|
|
1031
|
+
| `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations | ❌ No | Cache Context |
|
|
1032
|
+
| `insertAndEvictCache()` | Simple inserts without conflicts | ❌ No | ✅ Yes |
|
|
1033
|
+
| `updateAndEvictCache()` | Simple updates without conflicts | ❌ No | ✅ Yes |
|
|
1034
|
+
| `deleteAndEvictCache()` | Simple deletes without conflicts | ❌ No | ✅ Yes |
|
|
1035
|
+
| `insert/update/delete` | Basic Drizzle operations | ❌ No | ❌ No |
|
|
1036
|
+
| `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
|
|
1037
|
+
| `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
|
|
1038
|
+
| `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
1039
|
+
| `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
1040
|
+
| `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
|
|
1041
|
+
| `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
|
|
1042
|
+
| `executeWithMetadata()` | Resolver-level profiling with execution metrics and configurable query plan printing (TopSlowest or SummaryTable mode) | ❌ No | Local Cache |
|
|
1043
|
+
| `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.) | ❌ No | No Caching |
|
|
1044
|
+
| `executeDDLActions()` | Execute regular SQL queries in DDL operation context | ❌ No | No Caching |
|
|
1045
|
+
| `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
|
|
1013
1046
|
|
|
1014
1047
|
where Cache context - allows you to batch cache invalidation events and bypass cache reads for affected tables.
|
|
1015
1048
|
|
|
@@ -1413,7 +1446,12 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
1413
1446
|
}
|
|
1414
1447
|
|
|
1415
1448
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1416
|
-
}
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
// Optional: Configure query plan printing
|
|
1452
|
+
mode: 'TopSlowest', // Print top slowest queries (default)
|
|
1453
|
+
topQueries: 1, // Print top slowest query
|
|
1454
|
+
},
|
|
1417
1455
|
);
|
|
1418
1456
|
|
|
1419
1457
|
// Using executeDDL() for DDL operations (CREATE, ALTER, DROP, etc.)
|
|
@@ -1781,6 +1819,11 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
1781
1819
|
|
|
1782
1820
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1783
1821
|
},
|
|
1822
|
+
{
|
|
1823
|
+
// Optional: Configure query plan printing
|
|
1824
|
+
topQueries: 1, // Print top slowest query (default)
|
|
1825
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
1826
|
+
},
|
|
1784
1827
|
);
|
|
1785
1828
|
|
|
1786
1829
|
// Insert operation - evicts local cache for users table
|
|
@@ -1982,6 +2025,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
1982
2025
|
|
|
1983
2026
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1984
2027
|
},
|
|
2028
|
+
{
|
|
2029
|
+
// Optional: Configure query plan printing
|
|
2030
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2031
|
+
topQueries: 1, // Print top slowest query
|
|
2032
|
+
},
|
|
1985
2033
|
);
|
|
1986
2034
|
```
|
|
1987
2035
|
|
|
@@ -2622,6 +2670,227 @@ The error analysis mechanism:
|
|
|
2622
2670
|
|
|
2623
2671
|
> **💡 Tip**: The automatic error analysis only triggers for timeout and OOM errors. Other errors are logged normally without plan analysis.
|
|
2624
2672
|
|
|
2673
|
+
### Resolver-Level Performance Monitoring
|
|
2674
|
+
|
|
2675
|
+
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.
|
|
2676
|
+
|
|
2677
|
+
#### Basic Usage
|
|
2678
|
+
|
|
2679
|
+
```typescript
|
|
2680
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2681
|
+
async () => {
|
|
2682
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2683
|
+
const orders = await forgeSQL
|
|
2684
|
+
.selectFrom(ordersTable)
|
|
2685
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
2686
|
+
return { users, orders };
|
|
2687
|
+
},
|
|
2688
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2689
|
+
const threshold = 500; // ms baseline for this resolver
|
|
2690
|
+
|
|
2691
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2692
|
+
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
2693
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2694
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
2695
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2699
|
+
},
|
|
2700
|
+
);
|
|
2701
|
+
```
|
|
2702
|
+
|
|
2703
|
+
#### Query Plan Printing Options
|
|
2704
|
+
|
|
2705
|
+
The `printQueriesWithPlan` function supports two modes, configurable via the optional `options` parameter:
|
|
2706
|
+
|
|
2707
|
+
**1. TopSlowest Mode (default)**: Prints execution plans for the slowest queries from the current resolver invocation
|
|
2708
|
+
|
|
2709
|
+
```typescript
|
|
2710
|
+
// Full configuration example
|
|
2711
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2712
|
+
async () => {
|
|
2713
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2714
|
+
return users;
|
|
2715
|
+
},
|
|
2716
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2717
|
+
if (totalDbExecutionTime > 1000) {
|
|
2718
|
+
await printQueriesWithPlan(); // Will print top 3 slowest queries with execution plans
|
|
2719
|
+
}
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2723
|
+
topQueries: 3, // Number of top slowest queries to analyze (default: 1)
|
|
2724
|
+
showSlowestPlans: true, // Show execution plans (default: true)
|
|
2725
|
+
},
|
|
2726
|
+
);
|
|
2727
|
+
|
|
2728
|
+
// Minimal configuration - only specify what you need
|
|
2729
|
+
const result2 = await forgeSQL.executeWithMetadata(
|
|
2730
|
+
async () => {
|
|
2731
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2732
|
+
return users;
|
|
2733
|
+
},
|
|
2734
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2735
|
+
if (totalDbExecutionTime > 1000) {
|
|
2736
|
+
await printQueriesWithPlan(); // Will print top 3 slowest queries (all other options use defaults)
|
|
2737
|
+
}
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
topQueries: 3, // Only specify topQueries, mode and showSlowestPlans use defaults
|
|
2741
|
+
},
|
|
2742
|
+
);
|
|
2743
|
+
|
|
2744
|
+
// Disable execution plans - only show SQL and execution time
|
|
2745
|
+
const result3 = await forgeSQL.executeWithMetadata(
|
|
2746
|
+
async () => {
|
|
2747
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2748
|
+
return users;
|
|
2749
|
+
},
|
|
2750
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2751
|
+
if (totalDbExecutionTime > 1000) {
|
|
2752
|
+
await printQueriesWithPlan(); // Will print SQL and time only, no execution plans
|
|
2753
|
+
}
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
showSlowestPlans: false, // Disable execution plan printing
|
|
2757
|
+
},
|
|
2758
|
+
);
|
|
2759
|
+
|
|
2760
|
+
// Use all defaults - pass empty object or omit options parameter
|
|
2761
|
+
const result4 = await forgeSQL.executeWithMetadata(
|
|
2762
|
+
async () => {
|
|
2763
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2764
|
+
return users;
|
|
2765
|
+
},
|
|
2766
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2767
|
+
if (totalDbExecutionTime > 1000) {
|
|
2768
|
+
await printQueriesWithPlan(); // Uses all defaults: TopSlowest mode, topQueries: 1, showSlowestPlans: true
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
{}, // Empty object - all options use defaults
|
|
2772
|
+
);
|
|
2773
|
+
```
|
|
2774
|
+
|
|
2775
|
+
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
|
2776
|
+
read_file
|
|
2777
|
+
|
|
2778
|
+
**2. SummaryTable Mode**: Uses `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
2779
|
+
|
|
2780
|
+
```typescript
|
|
2781
|
+
const result = await forgeSQL.executeWithMetadata(
|
|
2782
|
+
async () => {
|
|
2783
|
+
const users = await forgeSQL.selectFrom(usersTable);
|
|
2784
|
+
return users;
|
|
2785
|
+
},
|
|
2786
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2787
|
+
if (totalDbExecutionTime > 1000) {
|
|
2788
|
+
await printQueriesWithPlan(); // Will use CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
{
|
|
2792
|
+
mode: "SummaryTable", // Use SummaryTable mode
|
|
2793
|
+
summaryTableWindowTime: 10000, // Time window in milliseconds (default: 15000ms)
|
|
2794
|
+
},
|
|
2795
|
+
);
|
|
2796
|
+
```
|
|
2797
|
+
|
|
2798
|
+
#### Configuration Options
|
|
2799
|
+
|
|
2800
|
+
All options are **optional**. If not specified, default values are used. You can pass only the options you need to customize.
|
|
2801
|
+
|
|
2802
|
+
| Option | Type | Default | Description |
|
|
2803
|
+
| ------------------------ | -------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2804
|
+
| `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 |
|
|
2805
|
+
| `summaryTableWindowTime` | `number` | `15000` | Time window in milliseconds for summary table queries. Only used when `mode` is `'SummaryTable'` |
|
|
2806
|
+
| `topQueries` | `number` | `1` | Number of top slowest queries to analyze when `mode` is `'TopSlowest'` |
|
|
2807
|
+
| `showSlowestPlans` | `boolean` | `true` | Whether to show execution plans for slowest queries in TopSlowest mode. If `false`, only SQL and execution time are printed |
|
|
2808
|
+
| `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 |
|
|
2809
|
+
|
|
2810
|
+
**Examples:**
|
|
2811
|
+
|
|
2812
|
+
```typescript
|
|
2813
|
+
// Use all defaults - omit options or pass empty object
|
|
2814
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn); // or { }
|
|
2815
|
+
|
|
2816
|
+
// Customize only what you need
|
|
2817
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { topQueries: 3 });
|
|
2818
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { mode: "SummaryTable" });
|
|
2819
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { showSlowestPlans: false });
|
|
2820
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, { normalizeQuery: false }); // Disable query normalization
|
|
2821
|
+
|
|
2822
|
+
// Combine multiple options
|
|
2823
|
+
await forgeSQL.executeWithMetadata(queryFn, onMetadataFn, {
|
|
2824
|
+
mode: "TopSlowest",
|
|
2825
|
+
topQueries: 5,
|
|
2826
|
+
showSlowestPlans: false,
|
|
2827
|
+
normalizeQuery: true, // Enable query normalization (default)
|
|
2828
|
+
});
|
|
2829
|
+
```
|
|
2830
|
+
|
|
2831
|
+
#### How It Works
|
|
2832
|
+
|
|
2833
|
+
1. **TopSlowest Mode** (default):
|
|
2834
|
+
- Collects all queries executed within the resolver
|
|
2835
|
+
- Sorts them by execution time (slowest first)
|
|
2836
|
+
- Prints execution plans for the top N queries (configurable via `topQueries`)
|
|
2837
|
+
- If `showSlowestPlans` is `false`, only prints SQL and execution time without plans
|
|
2838
|
+
- Works immediately after query execution
|
|
2839
|
+
|
|
2840
|
+
2. **SummaryTable Mode**:
|
|
2841
|
+
- Attempts to use `CLUSTER_STATEMENTS_SUMMARY` for query analysis
|
|
2842
|
+
- Only works if queries are executed within the specified time window (`summaryTableWindowTime`)
|
|
2843
|
+
- If the time window expires, falls back to TopSlowest mode
|
|
2844
|
+
- Provides aggregated statistics from TiDB's system tables
|
|
2845
|
+
|
|
2846
|
+
#### Example: Real-World Resolver
|
|
2847
|
+
|
|
2848
|
+
```typescript
|
|
2849
|
+
resolver.define("fetch", async (req: Request) => {
|
|
2850
|
+
try {
|
|
2851
|
+
return await forgeSQL.executeWithMetadata(
|
|
2852
|
+
async () => {
|
|
2853
|
+
const users = await forgeSQL.selectFrom(demoUsers);
|
|
2854
|
+
const orders = await forgeSQL
|
|
2855
|
+
.selectFrom(demoOrders)
|
|
2856
|
+
.where(eq(demoOrders.userId, demoUsers.id));
|
|
2857
|
+
return { users, orders };
|
|
2858
|
+
},
|
|
2859
|
+
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2860
|
+
const threshold = 500; // ms baseline for this resolver
|
|
2861
|
+
|
|
2862
|
+
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2863
|
+
console.warn(
|
|
2864
|
+
`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`,
|
|
2865
|
+
);
|
|
2866
|
+
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2867
|
+
} else if (totalDbExecutionTime > threshold) {
|
|
2868
|
+
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2869
|
+
}
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
2873
|
+
topQueries: 2, // Print top 2 slowest queries
|
|
2874
|
+
},
|
|
2875
|
+
);
|
|
2876
|
+
} catch (e) {
|
|
2877
|
+
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
2878
|
+
console.error(error, e);
|
|
2879
|
+
throw error;
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2882
|
+
```
|
|
2883
|
+
|
|
2884
|
+
#### Benefits
|
|
2885
|
+
|
|
2886
|
+
- **Resolver-Level Profiling**: Aggregates metrics across all database operations in a resolver
|
|
2887
|
+
- **Configurable Analysis**: Choose between TopSlowest mode or SummaryTable mode
|
|
2888
|
+
- **Automatic Plan Formatting**: Execution plans are formatted in a readable format
|
|
2889
|
+
- **Performance Thresholds**: Set custom thresholds for performance warnings
|
|
2890
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
2891
|
+
|
|
2892
|
+
> **💡 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.
|
|
2893
|
+
|
|
2625
2894
|
### Slow Query Monitoring
|
|
2626
2895
|
|
|
2627
2896
|
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 +3181,11 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
2912
3181
|
|
|
2913
3182
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2914
3183
|
},
|
|
3184
|
+
{
|
|
3185
|
+
// Optional: Configure query plan printing
|
|
3186
|
+
mode: "TopSlowest", // Print top slowest queries (default)
|
|
3187
|
+
topQueries: 1, // Print top slowest query
|
|
3188
|
+
},
|
|
2915
3189
|
);
|
|
2916
3190
|
|
|
2917
3191
|
// ✅ 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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"}
|