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
|
@@ -4,6 +4,7 @@ export * from "./fetchSchemaWebTrigger";
|
|
|
4
4
|
export * from "./dropTablesMigrationWebTrigger";
|
|
5
5
|
export * from "./clearCacheSchedulerTrigger";
|
|
6
6
|
export * from "./slowQuerySchedulerTrigger";
|
|
7
|
+
export * from "./topSlowestStatementLastHourTrigger";
|
|
7
8
|
export interface TriggerResponse<BODY> {
|
|
8
9
|
body?: BODY;
|
|
9
10
|
headers?: Record<string, string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sCAAsC,CAAC;AAErD,MAAM,WAAW,eAAe,CAAC,IAAI;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,IAAI,EAAE,YAAY,MAAM,EAAE,MAAM,IAAI,KAAG,eAAe,CAAC,IAAI,CAc1F,CAAC"}
|
|
@@ -21,6 +21,7 @@ __exportStar(require("./fetchSchemaWebTrigger"), exports);
|
|
|
21
21
|
__exportStar(require("./dropTablesMigrationWebTrigger"), exports);
|
|
22
22
|
__exportStar(require("./clearCacheSchedulerTrigger"), exports);
|
|
23
23
|
__exportStar(require("./slowQuerySchedulerTrigger"), exports);
|
|
24
|
+
__exportStar(require("./topSlowestStatementLastHourTrigger"), exports);
|
|
24
25
|
const getHttpResponse = (statusCode, body) => {
|
|
25
26
|
let statusText = "";
|
|
26
27
|
if (statusCode === 200) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4DAA0C;AAC1C,8DAA4C;AAC5C,0DAAwC;AACxC,kEAAgD;AAChD,+DAA6C;AAC7C,8DAA4C;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4DAA0C;AAC1C,8DAA4C;AAC5C,0DAAwC;AACxC,kEAAgD;AAChD,+DAA6C;AAC7C,8DAA4C;AAC5C,uEAAqD;AAS9C,MAAM,eAAe,GAAG,CAAO,UAAkB,EAAE,IAAU,EAAyB,EAAE;IAC7F,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;QACvB,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,aAAa,CAAC;IAC7B,CAAC;IAED,OAAO;QACL,OAAO,EAAE,EAAE,cAAc,EAAE,CAAC,kBAAkB,CAAC,EAAE;QACjD,UAAU;QACV,UAAU;QACV,IAAI;KACL,CAAC;AACJ,CAAC,CAAC;AAdW,QAAA,eAAe,mBAc1B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
2
|
+
import { TriggerResponse } from "./";
|
|
3
|
+
import { OperationType } from "../utils/requestTypeContextUtils";
|
|
4
|
+
export interface TriggerOptions {
|
|
5
|
+
warnThresholdMs?: number;
|
|
6
|
+
memoryThresholdBytes?: number;
|
|
7
|
+
showPlan?: boolean;
|
|
8
|
+
operationType?: OperationType;
|
|
9
|
+
topN?: number;
|
|
10
|
+
hours?: number;
|
|
11
|
+
tables?: "SUMMARY_AND_HISTORY" | "CLUSTER_SUMMARY_AND_HISTORY";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated This function is deprecated and will be removed in a future version.
|
|
15
|
+
*
|
|
16
|
+
* This function was previously a complex implementation that directly queried
|
|
17
|
+
* CLUSTER_STATEMENTS_SUMMARY tables to analyze query performance. However, this approach
|
|
18
|
+
* had reliability issues with long-running functions where metadata could be evicted
|
|
19
|
+
* before the function completes.
|
|
20
|
+
*
|
|
21
|
+
* The recommended replacement is to use the new observability system with `executeWithMetadata`:
|
|
22
|
+
* - **TopSlowest mode** (default): Deterministic logging of SQL digests executed in resolvers
|
|
23
|
+
* - **SummaryTable mode** (optional): Uses CLUSTER_STATEMENTS_SUMMARY with a short memory window
|
|
24
|
+
* - Automatic fallback mechanisms for long-running functions
|
|
25
|
+
* - More reliable post-mortem diagnostics for Timeout and OOM errors
|
|
26
|
+
*
|
|
27
|
+
* Note: `slowQuerySchedulerTrigger` is a different function that analyzes TiDB's slow query log
|
|
28
|
+
* and is not a direct replacement for this function.
|
|
29
|
+
*
|
|
30
|
+
* For more details on the improvements and migration path, see:
|
|
31
|
+
* https://community.developer.atlassian.com/t/practical-sql-observability-for-forge-apps-with-forge-sql-orm/123456
|
|
32
|
+
*
|
|
33
|
+
* @param orm - ForgeSQL ORM instance
|
|
34
|
+
* @param options - Configuration options (currently passed to slowQuerySchedulerTrigger as a temporary wrapper)
|
|
35
|
+
* @returns Promise<TriggerResponse<string>> - HTTP response with query results or error
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Old usage (deprecated):
|
|
40
|
+
* await topSlowestStatementLastHourTrigger(forgeSQL, { hours: 1 });
|
|
41
|
+
*
|
|
42
|
+
* // New usage (recommended - use executeWithMetadata in your resolvers):
|
|
43
|
+
* await forgeSQL.executeWithMetadata(
|
|
44
|
+
* async () => {
|
|
45
|
+
* // your resolver logic
|
|
46
|
+
* },
|
|
47
|
+
* async (totalDbTime, totalResponseSize, printPlan) => {
|
|
48
|
+
* // custom observability logic
|
|
49
|
+
* if (totalDbTime > 1000) await printPlan();
|
|
50
|
+
* },
|
|
51
|
+
* {
|
|
52
|
+
* mode: "TopSlowest",
|
|
53
|
+
* topQueries: 1,
|
|
54
|
+
* showSlowestPlans: true
|
|
55
|
+
* }
|
|
56
|
+
* );
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const topSlowestStatementLastHourTrigger: (orm: ForgeSqlOperation, options?: TriggerOptions) => Promise<TriggerResponse<string>>;
|
|
60
|
+
//# sourceMappingURL=topSlowestStatementLastHourTrigger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topSlowestStatementLastHourTrigger.d.ts","sourceRoot":"","sources":["../../src/webtriggers/topSlowestStatementLastHourTrigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAA6B,eAAe,EAAE,MAAM,IAAI,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,qBAAqB,GAAG,6BAA6B,CAAC;CAChE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,kCAAkC,GAC7C,KAAK,iBAAiB,EACtB,UAAU,cAAc,KACvB,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAKjC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.topSlowestStatementLastHourTrigger = void 0;
|
|
4
|
+
const _1 = require("./");
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated This function is deprecated and will be removed in a future version.
|
|
7
|
+
*
|
|
8
|
+
* This function was previously a complex implementation that directly queried
|
|
9
|
+
* CLUSTER_STATEMENTS_SUMMARY tables to analyze query performance. However, this approach
|
|
10
|
+
* had reliability issues with long-running functions where metadata could be evicted
|
|
11
|
+
* before the function completes.
|
|
12
|
+
*
|
|
13
|
+
* The recommended replacement is to use the new observability system with `executeWithMetadata`:
|
|
14
|
+
* - **TopSlowest mode** (default): Deterministic logging of SQL digests executed in resolvers
|
|
15
|
+
* - **SummaryTable mode** (optional): Uses CLUSTER_STATEMENTS_SUMMARY with a short memory window
|
|
16
|
+
* - Automatic fallback mechanisms for long-running functions
|
|
17
|
+
* - More reliable post-mortem diagnostics for Timeout and OOM errors
|
|
18
|
+
*
|
|
19
|
+
* Note: `slowQuerySchedulerTrigger` is a different function that analyzes TiDB's slow query log
|
|
20
|
+
* and is not a direct replacement for this function.
|
|
21
|
+
*
|
|
22
|
+
* For more details on the improvements and migration path, see:
|
|
23
|
+
* https://community.developer.atlassian.com/t/practical-sql-observability-for-forge-apps-with-forge-sql-orm/123456
|
|
24
|
+
*
|
|
25
|
+
* @param orm - ForgeSQL ORM instance
|
|
26
|
+
* @param options - Configuration options (currently passed to slowQuerySchedulerTrigger as a temporary wrapper)
|
|
27
|
+
* @returns Promise<TriggerResponse<string>> - HTTP response with query results or error
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Old usage (deprecated):
|
|
32
|
+
* await topSlowestStatementLastHourTrigger(forgeSQL, { hours: 1 });
|
|
33
|
+
*
|
|
34
|
+
* // New usage (recommended - use executeWithMetadata in your resolvers):
|
|
35
|
+
* await forgeSQL.executeWithMetadata(
|
|
36
|
+
* async () => {
|
|
37
|
+
* // your resolver logic
|
|
38
|
+
* },
|
|
39
|
+
* async (totalDbTime, totalResponseSize, printPlan) => {
|
|
40
|
+
* // custom observability logic
|
|
41
|
+
* if (totalDbTime > 1000) await printPlan();
|
|
42
|
+
* },
|
|
43
|
+
* {
|
|
44
|
+
* mode: "TopSlowest",
|
|
45
|
+
* topQueries: 1,
|
|
46
|
+
* showSlowestPlans: true
|
|
47
|
+
* }
|
|
48
|
+
* );
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
const topSlowestStatementLastHourTrigger = async (orm, options) => {
|
|
52
|
+
return (0, _1.slowQuerySchedulerTrigger)(orm, options ? { timeout: 3000, hours: options.hours ?? 1 } : { timeout: 3000, hours: 1 });
|
|
53
|
+
};
|
|
54
|
+
exports.topSlowestStatementLastHourTrigger = topSlowestStatementLastHourTrigger;
|
|
55
|
+
//# sourceMappingURL=topSlowestStatementLastHourTrigger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topSlowestStatementLastHourTrigger.js","sourceRoot":"","sources":["../../src/webtriggers/topSlowestStatementLastHourTrigger.ts"],"names":[],"mappings":";;;AACA,yBAAgE;AAahE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACI,MAAM,kCAAkC,GAAG,KAAK,EACrD,GAAsB,EACtB,OAAwB,EACU,EAAE;IACpC,OAAO,IAAA,4BAAyB,EAC9B,GAAG,EACH,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CACrF,CAAC;AACJ,CAAC,CAAC;AARW,QAAA,kCAAkC,sCAQ7C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-sql-orm",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.15",
|
|
4
4
|
"description": "Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"homepage": "https://github.com/vzakharchenko/forge-sql-orm#readme",
|
|
@@ -21,28 +21,29 @@
|
|
|
21
21
|
"drizzle-driver",
|
|
22
22
|
"drizzle-custom-driver",
|
|
23
23
|
"orm",
|
|
24
|
+
"rovo",
|
|
24
25
|
"database"
|
|
25
26
|
],
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@eslint/js": "^9.39.1",
|
|
28
29
|
"@types/luxon": "^3.7.1",
|
|
29
30
|
"@types/node": "^24.10.1",
|
|
30
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
31
|
-
"@typescript-eslint/parser": "^8.
|
|
32
|
-
"@vitest/coverage-v8": "^4.0.
|
|
33
|
-
"@vitest/ui": "^4.0.
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
|
32
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
33
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
34
|
+
"@vitest/ui": "^4.0.14",
|
|
34
35
|
"eslint": "^9.39.1",
|
|
35
36
|
"eslint-config-prettier": "^10.1.8",
|
|
36
37
|
"eslint-plugin-import": "^2.32.0",
|
|
37
38
|
"eslint-plugin-vitest": "^0.5.4",
|
|
38
39
|
"husky": "^9.1.7",
|
|
39
|
-
"knip": "^5.70.
|
|
40
|
+
"knip": "^5.70.2",
|
|
40
41
|
"patch-package": "^8.0.1",
|
|
41
|
-
"prettier": "^3.
|
|
42
|
+
"prettier": "^3.7.2",
|
|
42
43
|
"ts-node": "^10.9.2",
|
|
43
44
|
"typescript": "^5.9.3",
|
|
44
45
|
"uuid": "^13.0.0",
|
|
45
|
-
"vitest": "^4.0.
|
|
46
|
+
"vitest": "^4.0.14"
|
|
46
47
|
},
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"author": "Vasyl Zakharchenko",
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
"README.md"
|
|
71
72
|
],
|
|
72
73
|
"peerDependencies": {
|
|
73
|
-
"@forge/sql": "^3.0.
|
|
74
|
+
"@forge/sql": "^3.0.12",
|
|
74
75
|
"drizzle-orm": "^0.44.7"
|
|
75
76
|
},
|
|
76
77
|
"optionalDependencies": {
|
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -39,7 +39,11 @@ import { cacheApplicationContext, localCacheApplicationContext } from "../utils/
|
|
|
39
39
|
import { clearTablesCache } from "../utils/cacheUtils";
|
|
40
40
|
import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
41
41
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
getLastestMetadata,
|
|
44
|
+
metadataQueryContext,
|
|
45
|
+
MetadataQueryOptions,
|
|
46
|
+
} from "../utils/metadataContextUtils";
|
|
43
47
|
import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
|
|
44
48
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
45
49
|
import { Rovo } from "./Rovo";
|
|
@@ -124,7 +128,15 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
124
128
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
125
129
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
126
130
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
127
|
-
* @param onMetadata.
|
|
131
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
132
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
133
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
134
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
135
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
136
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
137
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
138
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
139
|
+
* @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
|
|
128
140
|
* @returns Promise with the query result
|
|
129
141
|
*
|
|
130
142
|
* @example
|
|
@@ -136,12 +148,12 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
136
148
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
137
149
|
* return { users, orders };
|
|
138
150
|
* },
|
|
139
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
151
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
140
152
|
* const threshold = 500; // ms baseline for this resolver
|
|
141
153
|
*
|
|
142
154
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
143
155
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
144
|
-
* await
|
|
156
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
145
157
|
* } else if (totalDbExecutionTime > threshold) {
|
|
146
158
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
147
159
|
* }
|
|
@@ -164,12 +176,12 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
164
176
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
165
177
|
* return { users, orders };
|
|
166
178
|
* },
|
|
167
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
179
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
168
180
|
* const threshold = 500; // ms baseline for this resolver
|
|
169
181
|
*
|
|
170
182
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
171
183
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
172
|
-
* await
|
|
184
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
173
185
|
* } else if (totalDbExecutionTime > threshold) {
|
|
174
186
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
175
187
|
* }
|
|
@@ -185,7 +197,47 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
185
197
|
* });
|
|
186
198
|
* ```
|
|
187
199
|
*
|
|
188
|
-
* @
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* // Using TopSlowest mode with custom topQueries
|
|
203
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
204
|
+
* async () => {
|
|
205
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
206
|
+
* return users;
|
|
207
|
+
* },
|
|
208
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
209
|
+
* if (totalDbExecutionTime > 1000) {
|
|
210
|
+
* await printQueriesWithPlan(); // Will print top 3 slowest queries
|
|
211
|
+
* }
|
|
212
|
+
* },
|
|
213
|
+
* {
|
|
214
|
+
* mode: 'TopSlowest', // Print top slowest queries (default)
|
|
215
|
+
* topQueries: 3, // Print top 3 slowest queries
|
|
216
|
+
* }
|
|
217
|
+
* );
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Using SummaryTable mode for query analysis
|
|
223
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
224
|
+
* async () => {
|
|
225
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
226
|
+
* return users;
|
|
227
|
+
* },
|
|
228
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
229
|
+
* if (totalDbExecutionTime > 1000) {
|
|
230
|
+
* await printQueriesWithPlan(); // Will use CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
231
|
+
* }
|
|
232
|
+
* },
|
|
233
|
+
* {
|
|
234
|
+
* mode: 'SummaryTable', // Use summary tables mode
|
|
235
|
+
* summaryTableWindowTime: 10000, // 10 second window
|
|
236
|
+
* }
|
|
237
|
+
* );
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @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.
|
|
189
241
|
*/
|
|
190
242
|
async executeWithMetadata<T>(
|
|
191
243
|
query: () => Promise<T>,
|
|
@@ -194,6 +246,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
194
246
|
totalResponseSize: number,
|
|
195
247
|
printQueriesWithPlan: () => Promise<void>,
|
|
196
248
|
) => Promise<void> | void,
|
|
249
|
+
options?: MetadataQueryOptions,
|
|
197
250
|
): Promise<T> {
|
|
198
251
|
return metadataQueryContext.run(
|
|
199
252
|
{
|
|
@@ -204,6 +257,8 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
204
257
|
printQueriesWithPlan: async () => {
|
|
205
258
|
return;
|
|
206
259
|
},
|
|
260
|
+
options: options,
|
|
261
|
+
statistics: [],
|
|
207
262
|
},
|
|
208
263
|
async () => {
|
|
209
264
|
const result = await query();
|
|
@@ -886,7 +941,15 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
886
941
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
887
942
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
888
943
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
889
|
-
* @param onMetadata.
|
|
944
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
945
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
946
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
947
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
948
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
949
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
950
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
951
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
952
|
+
* @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
|
|
890
953
|
* @returns Promise with the query result
|
|
891
954
|
*
|
|
892
955
|
* @example
|
|
@@ -898,12 +961,12 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
898
961
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
899
962
|
* return { users, orders };
|
|
900
963
|
* },
|
|
901
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
964
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
902
965
|
* const threshold = 500; // ms baseline for this resolver
|
|
903
966
|
*
|
|
904
967
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
905
968
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
906
|
-
* await
|
|
969
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
907
970
|
* } else if (totalDbExecutionTime > threshold) {
|
|
908
971
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
909
972
|
* }
|
|
@@ -926,12 +989,12 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
926
989
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
927
990
|
* return { users, orders };
|
|
928
991
|
* },
|
|
929
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
992
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
930
993
|
* const threshold = 500; // ms baseline for this resolver
|
|
931
994
|
*
|
|
932
995
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
933
996
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
934
|
-
* await
|
|
997
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
935
998
|
* } else if (totalDbExecutionTime > threshold) {
|
|
936
999
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
937
1000
|
* }
|
|
@@ -947,7 +1010,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
947
1010
|
* });
|
|
948
1011
|
* ```
|
|
949
1012
|
*
|
|
950
|
-
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `
|
|
1013
|
+
* @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.
|
|
951
1014
|
*/
|
|
952
1015
|
async executeWithMetadata<T>(
|
|
953
1016
|
query: () => Promise<T>,
|
|
@@ -956,8 +1019,9 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
956
1019
|
totalResponseSize: number,
|
|
957
1020
|
printQueriesWithPlan: () => Promise<void>,
|
|
958
1021
|
) => Promise<void> | void,
|
|
1022
|
+
options?: MetadataQueryOptions,
|
|
959
1023
|
): Promise<T> {
|
|
960
|
-
return this.ormInstance.executeWithMetadata(query, onMetadata);
|
|
1024
|
+
return this.ormInstance.executeWithMetadata(query, onMetadata, options);
|
|
961
1025
|
}
|
|
962
1026
|
|
|
963
1027
|
selectCacheable<TSelection extends SelectedFields>(
|
|
@@ -57,6 +57,7 @@ import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
|
57
57
|
import type { WithBuilder } from "drizzle-orm/mysql-core/subquery";
|
|
58
58
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
59
59
|
import { MySqlColumn } from "drizzle-orm/mysql-core";
|
|
60
|
+
import { MetadataQueryOptions } from "../utils/metadataContextUtils";
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* Core interface for ForgeSQL operations.
|
|
@@ -585,7 +586,15 @@ export interface QueryBuilderForgeSql {
|
|
|
585
586
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
586
587
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
587
588
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
588
|
-
* @param onMetadata.
|
|
589
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
590
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
591
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
592
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
593
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
594
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
595
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
596
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
597
|
+
* @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
|
|
589
598
|
* @returns Promise with the query result
|
|
590
599
|
*
|
|
591
600
|
* @example
|
|
@@ -597,12 +606,12 @@ export interface QueryBuilderForgeSql {
|
|
|
597
606
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
598
607
|
* return { users, orders };
|
|
599
608
|
* },
|
|
600
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
609
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
601
610
|
* const threshold = 500; // ms baseline for this resolver
|
|
602
611
|
*
|
|
603
612
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
604
613
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
605
|
-
* await
|
|
614
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
606
615
|
* } else if (totalDbExecutionTime > threshold) {
|
|
607
616
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
608
617
|
* }
|
|
@@ -655,6 +664,7 @@ export interface QueryBuilderForgeSql {
|
|
|
655
664
|
totalResponseSize: number,
|
|
656
665
|
printQueriesWithPlan: () => Promise<void>,
|
|
657
666
|
) => Promise<void> | void,
|
|
667
|
+
options?: MetadataQueryOptions,
|
|
658
668
|
): Promise<T>;
|
|
659
669
|
/**
|
|
660
670
|
* Executes a raw SQL query with local cache support.
|
package/src/utils/forgeDriver.ts
CHANGED
|
@@ -77,14 +77,22 @@ function inlineParams(sql: string, params: unknown[]): string {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Processes DDL query results and saves metadata.
|
|
80
|
+
* Processes DDL query results and saves metadata to the execution context.
|
|
81
81
|
*
|
|
82
|
+
* @param query - The SQL query string
|
|
83
|
+
* @param params - Query parameters
|
|
84
|
+
* @param method - Execution method ("all" or "execute")
|
|
82
85
|
* @param result - The DDL query result
|
|
83
86
|
* @returns Processed result for Drizzle ORM
|
|
84
87
|
*/
|
|
85
|
-
async function processDDLResult(
|
|
88
|
+
async function processDDLResult(
|
|
89
|
+
query: string,
|
|
90
|
+
params: unknown[],
|
|
91
|
+
method: QueryMethod,
|
|
92
|
+
result: any,
|
|
93
|
+
): Promise<ForgeDriverResult> {
|
|
86
94
|
if (result.metadata) {
|
|
87
|
-
await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
|
|
95
|
+
await saveMetaDataToContext(query, params, result.metadata as ForgeSQLMetadata);
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
if (!result?.rows) {
|
|
@@ -109,13 +117,16 @@ async function processDDLResult(method: QueryMethod, result: any): Promise<Forge
|
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
/**
|
|
112
|
-
* Processes execute method results (UPDATE, INSERT, DELETE).
|
|
120
|
+
* Processes execute method results (UPDATE, INSERT, DELETE) and saves metadata to the execution context.
|
|
113
121
|
*
|
|
114
|
-
* @param query - The SQL query
|
|
115
|
-
* @param params - Query parameters
|
|
122
|
+
* @param query - The SQL query string
|
|
123
|
+
* @param params - Query parameters (may be undefined)
|
|
116
124
|
* @returns Processed result for Drizzle ORM
|
|
117
125
|
*/
|
|
118
|
-
async function processExecuteMethod(
|
|
126
|
+
async function processExecuteMethod(
|
|
127
|
+
query: string,
|
|
128
|
+
params: unknown[] | undefined,
|
|
129
|
+
): Promise<ForgeDriverResult> {
|
|
119
130
|
const sqlStatement = sql.prepare<UpdateQueryResponse>(query);
|
|
120
131
|
|
|
121
132
|
if (params) {
|
|
@@ -123,7 +134,7 @@ async function processExecuteMethod(query: string, params: unknown[]): Promise<F
|
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
|
|
126
|
-
await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
|
|
137
|
+
await saveMetaDataToContext(query, params ?? [], result.metadata as ForgeSQLMetadata);
|
|
127
138
|
if (!result.rows) {
|
|
128
139
|
return { rows: [[]] };
|
|
129
140
|
}
|
|
@@ -132,13 +143,16 @@ async function processExecuteMethod(query: string, params: unknown[]): Promise<F
|
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
/**
|
|
135
|
-
* Processes all method results (SELECT queries).
|
|
146
|
+
* Processes all method results (SELECT queries) and saves metadata to the execution context.
|
|
136
147
|
*
|
|
137
|
-
* @param query - The SQL query
|
|
138
|
-
* @param params - Query parameters
|
|
148
|
+
* @param query - The SQL query string
|
|
149
|
+
* @param params - Query parameters (may be undefined)
|
|
139
150
|
* @returns Processed result for Drizzle ORM
|
|
140
151
|
*/
|
|
141
|
-
async function processAllMethod(
|
|
152
|
+
async function processAllMethod(
|
|
153
|
+
query: string,
|
|
154
|
+
params: unknown[] | undefined,
|
|
155
|
+
): Promise<ForgeDriverResult> {
|
|
142
156
|
const sqlStatement = await sql.prepare<unknown>(query);
|
|
143
157
|
|
|
144
158
|
if (params) {
|
|
@@ -146,7 +160,7 @@ async function processAllMethod(query: string, params: unknown[]): Promise<Forge
|
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
|
|
149
|
-
await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
|
|
163
|
+
await saveMetaDataToContext(query, params ?? [], result.metadata as ForgeSQLMetadata);
|
|
150
164
|
|
|
151
165
|
if (!result.rows) {
|
|
152
166
|
return { rows: [] };
|
|
@@ -160,9 +174,10 @@ async function processAllMethod(query: string, params: unknown[]): Promise<Forge
|
|
|
160
174
|
/**
|
|
161
175
|
* Main Forge SQL driver function for Drizzle ORM integration.
|
|
162
176
|
* Handles DDL operations, execute operations (UPDATE/INSERT/DELETE), and select operations.
|
|
177
|
+
* Automatically saves query execution metadata to the context for performance monitoring.
|
|
163
178
|
*
|
|
164
179
|
* @param query - The SQL query to execute
|
|
165
|
-
* @param params - Query parameters
|
|
180
|
+
* @param params - Query parameters (may be undefined or empty array)
|
|
166
181
|
* @param method - Execution method ("all" for SELECT, "execute" for UPDATE/INSERT/DELETE)
|
|
167
182
|
* @returns Promise with query results compatible with Drizzle ORM
|
|
168
183
|
*
|
|
@@ -182,25 +197,25 @@ async function processAllMethod(query: string, params: unknown[]): Promise<Forge
|
|
|
182
197
|
*/
|
|
183
198
|
export const forgeDriver = async (
|
|
184
199
|
query: string,
|
|
185
|
-
params: unknown[],
|
|
200
|
+
params: unknown[] | undefined,
|
|
186
201
|
method: QueryMethod,
|
|
187
202
|
): Promise<ForgeDriverResult> => {
|
|
188
203
|
const operationType = await getOperationType();
|
|
189
204
|
// Handle DDL operations
|
|
190
205
|
if (operationType === "DDL") {
|
|
191
206
|
const result = await withTimeout(
|
|
192
|
-
sql.executeDDL(inlineParams(query, params)),
|
|
207
|
+
sql.executeDDL(inlineParams(query, params ?? [])),
|
|
193
208
|
timeoutMessage,
|
|
194
209
|
timeoutMs,
|
|
195
210
|
);
|
|
196
|
-
return await processDDLResult(method, result);
|
|
211
|
+
return await processDDLResult(query, params ?? [], method, result);
|
|
197
212
|
}
|
|
198
213
|
|
|
199
214
|
// Handle execute method (UPDATE, INSERT, DELETE)
|
|
200
215
|
if (method === "execute") {
|
|
201
|
-
return await processExecuteMethod(query, params
|
|
216
|
+
return await processExecuteMethod(query, params);
|
|
202
217
|
}
|
|
203
218
|
|
|
204
219
|
// Handle all method (SELECT)
|
|
205
|
-
return await processAllMethod(query, params
|
|
220
|
+
return await processAllMethod(query, params);
|
|
206
221
|
};
|