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.
Files changed (31) hide show
  1. package/README.md +290 -20
  2. package/dist/core/ForgeSQLORM.d.ts +16 -7
  3. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  4. package/dist/core/ForgeSQLORM.js +73 -15
  5. package/dist/core/ForgeSQLORM.js.map +1 -1
  6. package/dist/core/ForgeSQLQueryBuilder.d.ts +13 -4
  7. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  9. package/dist/utils/forgeDriver.d.ts +3 -2
  10. package/dist/utils/forgeDriver.d.ts.map +1 -1
  11. package/dist/utils/forgeDriver.js +20 -16
  12. package/dist/utils/forgeDriver.js.map +1 -1
  13. package/dist/utils/metadataContextUtils.d.ts +27 -1
  14. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  15. package/dist/utils/metadataContextUtils.js +215 -10
  16. package/dist/utils/metadataContextUtils.js.map +1 -1
  17. package/dist/webtriggers/index.d.ts +1 -0
  18. package/dist/webtriggers/index.d.ts.map +1 -1
  19. package/dist/webtriggers/index.js +1 -0
  20. package/dist/webtriggers/index.js.map +1 -1
  21. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +60 -0
  22. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -0
  23. package/dist/webtriggers/topSlowestStatementLastHourTrigger.js +55 -0
  24. package/dist/webtriggers/topSlowestStatementLastHourTrigger.js.map +1 -0
  25. package/package.json +10 -9
  26. package/src/core/ForgeSQLORM.ts +78 -14
  27. package/src/core/ForgeSQLQueryBuilder.ts +13 -3
  28. package/src/utils/forgeDriver.ts +34 -19
  29. package/src/utils/metadataContextUtils.ts +267 -10
  30. package/src/webtriggers/index.ts +1 -0
  31. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +69 -0
package/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  [![forge-sql-orm CI](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml/badge.svg)](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml)
11
11
  [![Coverage Status](https://coveralls.io/repos/github/vzakharchenko/forge-sql-orm/badge.svg?branch=master)](https://coveralls.io/github/vzakharchenko/forge-sql-orm?branch=master)
12
12
  [![DeepScan grade](https://deepscan.io/api/teams/26652/projects/29272/branches/940614/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=26652&pid=29272&bid=940614)
13
+ [![Snyk Vulnerabilities](https://snyk.io/test/github/vzakharchenko/forge-sql-orm/badge.svg)](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 | Versioning | Cache Management |
997
- | ---------------------------------------------------------------------- | ----------------------------------------------------------- | ---------- | -------------------- |
998
- | `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations | ❌ No | Cache Context |
999
- | `insertAndEvictCache()` | Simple inserts without conflicts | ❌ No | ✅ Yes |
1000
- | `updateAndEvictCache()` | Simple updates without conflicts | ❌ No | ✅ Yes |
1001
- | `deleteAndEvictCache()` | Simple deletes without conflicts | ❌ No | ✅ Yes |
1002
- | `insert/update/delete` | Basic Drizzle operations | ❌ No | ❌ No |
1003
- | `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
1004
- | `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
1005
- | `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
1006
- | `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
1007
- | `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
1008
- | `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
1009
- | `executeWithMetadata()` | Raw SQL queries with execution metrics capture | ❌ No | Local Cache |
1010
- | `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.) | ❌ No | No Caching |
1011
- | `executeDDLActions()` | Execute regular SQL queries in DDL operation context | ❌ No | No Caching |
1012
- | `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
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.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
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, printQueries) => {
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 printQueries(); // Analyze and print query execution plans
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, printQueries) => {
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 printQueries(); // Optionally log or capture diagnostics for further analysis
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 `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
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":"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"}
1
+ {"version":3,"file":"ForgeSQLORM.d.ts","sourceRoot":"","sources":["../../src/core/ForgeSQLORM.ts"],"names":[],"mappings":"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"}
@@ -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.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
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, printQueries) => {
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 printQueries(); // Analyze and print query execution plans
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, printQueries) => {
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 printQueries(); // Optionally log or capture diagnostics for further analysis
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
- * @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
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.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
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, printQueries) => {
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 printQueries(); // Analyze and print query execution plans
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, printQueries) => {
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 printQueries(); // Optionally log or capture diagnostics for further analysis
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 `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
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);