forge-sql-orm 2.1.11 → 2.1.13

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 (73) hide show
  1. package/README.md +800 -541
  2. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
  3. package/dist/core/ForgeSQLAnalyseOperations.js +257 -0
  4. package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -0
  5. package/dist/core/ForgeSQLCacheOperations.js +172 -0
  6. package/dist/core/ForgeSQLCacheOperations.js.map +1 -0
  7. package/dist/core/ForgeSQLCrudOperations.js +349 -0
  8. package/dist/core/ForgeSQLCrudOperations.js.map +1 -0
  9. package/dist/core/ForgeSQLORM.d.ts +1 -1
  10. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLORM.js +1191 -0
  12. package/dist/core/ForgeSQLORM.js.map +1 -0
  13. package/dist/core/ForgeSQLQueryBuilder.js +77 -0
  14. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -0
  15. package/dist/core/ForgeSQLSelectOperations.js +81 -0
  16. package/dist/core/ForgeSQLSelectOperations.js.map +1 -0
  17. package/dist/core/SystemTables.js +258 -0
  18. package/dist/core/SystemTables.js.map +1 -0
  19. package/dist/index.js +30 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  22. package/dist/lib/drizzle/extensions/additionalActions.js +527 -0
  23. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -0
  24. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  25. package/dist/utils/cacheContextUtils.js +198 -0
  26. package/dist/utils/cacheContextUtils.js.map +1 -0
  27. package/dist/utils/cacheUtils.d.ts.map +1 -1
  28. package/dist/utils/cacheUtils.js +383 -0
  29. package/dist/utils/cacheUtils.js.map +1 -0
  30. package/dist/utils/forgeDriver.d.ts.map +1 -1
  31. package/dist/utils/forgeDriver.js +139 -0
  32. package/dist/utils/forgeDriver.js.map +1 -0
  33. package/dist/utils/forgeDriverProxy.js +68 -0
  34. package/dist/utils/forgeDriverProxy.js.map +1 -0
  35. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  36. package/dist/utils/metadataContextUtils.js +28 -0
  37. package/dist/utils/metadataContextUtils.js.map +1 -0
  38. package/dist/utils/requestTypeContextUtils.js +10 -0
  39. package/dist/utils/requestTypeContextUtils.js.map +1 -0
  40. package/dist/utils/sqlHints.js +52 -0
  41. package/dist/utils/sqlHints.js.map +1 -0
  42. package/dist/utils/sqlUtils.d.ts.map +1 -1
  43. package/dist/utils/sqlUtils.js +590 -0
  44. package/dist/utils/sqlUtils.js.map +1 -0
  45. package/dist/webtriggers/applyMigrationsWebTrigger.js +77 -0
  46. package/dist/webtriggers/applyMigrationsWebTrigger.js.map +1 -0
  47. package/dist/webtriggers/clearCacheSchedulerTrigger.js +83 -0
  48. package/dist/webtriggers/clearCacheSchedulerTrigger.js.map +1 -0
  49. package/dist/webtriggers/dropMigrationWebTrigger.js +54 -0
  50. package/dist/webtriggers/dropMigrationWebTrigger.js.map +1 -0
  51. package/dist/webtriggers/dropTablesMigrationWebTrigger.js +54 -0
  52. package/dist/webtriggers/dropTablesMigrationWebTrigger.js.map +1 -0
  53. package/dist/webtriggers/fetchSchemaWebTrigger.js +82 -0
  54. package/dist/webtriggers/fetchSchemaWebTrigger.js.map +1 -0
  55. package/dist/webtriggers/index.js +40 -0
  56. package/dist/webtriggers/index.js.map +1 -0
  57. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -1
  58. package/dist/webtriggers/slowQuerySchedulerTrigger.js +80 -0
  59. package/dist/webtriggers/slowQuerySchedulerTrigger.js.map +1 -0
  60. package/package.json +28 -23
  61. package/src/core/ForgeSQLAnalyseOperations.ts +3 -2
  62. package/src/core/ForgeSQLORM.ts +33 -27
  63. package/src/lib/drizzle/extensions/additionalActions.ts +11 -0
  64. package/src/utils/cacheContextUtils.ts +9 -6
  65. package/src/utils/cacheUtils.ts +28 -5
  66. package/src/utils/forgeDriver.ts +10 -6
  67. package/src/utils/metadataContextUtils.ts +1 -4
  68. package/src/utils/sqlUtils.ts +136 -125
  69. package/src/webtriggers/slowQuerySchedulerTrigger.ts +40 -33
  70. package/dist/ForgeSQLORM.js +0 -3896
  71. package/dist/ForgeSQLORM.js.map +0 -1
  72. package/dist/ForgeSQLORM.mjs +0 -3879
  73. package/dist/ForgeSQLORM.mjs.map +0 -1
@@ -1,3896 +0,0 @@
1
- "use strict";
2
- Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const drizzleOrm = require("drizzle-orm");
4
- const luxon = require("luxon");
5
- const sql$1 = require("drizzle-orm/sql/sql");
6
- const mysqlCore = require("drizzle-orm/mysql-core");
7
- const sql = require("@forge/sql");
8
- const node_async_hooks = require("node:async_hooks");
9
- const table = require("drizzle-orm/table");
10
- const crypto = require("crypto");
11
- const kvs = require("@forge/kvs");
12
- const mysqlProxy = require("drizzle-orm/mysql-proxy");
13
- function _interopNamespaceDefault(e) {
14
- const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
15
- if (e) {
16
- for (const k in e) {
17
- if (k !== "default") {
18
- const d = Object.getOwnPropertyDescriptor(e, k);
19
- Object.defineProperty(n, k, d.get ? d : {
20
- enumerable: true,
21
- get: () => e[k]
22
- });
23
- }
24
- }
25
- }
26
- n.default = e;
27
- return Object.freeze(n);
28
- }
29
- const crypto__namespace = /* @__PURE__ */ _interopNamespaceDefault(crypto);
30
- const migrations = mysqlCore.mysqlTable("__migrations", {
31
- id: mysqlCore.bigint("id", { mode: "number" }).primaryKey().autoincrement(),
32
- name: mysqlCore.varchar("name", { length: 255 }).notNull(),
33
- migratedAt: mysqlCore.timestamp("migratedAt").defaultNow().notNull()
34
- });
35
- const informationSchema = mysqlCore.mysqlSchema("information_schema");
36
- const slowQuery = informationSchema.table("CLUSTER_SLOW_QUERY", {
37
- time: mysqlCore.timestamp("Time", { fsp: 6, mode: "string" }).notNull(),
38
- // Timestamp when the slow query was recorded
39
- txnStartTs: mysqlCore.bigint("Txn_start_ts", { mode: "bigint", unsigned: true }),
40
- // Transaction start timestamp (TSO)
41
- user: mysqlCore.varchar("User", { length: 64 }),
42
- // User executing the query
43
- host: mysqlCore.varchar("Host", { length: 64 }),
44
- // Host from which the query originated
45
- connId: mysqlCore.bigint("Conn_ID", { mode: "bigint", unsigned: true }),
46
- // Connection ID
47
- sessionAlias: mysqlCore.varchar("Session_alias", { length: 64 }),
48
- // Session alias
49
- execRetryCount: mysqlCore.bigint("Exec_retry_count", { mode: "bigint", unsigned: true }),
50
- // Number of retries during execution
51
- execRetryTime: mysqlCore.double("Exec_retry_time"),
52
- // Time spent in retries
53
- queryTime: mysqlCore.double("Query_time"),
54
- // Total execution time
55
- parseTime: mysqlCore.double("Parse_time"),
56
- // Time spent parsing SQL
57
- compileTime: mysqlCore.double("Compile_time"),
58
- // Time spent compiling query plan
59
- rewriteTime: mysqlCore.double("Rewrite_time"),
60
- // Time spent rewriting query
61
- preprocSubqueries: mysqlCore.bigint("Preproc_subqueries", { mode: "bigint", unsigned: true }),
62
- // Number of subqueries preprocessed
63
- preprocSubqueriesTime: mysqlCore.double("Preproc_subqueries_time"),
64
- // Time spent preprocessing subqueries
65
- optimizeTime: mysqlCore.double("Optimize_time"),
66
- // Time spent in optimizer
67
- waitTs: mysqlCore.double("Wait_TS"),
68
- // Wait time for getting TSO
69
- prewriteTime: mysqlCore.double("Prewrite_time"),
70
- // Time spent in prewrite phase
71
- waitPrewriteBinlogTime: mysqlCore.double("Wait_prewrite_binlog_time"),
72
- // Time waiting for binlog prewrite
73
- commitTime: mysqlCore.double("Commit_time"),
74
- // Commit duration
75
- getCommitTsTime: mysqlCore.double("Get_commit_ts_time"),
76
- // Time waiting for commit TSO
77
- commitBackoffTime: mysqlCore.double("Commit_backoff_time"),
78
- // Backoff time during commit
79
- backoffTypes: mysqlCore.varchar("Backoff_types", { length: 64 }),
80
- // Types of backoff occurred
81
- resolveLockTime: mysqlCore.double("Resolve_lock_time"),
82
- // Time resolving locks
83
- localLatchWaitTime: mysqlCore.double("Local_latch_wait_time"),
84
- // Time waiting on local latch
85
- writeKeys: mysqlCore.bigint("Write_keys", { mode: "bigint" }),
86
- // Number of keys written
87
- writeSize: mysqlCore.bigint("Write_size", { mode: "bigint" }),
88
- // Amount of data written
89
- prewriteRegion: mysqlCore.bigint("Prewrite_region", { mode: "bigint" }),
90
- // Regions involved in prewrite
91
- txnRetry: mysqlCore.bigint("Txn_retry", { mode: "bigint" }),
92
- // Transaction retry count
93
- copTime: mysqlCore.double("Cop_time"),
94
- // Time spent in coprocessor
95
- processTime: mysqlCore.double("Process_time"),
96
- // Processing time
97
- waitTime: mysqlCore.double("Wait_time"),
98
- // Wait time in TiKV
99
- backoffTime: mysqlCore.double("Backoff_time"),
100
- // Backoff wait time
101
- lockKeysTime: mysqlCore.double("LockKeys_time"),
102
- // Time spent waiting for locks
103
- requestCount: mysqlCore.bigint("Request_count", { mode: "bigint", unsigned: true }),
104
- // Total number of requests
105
- totalKeys: mysqlCore.bigint("Total_keys", { mode: "bigint", unsigned: true }),
106
- // Total keys scanned
107
- processKeys: mysqlCore.bigint("Process_keys", { mode: "bigint", unsigned: true }),
108
- // Keys processed
109
- rocksdbDeleteSkippedCount: mysqlCore.bigint("Rocksdb_delete_skipped_count", {
110
- mode: "bigint",
111
- unsigned: true
112
- }),
113
- // RocksDB delete skips
114
- rocksdbKeySkippedCount: mysqlCore.bigint("Rocksdb_key_skipped_count", { mode: "bigint", unsigned: true }),
115
- // RocksDB key skips
116
- rocksdbBlockCacheHitCount: mysqlCore.bigint("Rocksdb_block_cache_hit_count", {
117
- mode: "bigint",
118
- unsigned: true
119
- }),
120
- // RocksDB block cache hits
121
- rocksdbBlockReadCount: mysqlCore.bigint("Rocksdb_block_read_count", { mode: "bigint", unsigned: true }),
122
- // RocksDB block reads
123
- rocksdbBlockReadByte: mysqlCore.bigint("Rocksdb_block_read_byte", { mode: "bigint", unsigned: true }),
124
- // RocksDB block read bytes
125
- db: mysqlCore.varchar("DB", { length: 64 }),
126
- // Database name
127
- indexNames: mysqlCore.varchar("Index_names", { length: 100 }),
128
- // Indexes used
129
- isInternal: mysqlCore.boolean("Is_internal"),
130
- // Whether the query is internal
131
- digest: mysqlCore.varchar("Digest", { length: 64 }),
132
- // SQL digest hash
133
- stats: mysqlCore.varchar("Stats", { length: 512 }),
134
- // Stats used during planning
135
- copProcAvg: mysqlCore.double("Cop_proc_avg"),
136
- // Coprocessor average processing time
137
- copProcP90: mysqlCore.double("Cop_proc_p90"),
138
- // Coprocessor 90th percentile processing time
139
- copProcMax: mysqlCore.double("Cop_proc_max"),
140
- // Coprocessor max processing time
141
- copProcAddr: mysqlCore.varchar("Cop_proc_addr", { length: 64 }),
142
- // Coprocessor address for processing
143
- copWaitAvg: mysqlCore.double("Cop_wait_avg"),
144
- // Coprocessor average wait time
145
- copWaitP90: mysqlCore.double("Cop_wait_p90"),
146
- // Coprocessor 90th percentile wait time
147
- copWaitMax: mysqlCore.double("Cop_wait_max"),
148
- // Coprocessor max wait time
149
- copWaitAddr: mysqlCore.varchar("Cop_wait_addr", { length: 64 }),
150
- // Coprocessor address for wait
151
- memMax: mysqlCore.bigint("Mem_max", { mode: "bigint" }),
152
- // Max memory usage
153
- diskMax: mysqlCore.bigint("Disk_max", { mode: "bigint" }),
154
- // Max disk usage
155
- kvTotal: mysqlCore.double("KV_total"),
156
- // Total KV request time
157
- pdTotal: mysqlCore.double("PD_total"),
158
- // Total PD request time
159
- backoffTotal: mysqlCore.double("Backoff_total"),
160
- // Total backoff time
161
- writeSqlResponseTotal: mysqlCore.double("Write_sql_response_total"),
162
- // SQL response write time
163
- resultRows: mysqlCore.bigint("Result_rows", { mode: "bigint" }),
164
- // Rows returned
165
- warnings: mysqlCore.longtext("Warnings"),
166
- // Warnings during execution
167
- backoffDetail: mysqlCore.varchar("Backoff_Detail", { length: 4096 }),
168
- // Detailed backoff info
169
- prepared: mysqlCore.boolean("Prepared"),
170
- // Whether query was prepared
171
- succ: mysqlCore.boolean("Succ"),
172
- // Success flag
173
- isExplicitTxn: mysqlCore.boolean("IsExplicitTxn"),
174
- // Whether explicit transaction
175
- isWriteCacheTable: mysqlCore.boolean("IsWriteCacheTable"),
176
- // Whether wrote to cache table
177
- planFromCache: mysqlCore.boolean("Plan_from_cache"),
178
- // Plan was from cache
179
- planFromBinding: mysqlCore.boolean("Plan_from_binding"),
180
- // Plan was from binding
181
- hasMoreResults: mysqlCore.boolean("Has_more_results"),
182
- // Query returned multiple results
183
- resourceGroup: mysqlCore.varchar("Resource_group", { length: 64 }),
184
- // Resource group name
185
- requestUnitRead: mysqlCore.double("Request_unit_read"),
186
- // RU consumed for read
187
- requestUnitWrite: mysqlCore.double("Request_unit_write"),
188
- // RU consumed for write
189
- timeQueuedByRc: mysqlCore.double("Time_queued_by_rc"),
190
- // Time queued by resource control
191
- tidbCpuTime: mysqlCore.double("Tidb_cpu_time"),
192
- // TiDB CPU time
193
- tikvCpuTime: mysqlCore.double("Tikv_cpu_time"),
194
- // TiKV CPU time
195
- plan: mysqlCore.longtext("Plan"),
196
- // Query execution plan
197
- planDigest: mysqlCore.varchar("Plan_digest", { length: 128 }),
198
- // Plan digest hash
199
- binaryPlan: mysqlCore.longtext("Binary_plan"),
200
- // Binary execution plan
201
- prevStmt: mysqlCore.longtext("Prev_stmt"),
202
- // Previous statement in session
203
- query: mysqlCore.longtext("Query")
204
- // Original SQL query
205
- });
206
- const createClusterStatementsSummarySchema = () => ({
207
- instance: mysqlCore.varchar("INSTANCE", { length: 64 }),
208
- // TiDB/TiKV instance address
209
- summaryBeginTime: mysqlCore.timestamp("SUMMARY_BEGIN_TIME", { mode: "string" }).notNull(),
210
- // Begin time of this summary window
211
- summaryEndTime: mysqlCore.timestamp("SUMMARY_END_TIME", { mode: "string" }).notNull(),
212
- // End time of this summary window
213
- stmtType: mysqlCore.varchar("STMT_TYPE", { length: 64 }).notNull(),
214
- // Statement type (e.g., Select/Insert/Update)
215
- schemaName: mysqlCore.varchar("SCHEMA_NAME", { length: 64 }),
216
- // Current schema name
217
- digest: mysqlCore.varchar("DIGEST", { length: 64 }),
218
- // SQL digest (normalized hash)
219
- digestText: mysqlCore.text("DIGEST_TEXT").notNull(),
220
- // Normalized SQL text
221
- tableNames: mysqlCore.text("TABLE_NAMES"),
222
- // Involved table names
223
- indexNames: mysqlCore.text("INDEX_NAMES"),
224
- // Used index names
225
- sampleUser: mysqlCore.varchar("SAMPLE_USER", { length: 64 }),
226
- // Sampled user who executed the statements
227
- execCount: mysqlCore.bigint("EXEC_COUNT", { mode: "bigint", unsigned: true }).notNull(),
228
- // Total executions
229
- sumErrors: mysqlCore.int("SUM_ERRORS", { unsigned: true }).notNull(),
230
- // Sum of errors
231
- sumWarnings: mysqlCore.int("SUM_WARNINGS", { unsigned: true }).notNull(),
232
- // Sum of warnings
233
- sumLatency: mysqlCore.bigint("SUM_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
234
- // Sum of latency (ns)
235
- maxLatency: mysqlCore.bigint("MAX_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
236
- // Max latency (ns)
237
- minLatency: mysqlCore.bigint("MIN_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
238
- // Min latency (ns)
239
- avgLatency: mysqlCore.bigint("AVG_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
240
- // Avg latency (ns)
241
- avgParseLatency: mysqlCore.bigint("AVG_PARSE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
242
- // Avg parse time (ns)
243
- maxParseLatency: mysqlCore.bigint("MAX_PARSE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
244
- // Max parse time (ns)
245
- avgCompileLatency: mysqlCore.bigint("AVG_COMPILE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
246
- // Avg compile time (ns)
247
- maxCompileLatency: mysqlCore.bigint("MAX_COMPILE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
248
- // Max compile time (ns)
249
- sumCopTaskNum: mysqlCore.bigint("SUM_COP_TASK_NUM", { mode: "bigint", unsigned: true }).notNull(),
250
- // Total number of cop tasks
251
- maxCopProcessTime: mysqlCore.bigint("MAX_COP_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
252
- // Max TiKV coprocessor processing time (ns)
253
- maxCopProcessAddress: mysqlCore.varchar("MAX_COP_PROCESS_ADDRESS", { length: 256 }),
254
- // Address of cop task with max processing time
255
- maxCopWaitTime: mysqlCore.bigint("MAX_COP_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
256
- // Max TiKV coprocessor wait time (ns)
257
- maxCopWaitAddress: mysqlCore.varchar("MAX_COP_WAIT_ADDRESS", { length: 256 }),
258
- // Address of cop task with max wait time
259
- avgProcessTime: mysqlCore.bigint("AVG_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
260
- // Avg TiKV processing time (ns)
261
- maxProcessTime: mysqlCore.bigint("MAX_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
262
- // Max TiKV processing time (ns)
263
- avgWaitTime: mysqlCore.bigint("AVG_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
264
- // Avg TiKV wait time (ns)
265
- maxWaitTime: mysqlCore.bigint("MAX_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
266
- // Max TiKV wait time (ns)
267
- avgBackoffTime: mysqlCore.bigint("AVG_BACKOFF_TIME", { mode: "bigint", unsigned: true }).notNull(),
268
- // Avg backoff time before retry (ns)
269
- maxBackoffTime: mysqlCore.bigint("MAX_BACKOFF_TIME", { mode: "bigint", unsigned: true }).notNull(),
270
- // Max backoff time before retry (ns)
271
- avgTotalKeys: mysqlCore.bigint("AVG_TOTAL_KEYS", { mode: "bigint", unsigned: true }).notNull(),
272
- // Avg scanned keys
273
- maxTotalKeys: mysqlCore.bigint("MAX_TOTAL_KEYS", { mode: "bigint", unsigned: true }).notNull(),
274
- // Max scanned keys
275
- avgProcessedKeys: mysqlCore.bigint("AVG_PROCESSED_KEYS", { mode: "bigint", unsigned: true }).notNull(),
276
- // Avg processed keys
277
- maxProcessedKeys: mysqlCore.bigint("MAX_PROCESSED_KEYS", { mode: "bigint", unsigned: true }).notNull(),
278
- // Max processed keys
279
- avgRocksdbDeleteSkippedCount: mysqlCore.double("AVG_ROCKSDB_DELETE_SKIPPED_COUNT").notNull(),
280
- // Avg RocksDB deletes skipped
281
- maxRocksdbDeleteSkippedCount: mysqlCore.int("MAX_ROCKSDB_DELETE_SKIPPED_COUNT", {
282
- unsigned: true
283
- }).notNull(),
284
- // Max RocksDB deletes skipped
285
- avgRocksdbKeySkippedCount: mysqlCore.double("AVG_ROCKSDB_KEY_SKIPPED_COUNT").notNull(),
286
- // Avg RocksDB keys skipped
287
- maxRocksdbKeySkippedCount: mysqlCore.int("MAX_ROCKSDB_KEY_SKIPPED_COUNT", { unsigned: true }).notNull(),
288
- // Max RocksDB keys skipped
289
- avgRocksdbBlockCacheHitCount: mysqlCore.double("AVG_ROCKSDB_BLOCK_CACHE_HIT_COUNT").notNull(),
290
- // Avg RocksDB block cache hits
291
- maxRocksdbBlockCacheHitCount: mysqlCore.int("MAX_ROCKSDB_BLOCK_CACHE_HIT_COUNT", {
292
- unsigned: true
293
- }).notNull(),
294
- // Max RocksDB block cache hits
295
- avgRocksdbBlockReadCount: mysqlCore.double("AVG_ROCKSDB_BLOCK_READ_COUNT").notNull(),
296
- // Avg RocksDB block reads
297
- maxRocksdbBlockReadCount: mysqlCore.int("MAX_ROCKSDB_BLOCK_READ_COUNT", { unsigned: true }).notNull(),
298
- // Max RocksDB block reads
299
- avgRocksdbBlockReadByte: mysqlCore.double("AVG_ROCKSDB_BLOCK_READ_BYTE").notNull(),
300
- // Avg RocksDB block read bytes
301
- maxRocksdbBlockReadByte: mysqlCore.int("MAX_ROCKSDB_BLOCK_READ_BYTE", { unsigned: true }).notNull(),
302
- // Max RocksDB block read bytes
303
- avgPrewriteTime: mysqlCore.bigint("AVG_PREWRITE_TIME", { mode: "bigint", unsigned: true }).notNull(),
304
- // Avg prewrite phase time (ns)
305
- maxPrewriteTime: mysqlCore.bigint("MAX_PREWRITE_TIME", { mode: "bigint", unsigned: true }).notNull(),
306
- // Max prewrite phase time (ns)
307
- avgCommitTime: mysqlCore.bigint("AVG_COMMIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
308
- // Avg commit phase time (ns)
309
- maxCommitTime: mysqlCore.bigint("MAX_COMMIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
310
- // Max commit phase time (ns)
311
- avgGetCommitTsTime: mysqlCore.bigint("AVG_GET_COMMIT_TS_TIME", {
312
- mode: "bigint",
313
- unsigned: true
314
- }).notNull(),
315
- // Avg get commit_ts time (ns)
316
- maxGetCommitTsTime: mysqlCore.bigint("MAX_GET_COMMIT_TS_TIME", {
317
- mode: "bigint",
318
- unsigned: true
319
- }).notNull(),
320
- // Max get commit_ts time (ns)
321
- avgCommitBackoffTime: mysqlCore.bigint("AVG_COMMIT_BACKOFF_TIME", {
322
- mode: "bigint",
323
- unsigned: true
324
- }).notNull(),
325
- // Avg backoff during commit (ns)
326
- maxCommitBackoffTime: mysqlCore.bigint("MAX_COMMIT_BACKOFF_TIME", {
327
- mode: "bigint",
328
- unsigned: true
329
- }).notNull(),
330
- // Max backoff during commit (ns)
331
- avgResolveLockTime: mysqlCore.bigint("AVG_RESOLVE_LOCK_TIME", {
332
- mode: "bigint",
333
- unsigned: true
334
- }).notNull(),
335
- // Avg resolve lock time (ns)
336
- maxResolveLockTime: mysqlCore.bigint("MAX_RESOLVE_LOCK_TIME", {
337
- mode: "bigint",
338
- unsigned: true
339
- }).notNull(),
340
- // Max resolve lock time (ns)
341
- avgLocalLatchWaitTime: mysqlCore.bigint("AVG_LOCAL_LATCH_WAIT_TIME", {
342
- mode: "bigint",
343
- unsigned: true
344
- }).notNull(),
345
- // Avg local latch wait (ns)
346
- maxLocalLatchWaitTime: mysqlCore.bigint("MAX_LOCAL_LATCH_WAIT_TIME", {
347
- mode: "bigint",
348
- unsigned: true
349
- }).notNull(),
350
- // Max local latch wait (ns)
351
- avgWriteKeys: mysqlCore.double("AVG_WRITE_KEYS").notNull(),
352
- // Avg number of written keys
353
- maxWriteKeys: mysqlCore.bigint("MAX_WRITE_KEYS", { mode: "bigint", unsigned: true }).notNull(),
354
- // Max written keys
355
- avgWriteSize: mysqlCore.double("AVG_WRITE_SIZE").notNull(),
356
- // Avg written bytes
357
- maxWriteSize: mysqlCore.bigint("MAX_WRITE_SIZE", { mode: "bigint", unsigned: true }).notNull(),
358
- // Max written bytes
359
- avgPrewriteRegions: mysqlCore.double("AVG_PREWRITE_REGIONS").notNull(),
360
- // Avg regions in prewrite
361
- maxPrewriteRegions: mysqlCore.int("MAX_PREWRITE_REGIONS", { unsigned: true }).notNull(),
362
- // Max regions in prewrite
363
- avgTxnRetry: mysqlCore.double("AVG_TXN_RETRY").notNull(),
364
- // Avg transaction retry count
365
- maxTxnRetry: mysqlCore.int("MAX_TXN_RETRY", { unsigned: true }).notNull(),
366
- // Max transaction retry count
367
- sumExecRetry: mysqlCore.bigint("SUM_EXEC_RETRY", { mode: "bigint", unsigned: true }).notNull(),
368
- // Sum of execution retries (pessimistic)
369
- sumExecRetryTime: mysqlCore.bigint("SUM_EXEC_RETRY_TIME", { mode: "bigint", unsigned: true }).notNull(),
370
- // Sum time of execution retries (ns)
371
- sumBackoffTimes: mysqlCore.bigint("SUM_BACKOFF_TIMES", { mode: "bigint", unsigned: true }).notNull(),
372
- // Sum of backoff retries
373
- backoffTypes: mysqlCore.varchar("BACKOFF_TYPES", { length: 1024 }),
374
- // Backoff types with counts
375
- avgMem: mysqlCore.bigint("AVG_MEM", { mode: "bigint", unsigned: true }).notNull(),
376
- // Avg memory used (bytes)
377
- maxMem: mysqlCore.bigint("MAX_MEM", { mode: "bigint", unsigned: true }).notNull(),
378
- // Max memory used (bytes)
379
- avgDisk: mysqlCore.bigint("AVG_DISK", { mode: "bigint", unsigned: true }).notNull(),
380
- // Avg disk used (bytes)
381
- maxDisk: mysqlCore.bigint("MAX_DISK", { mode: "bigint", unsigned: true }).notNull(),
382
- // Max disk used (bytes)
383
- avgKvTime: mysqlCore.bigint("AVG_KV_TIME", { mode: "bigint", unsigned: true }).notNull(),
384
- // Avg time spent in TiKV (ns)
385
- avgPdTime: mysqlCore.bigint("AVG_PD_TIME", { mode: "bigint", unsigned: true }).notNull(),
386
- // Avg time spent in PD (ns)
387
- avgBackoffTotalTime: mysqlCore.bigint("AVG_BACKOFF_TOTAL_TIME", {
388
- mode: "bigint",
389
- unsigned: true
390
- }).notNull(),
391
- // Avg total backoff time (ns)
392
- avgWriteSqlRespTime: mysqlCore.bigint("AVG_WRITE_SQL_RESP_TIME", {
393
- mode: "bigint",
394
- unsigned: true
395
- }).notNull(),
396
- // Avg write SQL response time (ns)
397
- avgTidbCpuTime: mysqlCore.bigint("AVG_TIDB_CPU_TIME", { mode: "bigint", unsigned: true }).notNull(),
398
- // Avg TiDB CPU time (ns)
399
- avgTikvCpuTime: mysqlCore.bigint("AVG_TIKV_CPU_TIME", { mode: "bigint", unsigned: true }).notNull(),
400
- // Avg TiKV CPU time (ns)
401
- maxResultRows: mysqlCore.bigint("MAX_RESULT_ROWS", { mode: "bigint" }).notNull(),
402
- // Max number of result rows
403
- minResultRows: mysqlCore.bigint("MIN_RESULT_ROWS", { mode: "bigint" }).notNull(),
404
- // Min number of result rows
405
- avgResultRows: mysqlCore.bigint("AVG_RESULT_ROWS", { mode: "bigint" }).notNull(),
406
- // Avg number of result rows
407
- prepared: mysqlCore.boolean("PREPARED").notNull(),
408
- // Whether statements are prepared
409
- avgAffectedRows: mysqlCore.double("AVG_AFFECTED_ROWS").notNull(),
410
- // Avg affected rows
411
- firstSeen: mysqlCore.timestamp("FIRST_SEEN", { mode: "string" }).notNull(),
412
- // First time statements observed
413
- lastSeen: mysqlCore.timestamp("LAST_SEEN", { mode: "string" }).notNull(),
414
- // Last time statements observed
415
- planInCache: mysqlCore.boolean("PLAN_IN_CACHE").notNull(),
416
- // Whether last stmt hit plan cache
417
- planCacheHits: mysqlCore.bigint("PLAN_CACHE_HITS", { mode: "bigint" }).notNull(),
418
- // Number of plan cache hits
419
- planInBinding: mysqlCore.boolean("PLAN_IN_BINDING").notNull(),
420
- // Whether matched bindings
421
- querySampleText: mysqlCore.text("QUERY_SAMPLE_TEXT"),
422
- // Sampled original SQL
423
- prevSampleText: mysqlCore.text("PREV_SAMPLE_TEXT"),
424
- // Sampled previous SQL before commit
425
- planDigest: mysqlCore.varchar("PLAN_DIGEST", { length: 64 }),
426
- // Plan digest hash
427
- plan: mysqlCore.text("PLAN"),
428
- // Sampled textual plan
429
- binaryPlan: mysqlCore.text("BINARY_PLAN"),
430
- // Sampled binary plan
431
- charset: mysqlCore.varchar("CHARSET", { length: 64 }),
432
- // Sampled charset
433
- collation: mysqlCore.varchar("COLLATION", { length: 64 }),
434
- // Sampled collation
435
- planHint: mysqlCore.varchar("PLAN_HINT", { length: 64 }),
436
- // Sampled plan hint
437
- maxRequestUnitRead: mysqlCore.double("MAX_REQUEST_UNIT_READ").notNull(),
438
- // Max RU cost (read)
439
- avgRequestUnitRead: mysqlCore.double("AVG_REQUEST_UNIT_READ").notNull(),
440
- // Avg RU cost (read)
441
- maxRequestUnitWrite: mysqlCore.double("MAX_REQUEST_UNIT_WRITE").notNull(),
442
- // Max RU cost (write)
443
- avgRequestUnitWrite: mysqlCore.double("AVG_REQUEST_UNIT_WRITE").notNull(),
444
- // Avg RU cost (write)
445
- maxQueuedRcTime: mysqlCore.bigint("MAX_QUEUED_RC_TIME", { mode: "bigint", unsigned: true }).notNull(),
446
- // Max queued time waiting for RU (ns)
447
- avgQueuedRcTime: mysqlCore.bigint("AVG_QUEUED_RC_TIME", { mode: "bigint", unsigned: true }).notNull(),
448
- // Avg queued time waiting for RU (ns)
449
- resourceGroup: mysqlCore.varchar("RESOURCE_GROUP", { length: 64 }),
450
- // Bound resource group name
451
- planCacheUnqualified: mysqlCore.bigint("PLAN_CACHE_UNQUALIFIED", { mode: "bigint" }).notNull(),
452
- // Times not eligible for plan cache
453
- planCacheUnqualifiedLastReason: mysqlCore.text("PLAN_CACHE_UNQUALIFIED_LAST_REASON")
454
- // Last reason of plan cache ineligibility
455
- });
456
- const clusterStatementsSummaryHistory = informationSchema.table(
457
- "CLUSTER_STATEMENTS_SUMMARY_HISTORY",
458
- createClusterStatementsSummarySchema()
459
- );
460
- const statementsSummaryHistory = informationSchema.table(
461
- "STATEMENTS_SUMMARY_HISTORY",
462
- createClusterStatementsSummarySchema()
463
- );
464
- const statementsSummary = informationSchema.table(
465
- "STATEMENTS_SUMMARY",
466
- createClusterStatementsSummarySchema()
467
- );
468
- const clusterStatementsSummary = informationSchema.table(
469
- "CLUSTER_STATEMENTS_SUMMARY",
470
- createClusterStatementsSummarySchema()
471
- );
472
- async function getTables() {
473
- const tables = await sql.sql.executeDDL("SHOW TABLES");
474
- return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
475
- }
476
- const forgeSystemTables = [migrations];
477
- const parseDateTime = (value, format) => {
478
- let result;
479
- if (value instanceof Date) {
480
- result = value;
481
- } else {
482
- const dt = luxon.DateTime.fromFormat(value, format);
483
- if (dt.isValid) {
484
- result = dt.toJSDate();
485
- } else {
486
- const sqlDt = luxon.DateTime.fromSQL(value);
487
- if (sqlDt.isValid) {
488
- result = sqlDt.toJSDate();
489
- } else {
490
- const isoDt = luxon.DateTime.fromRFC2822(value);
491
- if (isoDt.isValid) {
492
- result = isoDt.toJSDate();
493
- } else {
494
- result = new Date(value);
495
- }
496
- }
497
- }
498
- }
499
- if (isNaN(result.getTime())) {
500
- result = new Date(value);
501
- }
502
- return result;
503
- };
504
- function formatDateTime(value, format, isTimeStamp) {
505
- let dt = null;
506
- if (value instanceof Date) {
507
- dt = luxon.DateTime.fromJSDate(value);
508
- } else if (typeof value === "string") {
509
- for (const parser of [
510
- luxon.DateTime.fromISO,
511
- luxon.DateTime.fromRFC2822,
512
- luxon.DateTime.fromSQL,
513
- luxon.DateTime.fromHTTP
514
- ]) {
515
- dt = parser(value);
516
- if (dt.isValid) break;
517
- }
518
- if (!dt?.isValid) {
519
- const parsed = Number(value);
520
- if (!isNaN(parsed)) {
521
- dt = luxon.DateTime.fromMillis(parsed);
522
- }
523
- }
524
- } else if (typeof value === "number") {
525
- dt = luxon.DateTime.fromMillis(value);
526
- } else {
527
- throw new Error("Unsupported type");
528
- }
529
- if (!dt?.isValid) {
530
- throw new Error("Invalid Date");
531
- }
532
- const minDate = luxon.DateTime.fromSeconds(1);
533
- const maxDate = luxon.DateTime.fromMillis(2147483647 * 1e3);
534
- if (isTimeStamp) {
535
- if (dt < minDate) {
536
- throw new Error(
537
- "Atlassian Forge does not support zero or negative timestamps. Allowed range: from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'."
538
- );
539
- }
540
- if (dt > maxDate) {
541
- throw new Error(
542
- "Atlassian Forge does not support timestamps beyond 2038-01-19 03:14:07.999999. Please use a smaller date within the supported range."
543
- );
544
- }
545
- }
546
- return dt.toFormat(format);
547
- }
548
- function getPrimaryKeys(table2) {
549
- const { columns, primaryKeys } = getTableMetadata(table2);
550
- const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
551
- if (columnPrimaryKeys.length > 0) {
552
- return columnPrimaryKeys;
553
- }
554
- if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
555
- const primaryKeyColumns = /* @__PURE__ */ new Set();
556
- primaryKeys.forEach((primaryKeyBuilder) => {
557
- Object.entries(columns).filter(([, column]) => {
558
- return primaryKeyBuilder.columns.includes(column);
559
- }).forEach(([name, column]) => {
560
- primaryKeyColumns.add([name, column]);
561
- });
562
- });
563
- return Array.from(primaryKeyColumns);
564
- }
565
- return [];
566
- }
567
- function processForeignKeys(table2, foreignKeysSymbol, extraSymbol) {
568
- const foreignKeys = [];
569
- if (foreignKeysSymbol) {
570
- const fkArray = table2[foreignKeysSymbol];
571
- if (fkArray) {
572
- fkArray.forEach((fk) => {
573
- if (fk.reference) {
574
- const item = fk.reference(fk);
575
- foreignKeys.push(item);
576
- }
577
- });
578
- }
579
- }
580
- if (extraSymbol) {
581
- const extraConfigBuilder = table2[extraSymbol];
582
- if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
583
- const configBuilderData = extraConfigBuilder(table2);
584
- if (configBuilderData) {
585
- const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
586
- (item) => item.value ?? item
587
- );
588
- configBuilders.forEach((builder) => {
589
- if (!builder?.constructor) return;
590
- const builderName = builder.constructor.name.toLowerCase();
591
- if (builderName.includes("foreignkeybuilder")) {
592
- foreignKeys.push(builder);
593
- }
594
- });
595
- }
596
- }
597
- }
598
- return foreignKeys;
599
- }
600
- function getTableMetadata(table2) {
601
- const symbols = Object.getOwnPropertySymbols(table2);
602
- const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
603
- const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
604
- const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
605
- const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
606
- const builders = {
607
- indexes: [],
608
- checks: [],
609
- foreignKeys: [],
610
- primaryKeys: [],
611
- uniqueConstraints: [],
612
- extras: []
613
- };
614
- builders.foreignKeys = processForeignKeys(table2, foreignKeysSymbol, extraSymbol);
615
- if (extraSymbol) {
616
- const extraConfigBuilder = table2[extraSymbol];
617
- if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
618
- const configBuilderData = extraConfigBuilder(table2);
619
- if (configBuilderData) {
620
- const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
621
- (item) => item.value ?? item
622
- );
623
- configBuilders.forEach((builder) => {
624
- if (!builder?.constructor) return;
625
- const builderName = builder.constructor.name.toLowerCase();
626
- const builderMap = {
627
- indexbuilder: builders.indexes,
628
- checkbuilder: builders.checks,
629
- primarykeybuilder: builders.primaryKeys,
630
- uniqueconstraintbuilder: builders.uniqueConstraints
631
- };
632
- for (const [type, array] of Object.entries(builderMap)) {
633
- if (builderName.includes(type)) {
634
- array.push(builder);
635
- break;
636
- }
637
- }
638
- builders.extras.push(builder);
639
- });
640
- }
641
- }
642
- }
643
- return {
644
- tableName: nameSymbol ? table2[nameSymbol] : "",
645
- columns: columnsSymbol ? table2[columnsSymbol] : {},
646
- ...builders
647
- };
648
- }
649
- function generateDropTableStatements(tables, options) {
650
- const dropStatements = [];
651
- const validOptions = options ?? { sequence: true, table: true };
652
- if (!validOptions.sequence && !validOptions.table) {
653
- console.warn('No drop operations requested: both "table" and "sequence" options are false');
654
- return [];
655
- }
656
- tables.forEach((tableName) => {
657
- if (validOptions.table) {
658
- dropStatements.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
659
- }
660
- if (validOptions.sequence) {
661
- dropStatements.push(`DROP SEQUENCE IF EXISTS \`${tableName}\`;`);
662
- }
663
- });
664
- return dropStatements;
665
- }
666
- function mapSelectTableToAlias(table2, uniqPrefix, aliasMap) {
667
- const { columns, tableName } = getTableMetadata(table2);
668
- const selectionsTableFields = {};
669
- Object.keys(columns).forEach((name) => {
670
- const column = columns[name];
671
- const uniqName = `a_${uniqPrefix}_${tableName}_${column.name}`.toLowerCase();
672
- const fieldAlias = drizzleOrm.sql.raw(uniqName);
673
- selectionsTableFields[name] = drizzleOrm.sql`${column} as \`${fieldAlias}\``;
674
- aliasMap[uniqName] = column;
675
- });
676
- return selectionsTableFields;
677
- }
678
- function isDrizzleColumn(column) {
679
- return column && typeof column === "object" && "table" in column;
680
- }
681
- function mapSelectAllFieldsToAlias(selections, name, uniqName, fields, aliasMap) {
682
- if (drizzleOrm.isTable(fields)) {
683
- selections[name] = mapSelectTableToAlias(fields, uniqName, aliasMap);
684
- } else if (isDrizzleColumn(fields)) {
685
- const column = fields;
686
- const uniqAliasName = `a_${uniqName}_${column.name}`.toLowerCase();
687
- let aliasName = drizzleOrm.sql.raw(uniqAliasName);
688
- selections[name] = drizzleOrm.sql`${column} as \`${aliasName}\``;
689
- aliasMap[uniqAliasName] = column;
690
- } else if (sql$1.isSQLWrapper(fields)) {
691
- selections[name] = fields;
692
- } else {
693
- const innerSelections = {};
694
- Object.entries(fields).forEach(([iname, ifields]) => {
695
- mapSelectAllFieldsToAlias(innerSelections, iname, `${uniqName}_${iname}`, ifields, aliasMap);
696
- });
697
- selections[name] = innerSelections;
698
- }
699
- return selections;
700
- }
701
- function mapSelectFieldsWithAlias(fields) {
702
- if (!fields) {
703
- throw new Error("fields is empty");
704
- }
705
- const aliasMap = {};
706
- const selections = {};
707
- Object.entries(fields).forEach(([name, fields2]) => {
708
- mapSelectAllFieldsToAlias(selections, name, name, fields2, aliasMap);
709
- });
710
- return { selections, aliasMap };
711
- }
712
- function getAliasFromDrizzleAlias(value) {
713
- const isSQL = value !== null && typeof value === "object" && sql$1.isSQLWrapper(value) && "queryChunks" in value;
714
- if (isSQL) {
715
- const sql2 = value;
716
- const queryChunks = sql2.queryChunks;
717
- if (queryChunks.length > 3) {
718
- const aliasNameChunk = queryChunks[queryChunks.length - 2];
719
- if (sql$1.isSQLWrapper(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
720
- const aliasNameChunkSql = aliasNameChunk;
721
- if (aliasNameChunkSql.queryChunks?.length === 1 && aliasNameChunkSql.queryChunks[0]) {
722
- const queryChunksStringChunc = aliasNameChunkSql.queryChunks[0];
723
- if ("value" in queryChunksStringChunc) {
724
- const values = queryChunksStringChunc.value;
725
- if (values && values.length === 1) {
726
- return values[0];
727
- }
728
- }
729
- }
730
- }
731
- }
732
- }
733
- return void 0;
734
- }
735
- function transformValue(value, alias, aliasMap) {
736
- const column = aliasMap[alias];
737
- if (!column) return value;
738
- let customColumn = column;
739
- const fromDriver = customColumn?.mapFrom;
740
- if (fromDriver && value !== null && value !== void 0) {
741
- return fromDriver(value);
742
- }
743
- return value;
744
- }
745
- function transformObject(obj, selections, aliasMap) {
746
- const result = {};
747
- for (const [key, value] of Object.entries(obj)) {
748
- const selection = selections[key];
749
- const alias = getAliasFromDrizzleAlias(selection);
750
- if (alias && aliasMap[alias]) {
751
- result[key] = transformValue(value, alias, aliasMap);
752
- } else if (selection && typeof selection === "object" && !sql$1.isSQLWrapper(selection)) {
753
- result[key] = transformObject(
754
- value,
755
- selection,
756
- aliasMap
757
- );
758
- } else {
759
- result[key] = value;
760
- }
761
- }
762
- return result;
763
- }
764
- function applyFromDriverTransform(rows, selections, aliasMap) {
765
- return rows.map((row) => {
766
- const transformed = transformObject(
767
- row,
768
- selections,
769
- aliasMap
770
- );
771
- return processNullBranches(transformed);
772
- });
773
- }
774
- function processNullBranches(obj) {
775
- if (obj === null || typeof obj !== "object") {
776
- return obj;
777
- }
778
- if (obj.constructor && obj.constructor.name !== "Object") {
779
- return obj;
780
- }
781
- const result = {};
782
- let allNull = true;
783
- for (const [key, value] of Object.entries(obj)) {
784
- if (value === null || value === void 0) {
785
- result[key] = null;
786
- continue;
787
- }
788
- if (typeof value === "object") {
789
- const processed = processNullBranches(value);
790
- result[key] = processed;
791
- if (processed !== null) {
792
- allNull = false;
793
- }
794
- } else {
795
- result[key] = value;
796
- allNull = false;
797
- }
798
- }
799
- return allNull ? null : result;
800
- }
801
- function formatLimitOffset(limitOrOffset) {
802
- if (typeof limitOrOffset !== "number" || isNaN(limitOrOffset)) {
803
- throw new Error("limitOrOffset must be a valid number");
804
- }
805
- return drizzleOrm.sql.raw(`${limitOrOffset}`);
806
- }
807
- function nextVal(sequenceName) {
808
- return drizzleOrm.sql.raw(`NEXTVAL(${sequenceName})`);
809
- }
810
- async function printQueriesWithPlan(forgeSQLORM, timeDiffMs, timeout) {
811
- try {
812
- const statementsTable = clusterStatementsSummary;
813
- const timeoutMs2 = timeout ?? 3e3;
814
- const results = await withTimeout(
815
- forgeSQLORM.getDrizzleQueryBuilder().select({
816
- digestText: withTidbHint(statementsTable.digestText),
817
- avgLatency: statementsTable.avgLatency,
818
- avgMem: statementsTable.avgMem,
819
- execCount: statementsTable.execCount,
820
- plan: statementsTable.plan,
821
- stmtType: statementsTable.stmtType
822
- }).from(statementsTable).where(
823
- drizzleOrm.and(
824
- drizzleOrm.isNotNull(statementsTable.digest),
825
- drizzleOrm.not(drizzleOrm.ilike(statementsTable.digestText, "%information_schema%")),
826
- drizzleOrm.notInArray(statementsTable.stmtType, ["Use", "Set", "Show", "Commit", "Rollback", "Begin"]),
827
- drizzleOrm.gte(
828
- statementsTable.lastSeen,
829
- drizzleOrm.sql`DATE_SUB
830
- (NOW(), INTERVAL
831
- ${timeDiffMs * 1e3}
832
- MICROSECOND
833
- )`
834
- )
835
- )
836
- ),
837
- `Timeout ${timeoutMs2}ms in printQueriesWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
838
- timeoutMs2 + 200
839
- );
840
- results.forEach((result) => {
841
- const avgTimeMs = Number(result.avgLatency) / 1e6;
842
- const avgMemMB = Number(result.avgMem) / 1e6;
843
- console.warn(
844
- `SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}
845
- Plan:${result.plan}`
846
- );
847
- });
848
- } catch (error) {
849
- console.debug(
850
- `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
851
- error
852
- );
853
- }
854
- }
855
- const SESSION_ALIAS_NAME_ORM = "orm";
856
- async function slowQueryPerHours(forgeSQLORM, hours, timeout) {
857
- try {
858
- const timeoutMs2 = timeout ?? 1500;
859
- const results = await withTimeout(
860
- forgeSQLORM.getDrizzleQueryBuilder().select({
861
- query: withTidbHint(slowQuery.query),
862
- queryTime: slowQuery.queryTime,
863
- memMax: slowQuery.memMax,
864
- plan: slowQuery.plan
865
- }).from(slowQuery).where(
866
- drizzleOrm.and(
867
- drizzleOrm.isNotNull(slowQuery.digest),
868
- drizzleOrm.ne(slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM),
869
- drizzleOrm.gte(
870
- slowQuery.time,
871
- drizzleOrm.sql`DATE_SUB
872
- (NOW(), INTERVAL
873
- ${hours}
874
- HOUR
875
- )`
876
- )
877
- )
878
- ),
879
- `Timeout ${timeoutMs2}ms in slowQueryPerHours - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
880
- timeoutMs2
881
- );
882
- const response = [];
883
- results.forEach((result) => {
884
- const memMaxMB = result.memMax ? Number(result.memMax) / 1e6 : 0;
885
- const message = `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms
886
- Plan:${result.plan}`;
887
- response.push(message);
888
- console.warn(
889
- message
890
- );
891
- });
892
- return response;
893
- } catch (error) {
894
- console.debug(
895
- `Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
896
- error
897
- );
898
- return [`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}`];
899
- }
900
- }
901
- async function withTimeout(promise, message, timeoutMs2) {
902
- let timeoutId;
903
- const timeoutPromise = new Promise((_, reject) => {
904
- timeoutId = setTimeout(() => {
905
- reject(
906
- new Error(message)
907
- );
908
- }, timeoutMs2);
909
- });
910
- try {
911
- return await Promise.race([promise, timeoutPromise]);
912
- } finally {
913
- if (timeoutId) {
914
- clearTimeout(timeoutId);
915
- }
916
- }
917
- }
918
- function withTidbHint(column) {
919
- return drizzleOrm.sql`/*+ SET_VAR(tidb_session_alias=${drizzleOrm.sql.raw(`${SESSION_ALIAS_NAME_ORM}`)}) */ ${column}`;
920
- }
921
- const CACHE_CONSTANTS = {
922
- BATCH_SIZE: 25,
923
- MAX_RETRY_ATTEMPTS: 3,
924
- INITIAL_RETRY_DELAY: 1e3,
925
- RETRY_DELAY_MULTIPLIER: 2,
926
- DEFAULT_ENTITY_QUERY_NAME: "sql",
927
- DEFAULT_EXPIRATION_NAME: "expiration",
928
- DEFAULT_DATA_NAME: "data",
929
- HASH_LENGTH: 32
930
- };
931
- function getCurrentTime() {
932
- const dt = luxon.DateTime.now();
933
- return Math.floor(dt.toSeconds());
934
- }
935
- function nowPlusSeconds(secondsToAdd) {
936
- const dt = luxon.DateTime.now().plus({ seconds: secondsToAdd });
937
- return Math.floor(dt.toSeconds());
938
- }
939
- function hashKey(query) {
940
- const h = crypto__namespace.createHash("sha256");
941
- h.update(query.sql.toLowerCase());
942
- h.update(JSON.stringify(query.params));
943
- return "CachedQuery_" + h.digest("hex").slice(0, CACHE_CONSTANTS.HASH_LENGTH);
944
- }
945
- async function deleteCacheEntriesInBatches(results, cacheEntityName) {
946
- for (let i = 0; i < results.length; i += CACHE_CONSTANTS.BATCH_SIZE) {
947
- const batch = results.slice(i, i + CACHE_CONSTANTS.BATCH_SIZE);
948
- let transactionBuilder = kvs.kvs.transact();
949
- batch.forEach((result) => {
950
- transactionBuilder = transactionBuilder.delete(result.key, { entityName: cacheEntityName });
951
- });
952
- await transactionBuilder.execute();
953
- }
954
- }
955
- async function clearCursorCache(tables, cursor, options) {
956
- const cacheEntityName = options.cacheEntityName;
957
- if (!cacheEntityName) {
958
- throw new Error("cacheEntityName is not configured");
959
- }
960
- const entityQueryName = options.cacheEntityQueryName ?? CACHE_CONSTANTS.DEFAULT_ENTITY_QUERY_NAME;
961
- let filters = new kvs.Filter();
962
- for (const table2 of tables) {
963
- const wrapIfNeeded = options.cacheWrapTable ? `\`${table2}\`` : table2;
964
- filters.or(entityQueryName, kvs.FilterConditions.contains(wrapIfNeeded?.toLowerCase()));
965
- }
966
- let entityQueryBuilder = kvs.kvs.entity(cacheEntityName).query().index(entityQueryName).filters(filters);
967
- if (cursor) {
968
- entityQueryBuilder = entityQueryBuilder.cursor(cursor);
969
- }
970
- const listResult = await entityQueryBuilder.limit(100).getMany();
971
- if (options.logCache) {
972
- console.warn(`clear cache Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
973
- }
974
- await deleteCacheEntriesInBatches(listResult.results, cacheEntityName);
975
- if (listResult.nextCursor) {
976
- return listResult.results.length + await clearCursorCache(tables, listResult.nextCursor, options);
977
- } else {
978
- return listResult.results.length;
979
- }
980
- }
981
- async function clearExpirationCursorCache(cursor, options) {
982
- const cacheEntityName = options.cacheEntityName;
983
- if (!cacheEntityName) {
984
- throw new Error("cacheEntityName is not configured");
985
- }
986
- const entityExpirationName = options.cacheEntityExpirationName ?? CACHE_CONSTANTS.DEFAULT_EXPIRATION_NAME;
987
- let entityQueryBuilder = kvs.kvs.entity(cacheEntityName).query().index(entityExpirationName).where(kvs.WhereConditions.lessThan(Math.floor(luxon.DateTime.now().toSeconds())));
988
- if (cursor) {
989
- entityQueryBuilder = entityQueryBuilder.cursor(cursor);
990
- }
991
- const listResult = await entityQueryBuilder.limit(100).getMany();
992
- if (options.logCache) {
993
- console.warn(`clear expired Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
994
- }
995
- await deleteCacheEntriesInBatches(listResult.results, cacheEntityName);
996
- if (listResult.nextCursor) {
997
- return listResult.results.length + await clearExpirationCursorCache(listResult.nextCursor, options);
998
- } else {
999
- return listResult.results.length;
1000
- }
1001
- }
1002
- async function executeWithRetry(operation, operationName) {
1003
- let attempt = 0;
1004
- let delay = CACHE_CONSTANTS.INITIAL_RETRY_DELAY;
1005
- while (attempt < CACHE_CONSTANTS.MAX_RETRY_ATTEMPTS) {
1006
- try {
1007
- return await operation();
1008
- } catch (err) {
1009
- console.warn(`Error during ${operationName}: ${err.message}, retry ${attempt}`, err);
1010
- attempt++;
1011
- if (attempt >= CACHE_CONSTANTS.MAX_RETRY_ATTEMPTS) {
1012
- console.error(`Error during ${operationName}: ${err.message}`, err);
1013
- throw err;
1014
- }
1015
- await new Promise((resolve) => setTimeout(resolve, delay));
1016
- delay *= CACHE_CONSTANTS.RETRY_DELAY_MULTIPLIER;
1017
- }
1018
- }
1019
- throw new Error(`Maximum retry attempts exceeded for ${operationName}`);
1020
- }
1021
- async function clearCache(schema, options) {
1022
- const tableName = table.getTableName(schema);
1023
- if (cacheApplicationContext.getStore()) {
1024
- cacheApplicationContext.getStore()?.tables.add(tableName);
1025
- } else {
1026
- await clearTablesCache([tableName], options);
1027
- }
1028
- }
1029
- async function clearTablesCache(tables, options) {
1030
- if (!options.cacheEntityName) {
1031
- throw new Error("cacheEntityName is not configured");
1032
- }
1033
- const startTime = luxon.DateTime.now();
1034
- let totalRecords = 0;
1035
- try {
1036
- totalRecords = await executeWithRetry(
1037
- () => clearCursorCache(tables, "", options),
1038
- "clearing cache"
1039
- );
1040
- } finally {
1041
- if (options.logCache) {
1042
- const duration = luxon.DateTime.now().toSeconds() - startTime.toSeconds();
1043
- console.info(`Cleared ${totalRecords} cache records in ${duration} seconds`);
1044
- }
1045
- }
1046
- }
1047
- async function clearExpiredCache(options) {
1048
- if (!options.cacheEntityName) {
1049
- throw new Error("cacheEntityName is not configured");
1050
- }
1051
- const startTime = luxon.DateTime.now();
1052
- let totalRecords = 0;
1053
- try {
1054
- totalRecords = await executeWithRetry(
1055
- () => clearExpirationCursorCache("", options),
1056
- "clearing expired cache"
1057
- );
1058
- } finally {
1059
- const duration = luxon.DateTime.now().toSeconds() - startTime.toSeconds();
1060
- if (options?.logCache) {
1061
- console.debug(`Cleared ${totalRecords} expired cache records in ${duration} seconds`);
1062
- }
1063
- }
1064
- }
1065
- async function getFromCache(query, options) {
1066
- if (!options.cacheEntityName) {
1067
- throw new Error("cacheEntityName is not configured");
1068
- }
1069
- const entityQueryName = options.cacheEntityQueryName ?? CACHE_CONSTANTS.DEFAULT_ENTITY_QUERY_NAME;
1070
- const expirationName = options.cacheEntityExpirationName ?? CACHE_CONSTANTS.DEFAULT_EXPIRATION_NAME;
1071
- const dataName = options.cacheEntityDataName ?? CACHE_CONSTANTS.DEFAULT_DATA_NAME;
1072
- const sqlQuery = query.toSQL();
1073
- const key = hashKey(sqlQuery);
1074
- if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
1075
- if (options.logCache) {
1076
- console.warn(`Context contains value to clear. Skip getting from cache`);
1077
- }
1078
- return void 0;
1079
- }
1080
- try {
1081
- const cacheResult = await kvs.kvs.entity(options.cacheEntityName).get(key);
1082
- if (cacheResult && cacheResult[expirationName] >= getCurrentTime() && sqlQuery.sql.toLowerCase() === cacheResult[entityQueryName]) {
1083
- if (options.logCache) {
1084
- console.warn(`Get value from cache, cacheKey: ${key}`);
1085
- }
1086
- const results = cacheResult[dataName];
1087
- return JSON.parse(results);
1088
- }
1089
- } catch (error) {
1090
- console.error(`Error getting from cache: ${error.message}`, error);
1091
- }
1092
- return void 0;
1093
- }
1094
- async function setCacheResult(query, options, results, cacheTtl) {
1095
- if (!options.cacheEntityName) {
1096
- throw new Error("cacheEntityName is not configured");
1097
- }
1098
- try {
1099
- const entityQueryName = options.cacheEntityQueryName ?? CACHE_CONSTANTS.DEFAULT_ENTITY_QUERY_NAME;
1100
- const expirationName = options.cacheEntityExpirationName ?? CACHE_CONSTANTS.DEFAULT_EXPIRATION_NAME;
1101
- const dataName = options.cacheEntityDataName ?? CACHE_CONSTANTS.DEFAULT_DATA_NAME;
1102
- const sqlQuery = query.toSQL();
1103
- if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
1104
- if (options.logCache) {
1105
- console.warn(`Context contains value to clear. Skip setting from cache`);
1106
- }
1107
- return;
1108
- }
1109
- const key = hashKey(sqlQuery);
1110
- await kvs.kvs.transact().set(
1111
- key,
1112
- {
1113
- [entityQueryName]: sqlQuery.sql.toLowerCase(),
1114
- [expirationName]: nowPlusSeconds(cacheTtl),
1115
- [dataName]: JSON.stringify(results)
1116
- },
1117
- { entityName: options.cacheEntityName }
1118
- ).execute();
1119
- if (options.logCache) {
1120
- console.warn(`Store value to cache, cacheKey: ${key}`);
1121
- }
1122
- } catch (error) {
1123
- console.error(`Error setting cache: ${error.message}`, error);
1124
- }
1125
- }
1126
- function isQuery(obj) {
1127
- return typeof obj === "object" && obj !== null && typeof obj.sql === "string" && Array.isArray(obj.params);
1128
- }
1129
- const cacheApplicationContext = new node_async_hooks.AsyncLocalStorage();
1130
- const localCacheApplicationContext = new node_async_hooks.AsyncLocalStorage();
1131
- async function saveTableIfInsideCacheContext(table$1) {
1132
- const context = cacheApplicationContext.getStore();
1133
- if (context) {
1134
- const tableName = table.getTableName(table$1).toLowerCase();
1135
- context.tables.add(tableName);
1136
- }
1137
- }
1138
- async function saveQueryLocalCacheQuery(query, rows, options) {
1139
- const context = localCacheApplicationContext.getStore();
1140
- if (context) {
1141
- if (!context.cache) {
1142
- context.cache = {};
1143
- }
1144
- let sql2;
1145
- if (isQuery(query)) {
1146
- sql2 = { toSQL: () => query };
1147
- } else {
1148
- sql2 = query;
1149
- }
1150
- const key = hashKey(sql2.toSQL());
1151
- context.cache[key] = {
1152
- sql: sql2.toSQL().sql.toLowerCase(),
1153
- data: rows
1154
- };
1155
- if (options.logCache) {
1156
- const q = sql2.toSQL();
1157
- console.debug(
1158
- `[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`
1159
- );
1160
- }
1161
- }
1162
- }
1163
- async function getQueryLocalCacheQuery(query, options) {
1164
- const context = localCacheApplicationContext.getStore();
1165
- if (context) {
1166
- if (!context.cache) {
1167
- context.cache = {};
1168
- }
1169
- let sql2;
1170
- if (isQuery(query)) {
1171
- sql2 = { toSQL: () => query };
1172
- } else {
1173
- sql2 = query;
1174
- }
1175
- const key = hashKey(sql2.toSQL());
1176
- if (context.cache[key] && context.cache[key].sql === sql2.toSQL().sql.toLowerCase()) {
1177
- if (options.logCache) {
1178
- const q = sql2.toSQL();
1179
- console.debug(
1180
- `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`
1181
- );
1182
- }
1183
- return context.cache[key].data;
1184
- }
1185
- }
1186
- return void 0;
1187
- }
1188
- async function evictLocalCacheQuery(table$1, options) {
1189
- const context = localCacheApplicationContext.getStore();
1190
- if (context) {
1191
- if (!context.cache) {
1192
- context.cache = {};
1193
- }
1194
- const tableName = table.getTableName(table$1);
1195
- const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
1196
- const keyToEvicts = [];
1197
- Object.keys(context.cache).forEach((key) => {
1198
- if (context.cache[key].sql.includes(searchString)) {
1199
- keyToEvicts.push(key);
1200
- }
1201
- });
1202
- keyToEvicts.forEach((key) => delete context.cache[key]);
1203
- }
1204
- }
1205
- async function isTableContainsTableInCacheContext(sql2, options) {
1206
- const context = cacheApplicationContext.getStore();
1207
- if (!context) {
1208
- return false;
1209
- }
1210
- const tables = Array.from(context.tables);
1211
- const lowerSql = sql2.toLowerCase();
1212
- return tables.some((table2) => {
1213
- const tablePattern = options.cacheWrapTable ? `\`${table2}\`` : table2;
1214
- return lowerSql.includes(tablePattern);
1215
- });
1216
- }
1217
- class ForgeSQLCrudOperations {
1218
- forgeOperations;
1219
- options;
1220
- /**
1221
- * Creates a new instance of ForgeSQLCrudOperations.
1222
- * @param forgeSqlOperations - The ForgeSQL operations instance
1223
- * @param options - Configuration options for the ORM
1224
- */
1225
- constructor(forgeSqlOperations, options) {
1226
- this.forgeOperations = forgeSqlOperations;
1227
- this.options = options;
1228
- }
1229
- /**
1230
- * Inserts records into the database with optional versioning support.
1231
- * If a version field exists in the schema, versioning is applied.
1232
- *
1233
- * This method automatically handles:
1234
- * - Version field initialization for optimistic locking
1235
- * - Batch insertion for multiple records
1236
- * - Duplicate key handling with optional updates
1237
- *
1238
- * @template T - The type of the table schema
1239
- * @param schema - The entity schema
1240
- * @param models - Array of entities to insert
1241
- * @param updateIfExists - Whether to update existing records (default: false)
1242
- * @returns Promise that resolves to the number of inserted rows
1243
- * @throws Error if the insert operation fails
1244
- */
1245
- async insert(schema, models, updateIfExists = false) {
1246
- if (!models?.length) return 0;
1247
- const { tableName, columns } = getTableMetadata(schema);
1248
- const versionMetadata = this.validateVersionField(tableName, columns);
1249
- const preparedModels = models.map(
1250
- (model) => this.prepareModelWithVersion(model, versionMetadata, columns)
1251
- );
1252
- const queryBuilder = this.forgeOperations.insert(schema).values(preparedModels);
1253
- const finalQuery = updateIfExists ? queryBuilder.onDuplicateKeyUpdate({
1254
- set: Object.fromEntries(
1255
- Object.keys(preparedModels[0]).map((key) => [key, schema[key]])
1256
- )
1257
- }) : queryBuilder;
1258
- const result = await finalQuery;
1259
- await saveTableIfInsideCacheContext(schema);
1260
- return result[0].insertId;
1261
- }
1262
- /**
1263
- * Deletes a record by its primary key with optional version check.
1264
- * If versioning is enabled, ensures the record hasn't been modified since last read.
1265
- *
1266
- * This method automatically handles:
1267
- * - Single primary key validation
1268
- * - Optimistic locking checks if versioning is enabled
1269
- * - Version field validation before deletion
1270
- *
1271
- * @template T - The type of the table schema
1272
- * @param id - The ID of the record to delete
1273
- * @param schema - The entity schema
1274
- * @returns Promise that resolves to the number of affected rows
1275
- * @throws Error if the delete operation fails
1276
- * @throws Error if multiple primary keys are found
1277
- * @throws Error if optimistic locking check fails
1278
- */
1279
- async deleteById(id, schema) {
1280
- const { tableName, columns } = getTableMetadata(schema);
1281
- const primaryKeys = this.getPrimaryKeys(schema);
1282
- if (primaryKeys.length !== 1) {
1283
- throw new Error("Only single primary key is supported");
1284
- }
1285
- const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
1286
- const versionMetadata = this.validateVersionField(tableName, columns);
1287
- const conditions = [drizzleOrm.eq(primaryKeyColumn, id)];
1288
- if (versionMetadata && columns) {
1289
- const versionField = columns[versionMetadata.fieldName];
1290
- if (versionField) {
1291
- const oldModel = await this.getOldModel({ [primaryKeyName]: id }, schema, [
1292
- versionMetadata.fieldName,
1293
- versionField
1294
- ]);
1295
- conditions.push(drizzleOrm.eq(versionField, oldModel[versionMetadata.fieldName]));
1296
- }
1297
- }
1298
- const queryBuilder = this.forgeOperations.delete(schema).where(drizzleOrm.and(...conditions));
1299
- const result = await queryBuilder;
1300
- if (versionMetadata && result[0].affectedRows === 0) {
1301
- throw new Error(`Optimistic locking failed: record with primary key ${id} has been modified`);
1302
- }
1303
- await saveTableIfInsideCacheContext(schema);
1304
- return result[0].affectedRows;
1305
- }
1306
- /**
1307
- * Updates a record by its primary key with optimistic locking support.
1308
- * If versioning is enabled:
1309
- * - Retrieves the current version
1310
- * - Checks for concurrent modifications
1311
- * - Increments the version on successful update
1312
- *
1313
- * This method automatically handles:
1314
- * - Primary key validation
1315
- * - Version field retrieval and validation
1316
- * - Optimistic locking conflict detection
1317
- * - Version field incrementation
1318
- *
1319
- * @template T - The type of the table schema
1320
- * @param entity - The entity with updated values (must include primary key)
1321
- * @param schema - The entity schema
1322
- * @returns Promise that resolves to the number of affected rows
1323
- * @throws Error if the primary key is not provided
1324
- * @throws Error if optimistic locking check fails
1325
- * @throws Error if multiple primary keys are found
1326
- */
1327
- async updateById(entity, schema) {
1328
- const { tableName, columns } = getTableMetadata(schema);
1329
- const primaryKeys = this.getPrimaryKeys(schema);
1330
- if (primaryKeys.length !== 1) {
1331
- throw new Error("Only single primary key is supported");
1332
- }
1333
- const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
1334
- const versionMetadata = this.validateVersionField(tableName, columns);
1335
- if (!(primaryKeyName in entity)) {
1336
- throw new Error(`Primary key ${primaryKeyName} must be provided in the entity`);
1337
- }
1338
- const currentVersion = await this.getCurrentVersion(
1339
- entity,
1340
- primaryKeyName,
1341
- versionMetadata,
1342
- columns,
1343
- schema
1344
- );
1345
- const updateData = this.prepareUpdateData(entity, versionMetadata, columns, currentVersion);
1346
- const conditions = [
1347
- drizzleOrm.eq(primaryKeyColumn, entity[primaryKeyName])
1348
- ];
1349
- if (versionMetadata && columns) {
1350
- const versionField = columns[versionMetadata.fieldName];
1351
- if (versionField) {
1352
- conditions.push(drizzleOrm.eq(versionField, currentVersion));
1353
- }
1354
- }
1355
- const queryBuilder = this.forgeOperations.update(schema).set(updateData).where(drizzleOrm.and(...conditions));
1356
- const result = await queryBuilder;
1357
- if (versionMetadata && result[0].affectedRows === 0) {
1358
- throw new Error(
1359
- `Optimistic locking failed: record with primary key ${entity[primaryKeyName]} has been modified`
1360
- );
1361
- }
1362
- await saveTableIfInsideCacheContext(schema);
1363
- return result[0].affectedRows;
1364
- }
1365
- /**
1366
- * Updates specified fields of records based on provided conditions.
1367
- * This method does not support versioning and should be used with caution.
1368
- *
1369
- * @template T - The type of the table schema
1370
- * @param {Partial<InferInsertModel<T>>} updateData - The data to update
1371
- * @param {T} schema - The entity schema
1372
- * @param {SQL<unknown>} where - The WHERE conditions
1373
- * @returns {Promise<number>} Number of affected rows
1374
- * @throws {Error} If WHERE conditions are not provided
1375
- * @throws {Error} If the update operation fails
1376
- */
1377
- async updateFields(updateData, schema, where) {
1378
- if (!where) {
1379
- throw new Error("WHERE conditions must be provided");
1380
- }
1381
- const queryBuilder = this.forgeOperations.update(schema).set(updateData).where(where);
1382
- const result = await queryBuilder;
1383
- await saveTableIfInsideCacheContext(schema);
1384
- return result[0].affectedRows;
1385
- }
1386
- // Helper methods
1387
- /**
1388
- * Gets primary keys from the schema.
1389
- * @template T - The type of the table schema
1390
- * @param {T} schema - The table schema
1391
- * @returns {[string, AnyColumn][]} Array of primary key name and column pairs
1392
- * @throws {Error} If no primary keys are found
1393
- */
1394
- getPrimaryKeys(schema) {
1395
- const primaryKeys = getPrimaryKeys(schema);
1396
- if (!primaryKeys) {
1397
- throw new Error(`No primary keys found for schema: ${schema}`);
1398
- }
1399
- return primaryKeys;
1400
- }
1401
- /**
1402
- * Validates and retrieves version field metadata.
1403
- * @param {string} tableName - The name of the table
1404
- * @param {Record<string, AnyColumn>} columns - The table columns
1405
- * @returns {Object | undefined} Version field metadata if valid, undefined otherwise
1406
- */
1407
- validateVersionField(tableName, columns) {
1408
- if (this.options.disableOptimisticLocking) {
1409
- return void 0;
1410
- }
1411
- const versionMetadata = this.options.additionalMetadata?.[tableName]?.versionField;
1412
- if (!versionMetadata) return void 0;
1413
- let fieldName = versionMetadata.fieldName;
1414
- let versionField = columns[versionMetadata.fieldName];
1415
- if (!versionField) {
1416
- const find = Object.entries(columns).find(([, c]) => c.name === versionMetadata.fieldName);
1417
- if (find) {
1418
- fieldName = find[0];
1419
- versionField = find[1];
1420
- }
1421
- }
1422
- if (!versionField) {
1423
- console.warn(
1424
- `Version field "${versionMetadata.fieldName}" not found in table ${tableName}. Versioning will be skipped.`
1425
- );
1426
- return void 0;
1427
- }
1428
- if (!versionField.notNull) {
1429
- console.warn(
1430
- `Version field "${versionMetadata.fieldName}" in table ${tableName} is nullable. Versioning may not work correctly.`
1431
- );
1432
- return void 0;
1433
- }
1434
- const fieldType = versionField.getSQLType();
1435
- const isSupportedType = fieldType === "datetime" || fieldType === "timestamp" || fieldType === "int" || fieldType === "number" || fieldType === "decimal";
1436
- if (!isSupportedType) {
1437
- console.warn(
1438
- `Version field "${versionMetadata.fieldName}" in table ${tableName} has unsupported type "${fieldType}". Only datetime, timestamp, int, and decimal types are supported for versioning. Versioning will be skipped.`
1439
- );
1440
- return void 0;
1441
- }
1442
- return { fieldName, type: fieldType };
1443
- }
1444
- /**
1445
- * Gets the current version of an entity.
1446
- * @template T - The type of the table schema
1447
- * @param {Partial<InferInsertModel<T>>} entity - The entity
1448
- * @param {string} primaryKeyName - The name of the primary key
1449
- * @param {Object | undefined} versionMetadata - Version field metadata
1450
- * @param {Record<string, AnyColumn>} columns - The table columns
1451
- * @param {T} schema - The table schema
1452
- * @returns {Promise<unknown>} The current version value
1453
- */
1454
- async getCurrentVersion(entity, primaryKeyName, versionMetadata, columns, schema) {
1455
- if (!versionMetadata || !columns) return void 0;
1456
- const versionField = columns[versionMetadata.fieldName];
1457
- if (!versionField) return void 0;
1458
- if (versionMetadata.fieldName in entity) {
1459
- return entity[versionMetadata.fieldName];
1460
- }
1461
- const oldModel = await this.getOldModel(
1462
- { [primaryKeyName]: entity[primaryKeyName] },
1463
- schema,
1464
- [versionMetadata.fieldName, versionField]
1465
- );
1466
- return oldModel[versionMetadata.fieldName];
1467
- }
1468
- /**
1469
- * Prepares a model for insertion with version field.
1470
- * @template T - The type of the table schema
1471
- * @param {Partial<InferInsertModel<T>>} model - The model to prepare
1472
- * @param {Object | undefined} versionMetadata - Version field metadata
1473
- * @param {Record<string, AnyColumn>} columns - The table columns
1474
- * @returns {InferInsertModel<T>} The prepared model
1475
- */
1476
- prepareModelWithVersion(model, versionMetadata, columns) {
1477
- if (!versionMetadata || !columns) return model;
1478
- let fieldName = versionMetadata.fieldName;
1479
- let versionField = columns[versionMetadata.fieldName];
1480
- if (!versionField) {
1481
- const find = Object.entries(columns).find(([, c]) => c.name === versionMetadata.fieldName);
1482
- if (find) {
1483
- fieldName = find[0];
1484
- versionField = find[1];
1485
- }
1486
- }
1487
- if (!versionField) return model;
1488
- const modelWithVersion = { ...model };
1489
- const fieldType = versionField.getSQLType();
1490
- const versionValue = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : 1;
1491
- modelWithVersion[fieldName] = versionValue;
1492
- return modelWithVersion;
1493
- }
1494
- /**
1495
- * Prepares update data with version field.
1496
- * @template T - The type of the table schema
1497
- * @param {Partial<InferInsertModel<T>>} entity - The entity to update
1498
- * @param {Object | undefined} versionMetadata - Version field metadata
1499
- * @param {Record<string, AnyColumn>} columns - The table columns
1500
- * @param {unknown} currentVersion - The current version value
1501
- * @returns {Partial<InferInsertModel<T>>} The prepared update data
1502
- */
1503
- prepareUpdateData(entity, versionMetadata, columns, currentVersion) {
1504
- const updateData = { ...entity };
1505
- if (versionMetadata && columns) {
1506
- const versionField = columns[versionMetadata.fieldName];
1507
- if (versionField) {
1508
- const fieldType = versionField.getSQLType();
1509
- updateData[versionMetadata.fieldName] = fieldType === "datetime" || fieldType === "timestamp" ? /* @__PURE__ */ new Date() : currentVersion + 1;
1510
- }
1511
- }
1512
- return updateData;
1513
- }
1514
- /**
1515
- * Retrieves an existing model by primary key.
1516
- * @template T - The type of the table schema
1517
- * @param {Record<string, unknown>} primaryKeyValues - The primary key values
1518
- * @param {T} schema - The table schema
1519
- * @param {[string, AnyColumn]} versionField - The version field name and column
1520
- * @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} The existing model
1521
- * @throws {Error} If the record is not found
1522
- */
1523
- async getOldModel(primaryKeyValues, schema, versionField) {
1524
- const [versionFieldName, versionFieldColumn] = versionField;
1525
- const primaryKeys = this.getPrimaryKeys(schema);
1526
- const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
1527
- const resultQuery = this.forgeOperations.select({
1528
- [primaryKeyName]: primaryKeyColumn,
1529
- [versionFieldName]: versionFieldColumn
1530
- }).from(schema).where(drizzleOrm.eq(primaryKeyColumn, primaryKeyValues[primaryKeyName]));
1531
- const model = await this.forgeOperations.fetch().executeQueryOnlyOne(resultQuery);
1532
- if (!model) {
1533
- throw new Error(`Record not found in table ${schema}`);
1534
- }
1535
- return model;
1536
- }
1537
- }
1538
- class ForgeSQLSelectOperations {
1539
- options;
1540
- /**
1541
- * Creates a new instance of ForgeSQLSelectOperations.
1542
- * @param {ForgeSqlOrmOptions} options - Configuration options for the ORM
1543
- */
1544
- constructor(options) {
1545
- this.options = options;
1546
- }
1547
- /**
1548
- * Executes a Drizzle query and returns a single result.
1549
- * Throws an error if more than one record is returned.
1550
- *
1551
- * @template T - The type of the query builder
1552
- * @param {T} query - The Drizzle query to execute
1553
- * @returns {Promise<Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined>} A single result object or undefined
1554
- * @throws {Error} If more than one record is returned
1555
- */
1556
- async executeQueryOnlyOne(query) {
1557
- const results = await query;
1558
- const datas = results;
1559
- if (!datas.length) {
1560
- return void 0;
1561
- }
1562
- if (datas.length > 1) {
1563
- throw new Error(`Expected 1 record but returned ${datas.length}`);
1564
- }
1565
- return datas[0];
1566
- }
1567
- /**
1568
- * Executes a raw SQL query and returns the results.
1569
- * Logs the query if logging is enabled.
1570
- *
1571
- * @template T - The type of the result objects
1572
- * @param {string} query - The raw SQL query to execute
1573
- * @param {SqlParameters[]} [params] - Optional SQL parameters
1574
- * @returns {Promise<T[]>} A list of results as objects
1575
- */
1576
- async executeRawSQL(query, params) {
1577
- if (this.options.logRawSqlQuery) {
1578
- const paramsStr = params ? `, with params: ${JSON.stringify(params)}` : "";
1579
- console.debug(`Executing with SQL ${query}${paramsStr}`);
1580
- }
1581
- const sqlStatement = sql.sql.prepare(query);
1582
- if (params) {
1583
- sqlStatement.bindParams(...params);
1584
- }
1585
- const result = await sqlStatement.execute();
1586
- return result.rows;
1587
- }
1588
- /**
1589
- * Executes a raw SQL update query.
1590
- * @param {string} query - The raw SQL update query
1591
- * @param {SqlParameters[]} [params] - Optional SQL parameters
1592
- * @returns {Promise<UpdateQueryResponse>} The update response containing affected rows
1593
- */
1594
- async executeRawUpdateSQL(query, params) {
1595
- const sqlStatement = sql.sql.prepare(query);
1596
- if (params) {
1597
- sqlStatement.bindParams(...params);
1598
- }
1599
- if (this.options.logRawSqlQuery) {
1600
- console.debug(
1601
- `Executing Update with SQL ${query}` + (params ? `, with params: ${JSON.stringify(params)}` : "")
1602
- );
1603
- }
1604
- const updateQueryResponseResults = await sqlStatement.execute();
1605
- return updateQueryResponseResults.rows;
1606
- }
1607
- }
1608
- const metadataQueryContext = new node_async_hooks.AsyncLocalStorage();
1609
- async function saveMetaDataToContext(metadata) {
1610
- const context = metadataQueryContext.getStore();
1611
- if (context) {
1612
- context.printQueriesWithPlan = async () => {
1613
- if (process.env.NODE_ENV !== "test") {
1614
- await new Promise((r) => setTimeout(r, 200));
1615
- }
1616
- await printQueriesWithPlan(
1617
- context.forgeSQLORM,
1618
- Date.now() - context.beginTime.getTime()
1619
- );
1620
- };
1621
- if (metadata) {
1622
- context.totalResponseSize += metadata.responseSize;
1623
- context.totalDbExecutionTime += metadata.dbExecutionTime;
1624
- }
1625
- }
1626
- }
1627
- async function getLastestMetadata() {
1628
- return metadataQueryContext.getStore();
1629
- }
1630
- const operationTypeQueryContext = new node_async_hooks.AsyncLocalStorage();
1631
- async function getOperationType() {
1632
- return operationTypeQueryContext.getStore()?.operationType ?? "DML";
1633
- }
1634
- const timeoutMs = 1e4;
1635
- const timeoutMessage = `Atlassian @forge/sql did not return a response within ${timeoutMs}ms (${timeoutMs / 1e3} seconds), so the request is blocked. Possible causes: slow query, network issues, or exceeding Forge SQL limits.`;
1636
- function isUpdateQueryResponse(obj) {
1637
- return obj !== null && typeof obj === "object" && typeof obj.affectedRows === "number" && typeof obj.insertId === "number";
1638
- }
1639
- function inlineParams(sql2, params) {
1640
- let i = 0;
1641
- return sql2.replace(/\?/g, () => {
1642
- const val = params[i++];
1643
- if (val === null) return "NULL";
1644
- if (typeof val === "number") return val.toString();
1645
- return `'${String(val).replace(/'/g, "''")}'`;
1646
- });
1647
- }
1648
- async function processDDLResult(method, result) {
1649
- if (result.metadata) {
1650
- await saveMetaDataToContext(result.metadata);
1651
- }
1652
- if (!result?.rows) {
1653
- return { rows: [] };
1654
- }
1655
- if (isUpdateQueryResponse(result.rows)) {
1656
- const oneRow = result.rows;
1657
- return { ...oneRow, rows: [oneRow] };
1658
- }
1659
- if (Array.isArray(result.rows)) {
1660
- if (method === "execute") {
1661
- return { rows: [result.rows] };
1662
- } else {
1663
- const rows = result.rows.map((r) => Object.values(r));
1664
- return { rows };
1665
- }
1666
- }
1667
- return { rows: [] };
1668
- }
1669
- async function processExecuteMethod(query, params) {
1670
- const sqlStatement = sql.sql.prepare(query);
1671
- if (params) {
1672
- sqlStatement.bindParams(...params);
1673
- }
1674
- const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
1675
- await saveMetaDataToContext(result.metadata);
1676
- if (!result.rows) {
1677
- return { rows: [[]] };
1678
- }
1679
- return { rows: [result.rows] };
1680
- }
1681
- async function processAllMethod(query, params) {
1682
- const sqlStatement = await sql.sql.prepare(query);
1683
- if (params) {
1684
- await sqlStatement.bindParams(...params);
1685
- }
1686
- const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
1687
- await saveMetaDataToContext(result.metadata);
1688
- if (!result.rows) {
1689
- return { rows: [] };
1690
- }
1691
- const rows = result.rows.map((r) => Object.values(r));
1692
- return { rows };
1693
- }
1694
- const forgeDriver = async (query, params, method) => {
1695
- const operationType = await getOperationType();
1696
- if (operationType === "DDL") {
1697
- const result = await withTimeout(sql.sql.executeDDL(inlineParams(query, params)), timeoutMessage, timeoutMs);
1698
- return await processDDLResult(method, result);
1699
- }
1700
- if (method === "execute") {
1701
- return await processExecuteMethod(query, params ?? []);
1702
- }
1703
- return await processAllMethod(query, params ?? []);
1704
- };
1705
- function injectSqlHints(query, hints) {
1706
- if (!hints) {
1707
- return query;
1708
- }
1709
- const normalizedQuery = query.trim().toUpperCase();
1710
- let queryHints;
1711
- if (normalizedQuery.startsWith("SELECT")) {
1712
- queryHints = hints.select;
1713
- } else if (normalizedQuery.startsWith("INSERT")) {
1714
- queryHints = hints.insert;
1715
- } else if (normalizedQuery.startsWith("UPDATE")) {
1716
- queryHints = hints.update;
1717
- } else if (normalizedQuery.startsWith("DELETE")) {
1718
- queryHints = hints.delete;
1719
- }
1720
- if (!queryHints || queryHints.length === 0) {
1721
- return query;
1722
- }
1723
- const hintsString = queryHints.join(" ");
1724
- if (normalizedQuery.startsWith("SELECT")) {
1725
- return `SELECT /*+ ${hintsString} */ ${query.substring(6)}`;
1726
- } else if (normalizedQuery.startsWith("INSERT")) {
1727
- return `INSERT /*+ ${hintsString} */ ${query.substring(6)}`;
1728
- } else if (normalizedQuery.startsWith("UPDATE")) {
1729
- return `UPDATE /*+ ${hintsString} */ ${query.substring(6)}`;
1730
- } else if (normalizedQuery.startsWith("DELETE")) {
1731
- return `DELETE /*+ ${hintsString} */ ${query.substring(6)}`;
1732
- }
1733
- return query;
1734
- }
1735
- const QUERY_ERROR_CODES = {
1736
- TIMEOUT: "SQL_QUERY_TIMEOUT",
1737
- OUT_OF_MEMORY_ERRNO: 8175
1738
- };
1739
- const STATEMENTS_SUMMARY_DELAY_MS = 200;
1740
- function createForgeDriverProxy(forgeSqlOperation, options, logRawSqlQuery) {
1741
- return async (query, params, method) => {
1742
- const modifiedQuery = injectSqlHints(query, options);
1743
- if (options && logRawSqlQuery && modifiedQuery !== query) {
1744
- console.debug(`SQL Hints injected: ${modifiedQuery}`);
1745
- }
1746
- const queryStartTime = Date.now();
1747
- try {
1748
- return await forgeDriver(modifiedQuery, params, method);
1749
- } catch (error) {
1750
- const isTimeoutError = error.code === QUERY_ERROR_CODES.TIMEOUT;
1751
- const isOutOfMemoryError = error?.context?.debug?.errno === QUERY_ERROR_CODES.OUT_OF_MEMORY_ERRNO;
1752
- if (isTimeoutError || isOutOfMemoryError) {
1753
- if (isTimeoutError) {
1754
- console.error(` TIMEOUT detected - Query exceeded time limit`);
1755
- } else {
1756
- console.error(`OUT OF MEMORY detected - Query exceeded memory limit`);
1757
- }
1758
- await new Promise((resolve) => setTimeout(resolve, STATEMENTS_SUMMARY_DELAY_MS));
1759
- const queryEndTime = Date.now();
1760
- const queryDuration = queryEndTime - queryStartTime;
1761
- await printQueriesWithPlan(forgeSqlOperation, queryDuration);
1762
- }
1763
- if (logRawSqlQuery) {
1764
- console.debug(`SQL Error Details:`, JSON.stringify(error, null, 2));
1765
- }
1766
- throw error;
1767
- }
1768
- };
1769
- }
1770
- const NON_CACHE_CLEARING_ERROR_CODES = ["VALIDATION_ERROR", "CONSTRAINT_ERROR"];
1771
- const CACHE_CLEARING_ERROR_CODES = ["DEADLOCK", "LOCK_WAIT_TIMEOUT", "CONNECTION_ERROR"];
1772
- const NON_CACHE_CLEARING_PATTERNS = [/validation/i, /constraint/i];
1773
- const CACHE_CLEARING_PATTERNS = [/timeout/i, /connection/i];
1774
- function shouldClearCacheOnError(error) {
1775
- if (error?.code && NON_CACHE_CLEARING_ERROR_CODES.includes(error.code)) {
1776
- return false;
1777
- }
1778
- if (error?.message && NON_CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message))) {
1779
- return false;
1780
- }
1781
- if (error?.code && CACHE_CLEARING_ERROR_CODES.includes(error.code)) {
1782
- return true;
1783
- }
1784
- if (error?.message && CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message))) {
1785
- return true;
1786
- }
1787
- return true;
1788
- }
1789
- async function handleSuccessfulExecution(rows, onfulfilled, table2, options, isCached) {
1790
- try {
1791
- await evictLocalCacheQuery(table2, options);
1792
- await saveTableIfInsideCacheContext(table2);
1793
- if (isCached && !cacheApplicationContext.getStore()) {
1794
- await clearCache(table2, options);
1795
- }
1796
- const result = onfulfilled ? onfulfilled(rows) : rows;
1797
- return result;
1798
- } catch (error) {
1799
- if (shouldClearCacheOnError(error)) {
1800
- await evictLocalCacheQuery(table2, options);
1801
- if (isCached) {
1802
- await clearCache(table2, options).catch((e) => {
1803
- console.warn("Ignore cache clear errors", e);
1804
- });
1805
- } else {
1806
- await saveTableIfInsideCacheContext(table2);
1807
- }
1808
- }
1809
- throw error;
1810
- }
1811
- }
1812
- function handleFunctionCall(value, target, args, table2, options, isCached) {
1813
- const result = value.apply(target, args);
1814
- if (typeof result === "object" && result !== null && "execute" in result) {
1815
- return wrapCacheEvictBuilder(result, table2, options, isCached);
1816
- }
1817
- return result;
1818
- }
1819
- const wrapCacheEvictBuilder = (rawBuilder, table2, options, isCached) => {
1820
- return new Proxy(rawBuilder, {
1821
- get(target, prop, receiver) {
1822
- if (prop === "then") {
1823
- return (onfulfilled, onrejected) => target.execute().then(
1824
- (rows) => handleSuccessfulExecution(rows, onfulfilled, table2, options, isCached),
1825
- onrejected
1826
- );
1827
- }
1828
- const value = Reflect.get(target, prop, receiver);
1829
- if (typeof value === "function") {
1830
- return (...args) => handleFunctionCall(value, target, args, table2, options, isCached);
1831
- }
1832
- return value;
1833
- }
1834
- });
1835
- };
1836
- function insertAndEvictCacheBuilder(db, table2, options, isCached) {
1837
- const builder = db.insert(table2);
1838
- return wrapCacheEvictBuilder(
1839
- builder,
1840
- table2,
1841
- options,
1842
- isCached
1843
- );
1844
- }
1845
- function updateAndEvictCacheBuilder(db, table2, options, isCached) {
1846
- const builder = db.update(table2);
1847
- return wrapCacheEvictBuilder(
1848
- builder,
1849
- table2,
1850
- options,
1851
- isCached
1852
- );
1853
- }
1854
- function deleteAndEvictCacheBuilder(db, table2, options, isCached) {
1855
- const builder = db.delete(table2);
1856
- return wrapCacheEvictBuilder(
1857
- builder,
1858
- table2,
1859
- options,
1860
- isCached
1861
- );
1862
- }
1863
- async function handleCachedQuery(target, options, cacheTtl, selections, aliasMap, onfulfilled, onrejected) {
1864
- try {
1865
- const localCached = await getQueryLocalCacheQuery(target, options);
1866
- if (localCached) {
1867
- return onfulfilled ? onfulfilled(localCached) : localCached;
1868
- }
1869
- const cacheResult = await getFromCache(target, options);
1870
- if (cacheResult) {
1871
- return onfulfilled ? onfulfilled(cacheResult) : cacheResult;
1872
- }
1873
- const rows = await target.execute();
1874
- const transformed = applyFromDriverTransform(rows, selections, aliasMap);
1875
- await saveQueryLocalCacheQuery(target, transformed, options);
1876
- await setCacheResult(target, options, transformed, cacheTtl).catch((cacheError) => {
1877
- console.warn("Cache set error:", cacheError);
1878
- });
1879
- return onfulfilled ? onfulfilled(transformed) : transformed;
1880
- } catch (error) {
1881
- if (onrejected) {
1882
- return onrejected(error);
1883
- }
1884
- throw error;
1885
- }
1886
- }
1887
- async function handleNonCachedQuery(target, options, selections, aliasMap, onfulfilled, onrejected) {
1888
- try {
1889
- const localCached = await getQueryLocalCacheQuery(target, options);
1890
- if (localCached) {
1891
- return onfulfilled ? onfulfilled(localCached) : localCached;
1892
- }
1893
- const rows = await target.execute();
1894
- const transformed = applyFromDriverTransform(rows, selections, aliasMap);
1895
- await saveQueryLocalCacheQuery(target, transformed, options);
1896
- return onfulfilled ? onfulfilled(transformed) : transformed;
1897
- } catch (error) {
1898
- if (onrejected) {
1899
- return onrejected(error);
1900
- }
1901
- throw error;
1902
- }
1903
- }
1904
- function createAliasedSelectBuilder(db, fields, selectFn, useCache, options, cacheTtl) {
1905
- const { selections, aliasMap } = mapSelectFieldsWithAlias(fields);
1906
- const builder = selectFn(selections);
1907
- const wrapBuilder = (rawBuilder) => {
1908
- return new Proxy(rawBuilder, {
1909
- get(target, prop, receiver) {
1910
- if (prop === "execute") {
1911
- return async (...args) => {
1912
- const rows = await target.execute(...args);
1913
- return applyFromDriverTransform(rows, selections, aliasMap);
1914
- };
1915
- }
1916
- if (prop === "then") {
1917
- return (onfulfilled, onrejected) => {
1918
- if (useCache) {
1919
- const ttl = cacheTtl ?? options.cacheTTL ?? 120;
1920
- return handleCachedQuery(
1921
- target,
1922
- options,
1923
- ttl,
1924
- selections,
1925
- aliasMap,
1926
- onfulfilled,
1927
- onrejected
1928
- );
1929
- } else {
1930
- return handleNonCachedQuery(
1931
- target,
1932
- options,
1933
- selections,
1934
- aliasMap,
1935
- onfulfilled,
1936
- onrejected
1937
- );
1938
- }
1939
- };
1940
- }
1941
- const value = Reflect.get(target, prop, receiver);
1942
- if (typeof value === "function") {
1943
- return (...args) => {
1944
- const result = value.apply(target, args);
1945
- if (typeof result === "object" && result !== null && "execute" in result) {
1946
- return wrapBuilder(result);
1947
- }
1948
- return result;
1949
- };
1950
- }
1951
- return value;
1952
- }
1953
- });
1954
- };
1955
- return wrapBuilder(builder);
1956
- }
1957
- const DEFAULT_OPTIONS = {
1958
- logRawSqlQuery: false,
1959
- disableOptimisticLocking: false,
1960
- cacheTTL: 120,
1961
- cacheWrapTable: true,
1962
- cacheEntityQueryName: "sql",
1963
- cacheEntityExpirationName: "expiration",
1964
- cacheEntityDataName: "data"
1965
- };
1966
- function createRawQueryExecutor(db, options, useGlobalCache = false) {
1967
- return async function(query, cacheTtl) {
1968
- let sql2;
1969
- if (sql$1.isSQLWrapper(query)) {
1970
- const dialect = db.dialect;
1971
- sql2 = dialect.sqlToQuery(query);
1972
- } else {
1973
- sql2 = {
1974
- sql: query,
1975
- params: []
1976
- };
1977
- }
1978
- const localCacheResult = await getQueryLocalCacheQuery(sql2, options);
1979
- if (localCacheResult) {
1980
- return localCacheResult;
1981
- }
1982
- if (useGlobalCache) {
1983
- const cacheResult = await getFromCache({ toSQL: () => sql2 }, options);
1984
- if (cacheResult) {
1985
- return cacheResult;
1986
- }
1987
- }
1988
- const results = await db.execute(query);
1989
- await saveQueryLocalCacheQuery(sql2, results, options);
1990
- if (useGlobalCache) {
1991
- await setCacheResult(
1992
- { toSQL: () => sql2 },
1993
- options,
1994
- results,
1995
- cacheTtl ?? options.cacheTTL ?? 120
1996
- );
1997
- }
1998
- return results;
1999
- };
2000
- }
2001
- function patchDbWithSelectAliased(db, options) {
2002
- const newOptions = { ...DEFAULT_OPTIONS, ...options };
2003
- db.selectAliased = function(fields) {
2004
- return createAliasedSelectBuilder(
2005
- db,
2006
- fields,
2007
- (selections) => db.select(selections),
2008
- false,
2009
- newOptions
2010
- );
2011
- };
2012
- db.selectAliasedCacheable = function(fields, cacheTtl) {
2013
- return createAliasedSelectBuilder(
2014
- db,
2015
- fields,
2016
- (selections) => db.select(selections),
2017
- true,
2018
- newOptions,
2019
- cacheTtl
2020
- );
2021
- };
2022
- db.selectAliasedDistinct = function(fields) {
2023
- return createAliasedSelectBuilder(
2024
- db,
2025
- fields,
2026
- (selections) => db.selectDistinct(selections),
2027
- false,
2028
- newOptions
2029
- );
2030
- };
2031
- db.selectAliasedDistinctCacheable = function(fields, cacheTtl) {
2032
- return createAliasedSelectBuilder(
2033
- db,
2034
- fields,
2035
- (selections) => db.selectDistinct(selections),
2036
- true,
2037
- newOptions,
2038
- cacheTtl
2039
- );
2040
- };
2041
- db.selectFrom = function(table2) {
2042
- return db.selectAliased(drizzleOrm.getTableColumns(table2)).from(table2);
2043
- };
2044
- db.selectFromCacheable = function(table2, cacheTtl) {
2045
- return db.selectAliasedCacheable(drizzleOrm.getTableColumns(table2), cacheTtl).from(table2);
2046
- };
2047
- db.selectDistinctFrom = function(table2) {
2048
- return db.selectAliasedDistinct(drizzleOrm.getTableColumns(table2)).from(table2);
2049
- };
2050
- db.selectDistinctFromCacheable = function(table2, cacheTtl) {
2051
- return db.selectAliasedDistinctCacheable(drizzleOrm.getTableColumns(table2), cacheTtl).from(table2);
2052
- };
2053
- db.insertWithCacheContext = function(table2) {
2054
- return insertAndEvictCacheBuilder(db, table2, newOptions, false);
2055
- };
2056
- db.insertAndEvictCache = function(table2) {
2057
- return insertAndEvictCacheBuilder(db, table2, newOptions, true);
2058
- };
2059
- db.updateWithCacheContext = function(table2) {
2060
- return updateAndEvictCacheBuilder(db, table2, newOptions, false);
2061
- };
2062
- db.updateAndEvictCache = function(table2) {
2063
- return updateAndEvictCacheBuilder(db, table2, newOptions, true);
2064
- };
2065
- db.deleteWithCacheContext = function(table2) {
2066
- return deleteAndEvictCacheBuilder(db, table2, newOptions, false);
2067
- };
2068
- db.deleteAndEvictCache = function(table2) {
2069
- return deleteAndEvictCacheBuilder(db, table2, newOptions, true);
2070
- };
2071
- db.executeQuery = createRawQueryExecutor(db, newOptions, false);
2072
- db.executeQueryCacheable = createRawQueryExecutor(db, newOptions, true);
2073
- return db;
2074
- }
2075
- class ForgeSQLAnalyseOperation {
2076
- forgeOperations;
2077
- /**
2078
- * Creates a new instance of ForgeSQLAnalizeOperation.
2079
- * @param {ForgeSqlOperation} forgeOperations - The ForgeSQL operations instance
2080
- */
2081
- constructor(forgeOperations) {
2082
- this.forgeOperations = forgeOperations;
2083
- this.mapToCamelCaseClusterStatement = this.mapToCamelCaseClusterStatement.bind(this);
2084
- }
2085
- /**
2086
- * Executes EXPLAIN on a raw SQL query.
2087
- * @param {string} query - The SQL query to analyze
2088
- * @param {unknown[]} bindParams - The query parameters
2089
- * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
2090
- */
2091
- async explainRaw(query, bindParams) {
2092
- const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ${query}`, bindParams);
2093
- return results.map((row) => ({
2094
- id: row.id,
2095
- estRows: row.estRows,
2096
- actRows: row.actRows,
2097
- task: row.task,
2098
- accessObject: row["access object"],
2099
- executionInfo: row["execution info"],
2100
- operatorInfo: row["operator info"],
2101
- memory: row.memory,
2102
- disk: row.disk
2103
- }));
2104
- }
2105
- /**
2106
- * Executes EXPLAIN on a Drizzle query.
2107
- * @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
2108
- * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
2109
- */
2110
- async explain(query) {
2111
- const { sql: sql2, params } = query.toSQL();
2112
- return this.explainRaw(sql2, params);
2113
- }
2114
- /**
2115
- * Executes EXPLAIN ANALYZE on a raw SQL query.
2116
- * @param {string} query - The SQL query to analyze
2117
- * @param {unknown[]} bindParams - The query parameters
2118
- * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
2119
- */
2120
- async explainAnalyzeRaw(query, bindParams) {
2121
- const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ANALYZE ${query}`, bindParams);
2122
- return results.map((row) => ({
2123
- id: row.id,
2124
- estRows: row.estRows,
2125
- actRows: row.actRows,
2126
- task: row.task,
2127
- accessObject: row["access object"],
2128
- executionInfo: row["execution info"],
2129
- operatorInfo: row["operator info"],
2130
- memory: row.memory,
2131
- disk: row.disk
2132
- }));
2133
- }
2134
- /**
2135
- * Executes EXPLAIN ANALYZE on a Drizzle query.
2136
- * @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
2137
- * @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
2138
- */
2139
- async explainAnalyze(query) {
2140
- const { sql: sql2, params } = query.toSQL();
2141
- return this.explainAnalyzeRaw(sql2, params);
2142
- }
2143
- /**
2144
- * Decodes a query execution plan from its string representation.
2145
- * @param {string} input - The raw execution plan string
2146
- * @returns {ExplainAnalyzeRow[]} The decoded execution plan rows
2147
- */
2148
- decodedPlan(input) {
2149
- if (!input) {
2150
- return [];
2151
- }
2152
- const lines = input.trim().split("\n");
2153
- if (lines.length < 2) return [];
2154
- const headersRaw = lines[0].split(" ").map((h) => h.trim()).filter(Boolean);
2155
- const headers = headersRaw.map((h) => {
2156
- return h.replace(/\s+/g, " ").replace(/[-\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (s) => s.toLowerCase());
2157
- });
2158
- return lines.slice(1).map((line) => {
2159
- const values = line.split(" ").map((s) => s.trim()).filter(Boolean);
2160
- const row = {};
2161
- headers.forEach((key, i) => {
2162
- row[key] = values[i] ?? "";
2163
- });
2164
- return row;
2165
- });
2166
- }
2167
- /**
2168
- * Normalizes a raw slow query row into a more structured format.
2169
- * @param {SlowQueryRaw} row - The raw slow query data
2170
- * @returns {SlowQueryNormalized} The normalized slow query data
2171
- */
2172
- normalizeSlowQuery(row) {
2173
- return {
2174
- time: row.Time,
2175
- txnStartTs: row.Txn_start_ts,
2176
- user: row.User,
2177
- host: row.Host,
2178
- connId: row.Conn_ID,
2179
- db: row.DB,
2180
- query: row.Query,
2181
- digest: row.Digest,
2182
- queryTime: row.Query_time,
2183
- compileTime: row.Compile_time,
2184
- optimizeTime: row.Optimize_time,
2185
- processTime: row.Process_time,
2186
- waitTime: row.Wait_time,
2187
- parseTime: row.Parse_time,
2188
- rewriteTime: row.Rewrite_time,
2189
- copTime: row.Cop_time,
2190
- copProcAvg: row.Cop_proc_avg,
2191
- copProcMax: row.Cop_proc_max,
2192
- copProcP90: row.Cop_proc_p90,
2193
- copProcAddr: row.Cop_proc_addr,
2194
- copWaitAvg: row.Cop_wait_avg,
2195
- copWaitMax: row.Cop_wait_max,
2196
- copWaitP90: row.Cop_wait_p90,
2197
- copWaitAddr: row.Cop_wait_addr,
2198
- memMax: row.Mem_max,
2199
- diskMax: row.Disk_max,
2200
- totalKeys: row.Total_keys,
2201
- processKeys: row.Process_keys,
2202
- requestCount: row.Request_count,
2203
- kvTotal: row.KV_total,
2204
- pdTotal: row.PD_total,
2205
- resultRows: row.Result_rows,
2206
- rocksdbBlockCacheHitCount: row.Rocksdb_block_cache_hit_count,
2207
- rocksdbBlockReadCount: row.Rocksdb_block_read_count,
2208
- rocksdbBlockReadByte: row.Rocksdb_block_read_byte,
2209
- plan: row.Plan,
2210
- binaryPlan: row.Binary_plan,
2211
- planDigest: row.Plan_digest,
2212
- parsedPlan: this.decodedPlan(row.Plan)
2213
- };
2214
- }
2215
- /**
2216
- * Builds a SQL query for retrieving cluster statement history.
2217
- * @param {string[]} tables - The tables to analyze
2218
- * @param {Date} [from] - The start date for the analysis
2219
- * @param {Date} [to] - The end date for the analysis
2220
- * @returns {string} The SQL query for cluster statement history
2221
- */
2222
- buildClusterStatementQuery(tables, from, to) {
2223
- const formatDateTime2 = (date) => luxon.DateTime.fromJSDate(date).toFormat("yyyy-LL-dd'T'HH:mm:ss.SSS");
2224
- const tableConditions = tables.map((table2) => `TABLE_NAMES LIKE CONCAT(SCHEMA_NAME, '.', '%', '${table2}', '%')`).join(" OR ");
2225
- const timeConditions = [];
2226
- if (from) {
2227
- timeConditions.push(`SUMMARY_BEGIN_TIME >= '${formatDateTime2(from)}'`);
2228
- }
2229
- if (to) {
2230
- timeConditions.push(`SUMMARY_END_TIME <= '${formatDateTime2(to)}'`);
2231
- }
2232
- let whereClauses;
2233
- if (tableConditions?.length) {
2234
- whereClauses = [tableConditions ? `(${tableConditions})` : "", ...timeConditions];
2235
- } else {
2236
- whereClauses = timeConditions;
2237
- }
2238
- return `
2239
- SELECT *
2240
- FROM (
2241
- SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY
2242
- UNION ALL
2243
- SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
2244
- ) AS combined
2245
- ${whereClauses?.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""}
2246
- `;
2247
- }
2248
- /**
2249
- * Retrieves and analyzes slow queries from the database.
2250
- * @returns {Promise<SlowQueryNormalized[]>} The normalized slow query data
2251
- */
2252
- // CLUSTER_SLOW_QUERY STATISTICS
2253
- async analyzeSlowQueries() {
2254
- const results = await this.forgeOperations.fetch().executeRawSQL(`
2255
- SELECT *
2256
- FROM information_schema.slow_query
2257
- ORDER BY time DESC
2258
- `);
2259
- return results.map((row) => this.normalizeSlowQuery(row));
2260
- }
2261
- /**
2262
- * Converts a cluster statement row to camelCase format.
2263
- * @param {Record<string, any>} input - The input row data
2264
- * @returns {ClusterStatementRowCamelCase} The converted row data
2265
- */
2266
- mapToCamelCaseClusterStatement(input) {
2267
- if (!input) {
2268
- return {};
2269
- }
2270
- const result = {};
2271
- result.parsedPlan = this.decodedPlan(input["PLAN"] ?? "");
2272
- for (const key in input) {
2273
- const camelKey = key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
2274
- result[camelKey] = input[key];
2275
- }
2276
- return result;
2277
- }
2278
- /**
2279
- * Analyzes query history for specific tables using raw table names.
2280
- * @param {string[]} tables - The table names to analyze
2281
- * @param {Date} [fromDate] - The start date for the analysis
2282
- * @param {Date} [toDate] - The end date for the analysis
2283
- * @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
2284
- */
2285
- async analyzeQueriesHistoryRaw(tables, fromDate, toDate) {
2286
- const results = await this.forgeOperations.fetch().executeRawSQL(
2287
- this.buildClusterStatementQuery(tables ?? [], fromDate, toDate)
2288
- );
2289
- return results.map((r) => this.mapToCamelCaseClusterStatement(r));
2290
- }
2291
- /**
2292
- * Analyzes query history for specific tables using Drizzle table objects.
2293
- * @param {AnyMySqlTable[]} tables - The Drizzle table objects to analyze
2294
- * @param {Date} [fromDate] - The start date for the analysis
2295
- * @param {Date} [toDate] - The end date for the analysis
2296
- * @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
2297
- */
2298
- async analyzeQueriesHistory(tables, fromDate, toDate) {
2299
- const tableNames = tables?.map((table$1) => table.getTableName(table$1)) ?? [];
2300
- return this.analyzeQueriesHistoryRaw(tableNames, fromDate, toDate);
2301
- }
2302
- }
2303
- class ForgeSQLCacheOperations {
2304
- options;
2305
- forgeOperations;
2306
- /**
2307
- * Creates a new instance of ForgeSQLCacheOperations.
2308
- *
2309
- * @param options - Configuration options for the ORM
2310
- * @param forgeOperations - The ForgeSQL operations instance
2311
- */
2312
- constructor(options, forgeOperations) {
2313
- this.options = options;
2314
- this.forgeOperations = forgeOperations;
2315
- }
2316
- /**
2317
- * Evicts cache for multiple tables using Drizzle table objects.
2318
- *
2319
- * @param tables - Array of Drizzle table objects to clear cache for
2320
- * @returns Promise that resolves when cache eviction is complete
2321
- * @throws Error if cacheEntityName is not configured
2322
- */
2323
- async evictCacheEntities(tables) {
2324
- if (!this.options.cacheEntityName) {
2325
- throw new Error("cacheEntityName is not configured");
2326
- }
2327
- await this.evictCache(tables.map((t) => table.getTableName(t)));
2328
- }
2329
- /**
2330
- * Evicts cache for multiple tables by their names.
2331
- *
2332
- * @param tables - Array of table names to clear cache for
2333
- * @returns Promise that resolves when cache eviction is complete
2334
- * @throws Error if cacheEntityName is not configured
2335
- */
2336
- async evictCache(tables) {
2337
- if (!this.options.cacheEntityName) {
2338
- throw new Error("cacheEntityName is not configured");
2339
- }
2340
- await clearTablesCache(tables, this.options);
2341
- }
2342
- /**
2343
- * Inserts records with optimistic locking/versioning and automatically evicts cache.
2344
- *
2345
- * This method uses `modifyWithVersioning().insert()` internally, providing:
2346
- * - Automatic version field initialization
2347
- * - Optimistic locking support
2348
- * - Cache eviction after successful operation
2349
- *
2350
- * @param schema - The table schema
2351
- * @param models - Array of entities to insert
2352
- * @param updateIfExists - Whether to update existing records
2353
- * @returns Promise that resolves to the number of inserted rows
2354
- * @throws Error if cacheEntityName is not configured
2355
- * @throws Error if optimistic locking check fails
2356
- */
2357
- async insert(schema, models, updateIfExists) {
2358
- this.validateCacheConfiguration();
2359
- const number = await this.forgeOperations.modifyWithVersioning().insert(schema, models, updateIfExists);
2360
- await clearCache(schema, this.options);
2361
- return number;
2362
- }
2363
- /**
2364
- * Deletes a record by ID with optimistic locking/versioning and automatically evicts cache.
2365
- *
2366
- * This method uses `modifyWithVersioning().deleteById()` internally, providing:
2367
- * - Optimistic locking checks before deletion
2368
- * - Version field validation
2369
- * - Cache eviction after successful operation
2370
- *
2371
- * @param id - The ID of the record to delete
2372
- * @param schema - The table schema
2373
- * @returns Promise that resolves to the number of affected rows
2374
- * @throws Error if cacheEntityName is not configured
2375
- * @throws Error if optimistic locking check fails
2376
- */
2377
- async deleteById(id, schema) {
2378
- this.validateCacheConfiguration();
2379
- const number = await this.forgeOperations.modifyWithVersioning().deleteById(id, schema);
2380
- await clearCache(schema, this.options);
2381
- return number;
2382
- }
2383
- /**
2384
- * Updates a record by ID with optimistic locking/versioning and automatically evicts cache.
2385
- *
2386
- * This method uses `modifyWithVersioning().updateById()` internally, providing:
2387
- * - Optimistic locking checks before update
2388
- * - Version field incrementation
2389
- * - Cache eviction after successful operation
2390
- *
2391
- * @param entity - The entity with updated values (must include primary key)
2392
- * @param schema - The table schema
2393
- * @returns Promise that resolves to the number of affected rows
2394
- * @throws Error if cacheEntityName is not configured
2395
- * @throws Error if optimistic locking check fails
2396
- */
2397
- async updateById(entity, schema) {
2398
- this.validateCacheConfiguration();
2399
- const number = await this.forgeOperations.modifyWithVersioning().updateById(entity, schema);
2400
- await clearCache(schema, this.options);
2401
- return number;
2402
- }
2403
- /**
2404
- * Updates fields based on conditions with optimistic locking/versioning and automatically evicts cache.
2405
- *
2406
- * This method uses `modifyWithVersioning().updateFields()` internally, providing:
2407
- * - Optimistic locking support (if version field is configured)
2408
- * - Version field validation and incrementation
2409
- * - Cache eviction after successful operation
2410
- *
2411
- * @param updateData - The data to update
2412
- * @param schema - The table schema
2413
- * @param where - Optional WHERE conditions
2414
- * @returns Promise that resolves to the number of affected rows
2415
- * @throws Error if cacheEntityName is not configured
2416
- * @throws Error if optimistic locking check fails
2417
- */
2418
- async updateFields(updateData, schema, where) {
2419
- this.validateCacheConfiguration();
2420
- const number = await this.forgeOperations.modifyWithVersioning().updateFields(updateData, schema, where);
2421
- await clearCache(schema, this.options);
2422
- return number;
2423
- }
2424
- /**
2425
- * Executes a query with caching support.
2426
- * First checks cache, if not found executes query and stores result in cache.
2427
- *
2428
- * @param query - The Drizzle query to execute
2429
- * @param cacheTtl - Optional cache TTL override
2430
- * @returns Promise that resolves to the query results
2431
- * @throws Error if cacheEntityName is not configured
2432
- */
2433
- async executeQuery(query, cacheTtl) {
2434
- this.validateCacheConfiguration();
2435
- const sqlQuery = query;
2436
- const cacheResult = await getFromCache(sqlQuery, this.options);
2437
- if (cacheResult) {
2438
- return cacheResult;
2439
- }
2440
- const results = await query;
2441
- await setCacheResult(sqlQuery, this.options, results, cacheTtl ?? this.options.cacheTTL ?? 60);
2442
- return results;
2443
- }
2444
- /**
2445
- * Validates that cache configuration is properly set up.
2446
- *
2447
- * @throws Error if cacheEntityName is not configured
2448
- * @private
2449
- */
2450
- validateCacheConfiguration() {
2451
- if (!this.options.cacheEntityName) {
2452
- throw new Error("cacheEntityName is not configured");
2453
- }
2454
- }
2455
- }
2456
- class ForgeSQLORMImpl {
2457
- static instance = null;
2458
- drizzle;
2459
- crudOperations;
2460
- fetchOperations;
2461
- analyzeOperations;
2462
- cacheOperations;
2463
- options;
2464
- /**
2465
- * Private constructor to enforce singleton behavior.
2466
- * @param options - Options for configuring ForgeSQL ORM behavior.
2467
- */
2468
- constructor(options) {
2469
- try {
2470
- const newOptions = options ?? {
2471
- logRawSqlQuery: false,
2472
- logCache: false,
2473
- disableOptimisticLocking: false,
2474
- cacheWrapTable: true,
2475
- cacheTTL: 120,
2476
- cacheEntityQueryName: "sql",
2477
- cacheEntityExpirationName: "expiration",
2478
- cacheEntityDataName: "data"
2479
- };
2480
- this.options = newOptions;
2481
- if (newOptions.logRawSqlQuery) {
2482
- console.debug("Initializing ForgeSQLORM...");
2483
- }
2484
- const proxiedDriver = createForgeDriverProxy(
2485
- this,
2486
- newOptions.hints,
2487
- newOptions.logRawSqlQuery
2488
- );
2489
- this.drizzle = patchDbWithSelectAliased(
2490
- mysqlProxy.drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
2491
- newOptions
2492
- );
2493
- this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
2494
- this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
2495
- this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
2496
- this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
2497
- } catch (error) {
2498
- console.error("ForgeSQLORM initialization failed:", error);
2499
- throw error;
2500
- }
2501
- }
2502
- /**
2503
- * Executes a query and provides access to execution metadata with performance monitoring.
2504
- * This method allows you to capture detailed information about query execution
2505
- * including database execution time, response size, and query analysis capabilities.
2506
- *
2507
- * The method aggregates metrics across all database operations within the query function,
2508
- * making it ideal for monitoring resolver performance and detecting performance issues.
2509
- *
2510
- * @template T - The return type of the query
2511
- * @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
2512
- * @param onMetadata - Callback function that receives aggregated execution metadata
2513
- * @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
2514
- * @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
2515
- * @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
2516
- * @returns Promise with the query result
2517
- *
2518
- * @example
2519
- * ```typescript
2520
- * // Basic usage with performance monitoring
2521
- * const result = await forgeSQL.executeWithMetadata(
2522
- * async () => {
2523
- * const users = await forgeSQL.selectFrom(usersTable);
2524
- * const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
2525
- * return { users, orders };
2526
- * },
2527
- * (totalDbExecutionTime, totalResponseSize, printQueries) => {
2528
- * const threshold = 500; // ms baseline for this resolver
2529
- *
2530
- * if (totalDbExecutionTime > threshold * 1.5) {
2531
- * console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
2532
- * await printQueries(); // Analyze and print query execution plans
2533
- * } else if (totalDbExecutionTime > threshold) {
2534
- * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
2535
- * }
2536
- *
2537
- * console.log(`DB response size: ${totalResponseSize} bytes`);
2538
- * }
2539
- * );
2540
- * ```
2541
- *
2542
- * @example
2543
- * ```typescript
2544
- * // Resolver with performance monitoring
2545
- * resolver.define("fetch", async (req: Request) => {
2546
- * try {
2547
- * return await forgeSQL.executeWithMetadata(
2548
- * async () => {
2549
- * // Resolver logic with multiple queries
2550
- * const users = await forgeSQL.selectFrom(demoUsers);
2551
- * const orders = await forgeSQL.selectFrom(demoOrders)
2552
- * .where(eq(demoOrders.userId, demoUsers.id));
2553
- * return { users, orders };
2554
- * },
2555
- * async (totalDbExecutionTime, totalResponseSize, printQueries) => {
2556
- * const threshold = 500; // ms baseline for this resolver
2557
- *
2558
- * if (totalDbExecutionTime > threshold * 1.5) {
2559
- * console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
2560
- * await printQueries(); // Optionally log or capture diagnostics for further analysis
2561
- * } else if (totalDbExecutionTime > threshold) {
2562
- * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
2563
- * }
2564
- *
2565
- * console.log(`DB response size: ${totalResponseSize} bytes`);
2566
- * }
2567
- * );
2568
- * } catch (e) {
2569
- * const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
2570
- * console.error(error, e);
2571
- * throw error;
2572
- * }
2573
- * });
2574
- * ```
2575
- *
2576
- * @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.
2577
- */
2578
- async executeWithMetadata(query, onMetadata) {
2579
- return metadataQueryContext.run(
2580
- {
2581
- totalDbExecutionTime: 0,
2582
- totalResponseSize: 0,
2583
- beginTime: /* @__PURE__ */ new Date(),
2584
- forgeSQLORM: this,
2585
- printQueriesWithPlan: async () => {
2586
- return;
2587
- }
2588
- },
2589
- async () => {
2590
- const result = await query();
2591
- const metadata = await getLastestMetadata();
2592
- try {
2593
- if (metadata) {
2594
- await onMetadata(
2595
- metadata.totalDbExecutionTime,
2596
- metadata.totalResponseSize,
2597
- metadata.printQueriesWithPlan
2598
- );
2599
- }
2600
- } catch (e) {
2601
- console.error(
2602
- "[ForgeSQLORM][executeWithMetadata] Failed to run onMetadata callback",
2603
- {
2604
- errorMessage: e?.message,
2605
- errorStack: e?.stack,
2606
- totalDbExecutionTime: metadata?.totalDbExecutionTime,
2607
- totalResponseSize: metadata?.totalResponseSize,
2608
- beginTime: metadata?.beginTime
2609
- },
2610
- e
2611
- );
2612
- }
2613
- return result;
2614
- }
2615
- );
2616
- }
2617
- /**
2618
- * Executes operations within a cache context that collects cache eviction events.
2619
- * All clearCache calls within the context are collected and executed in batch at the end.
2620
- * Queries executed within this context will bypass cache for tables that were marked for clearing.
2621
- *
2622
- * This is useful for:
2623
- * - Batch operations that affect multiple tables
2624
- * - Transaction-like operations where you want to clear cache only at the end
2625
- * - Performance optimization by reducing cache clear operations
2626
- *
2627
- * @param cacheContext - Function containing operations that may trigger cache evictions
2628
- * @returns Promise that resolves when all operations and cache clearing are complete
2629
- *
2630
- * @example
2631
- * ```typescript
2632
- * await forgeSQL.executeWithCacheContext(async () => {
2633
- * await forgeSQL.modifyWithVersioning().insert(users, userData);
2634
- * await forgeSQL.modifyWithVersioning().insert(orders, orderData);
2635
- * // Cache for both users and orders tables will be cleared at the end
2636
- * });
2637
- * ```
2638
- */
2639
- executeWithCacheContext(cacheContext) {
2640
- return this.executeWithCacheContextAndReturnValue(cacheContext);
2641
- }
2642
- /**
2643
- * Executes operations within a cache context and returns a value.
2644
- * All clearCache calls within the context are collected and executed in batch at the end.
2645
- * Queries executed within this context will bypass cache for tables that were marked for clearing.
2646
- *
2647
- * @param cacheContext - Function containing operations that may trigger cache evictions
2648
- * @returns Promise that resolves to the return value of the cacheContext function
2649
- *
2650
- * @example
2651
- * ```typescript
2652
- * const result = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
2653
- * await forgeSQL.modifyWithVersioning().insert(users, userData);
2654
- * return await forgeSQL.fetch().executeQueryOnlyOne(selectUserQuery);
2655
- * });
2656
- * ```
2657
- */
2658
- async executeWithCacheContextAndReturnValue(cacheContext) {
2659
- return await this.executeWithLocalCacheContextAndReturnValue(
2660
- async () => await cacheApplicationContext.run(
2661
- cacheApplicationContext.getStore() ?? { tables: /* @__PURE__ */ new Set() },
2662
- async () => {
2663
- try {
2664
- return await cacheContext();
2665
- } finally {
2666
- await clearTablesCache(
2667
- Array.from(cacheApplicationContext.getStore()?.tables ?? []),
2668
- this.options
2669
- );
2670
- }
2671
- }
2672
- )
2673
- );
2674
- }
2675
- /**
2676
- * Executes operations within a local cache context and returns a value.
2677
- * This provides in-memory caching for select queries within a single request scope.
2678
- *
2679
- * @param cacheContext - Function containing operations that will benefit from local caching
2680
- * @returns Promise that resolves to the return value of the cacheContext function
2681
- */
2682
- async executeWithLocalCacheContextAndReturnValue(cacheContext) {
2683
- return await localCacheApplicationContext.run(
2684
- localCacheApplicationContext.getStore() ?? { cache: {} },
2685
- async () => {
2686
- return await cacheContext();
2687
- }
2688
- );
2689
- }
2690
- /**
2691
- * Executes operations within a local cache context.
2692
- * This provides in-memory caching for select queries within a single request scope.
2693
- *
2694
- * @param cacheContext - Function containing operations that will benefit from local caching
2695
- * @returns Promise that resolves when all operations are complete
2696
- */
2697
- executeWithLocalContext(cacheContext) {
2698
- return this.executeWithLocalCacheContextAndReturnValue(cacheContext);
2699
- }
2700
- /**
2701
- * Creates an insert query builder.
2702
- *
2703
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2704
- * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
2705
- *
2706
- * @param table - The table to insert into
2707
- * @returns Insert query builder (no versioning, no cache management)
2708
- */
2709
- insert(table2) {
2710
- return this.drizzle.insertWithCacheContext(table2);
2711
- }
2712
- /**
2713
- * Creates an insert query builder that automatically evicts cache after execution.
2714
- *
2715
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2716
- * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
2717
- *
2718
- * @param table - The table to insert into
2719
- * @returns Insert query builder with automatic cache eviction (no versioning)
2720
- */
2721
- insertAndEvictCache(table2) {
2722
- return this.drizzle.insertAndEvictCache(table2);
2723
- }
2724
- /**
2725
- * Creates an update query builder that automatically evicts cache after execution.
2726
- *
2727
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2728
- * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
2729
- *
2730
- * @param table - The table to update
2731
- * @returns Update query builder with automatic cache eviction (no versioning)
2732
- */
2733
- updateAndEvictCache(table2) {
2734
- return this.drizzle.updateAndEvictCache(table2);
2735
- }
2736
- /**
2737
- * Creates an update query builder.
2738
- *
2739
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2740
- * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
2741
- *
2742
- * @param table - The table to update
2743
- * @returns Update query builder (no versioning, no cache management)
2744
- */
2745
- update(table2) {
2746
- return this.drizzle.updateWithCacheContext(table2);
2747
- }
2748
- /**
2749
- * Creates a delete query builder.
2750
- *
2751
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2752
- * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
2753
- *
2754
- * @param table - The table to delete from
2755
- * @returns Delete query builder (no versioning, no cache management)
2756
- */
2757
- delete(table2) {
2758
- return this.drizzle.deleteWithCacheContext(table2);
2759
- }
2760
- /**
2761
- * Creates a delete query builder that automatically evicts cache after execution.
2762
- *
2763
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
2764
- * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
2765
- *
2766
- * @param table - The table to delete from
2767
- * @returns Delete query builder with automatic cache eviction (no versioning)
2768
- */
2769
- deleteAndEvictCache(table2) {
2770
- return this.drizzle.deleteAndEvictCache(table2);
2771
- }
2772
- /**
2773
- * Create the modify operations instance.
2774
- * @returns modify operations.
2775
- */
2776
- modifyWithVersioning() {
2777
- return this.crudOperations;
2778
- }
2779
- /**
2780
- * Returns the singleton instance of ForgeSQLORMImpl.
2781
- * @param options - Options for configuring ForgeSQL ORM behavior.
2782
- * @returns The singleton instance of ForgeSQLORMImpl.
2783
- */
2784
- static getInstance(options) {
2785
- ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
2786
- return ForgeSQLORMImpl.instance;
2787
- }
2788
- /**
2789
- * Retrieves the fetch operations instance.
2790
- * @returns Fetch operations.
2791
- */
2792
- fetch() {
2793
- return this.fetchOperations;
2794
- }
2795
- /**
2796
- * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
2797
- * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
2798
- */
2799
- analyze() {
2800
- return this.analyzeOperations;
2801
- }
2802
- /**
2803
- * Provides schema-level SQL operations with optimistic locking/versioning and automatic cache eviction.
2804
- *
2805
- * This method returns operations that use `modifyWithVersioning()` internally, providing:
2806
- * - Optimistic locking support
2807
- * - Automatic version field management
2808
- * - Cache eviction after successful operations
2809
- *
2810
- * @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
2811
- */
2812
- modifyWithVersioningAndEvictCache() {
2813
- return this.cacheOperations;
2814
- }
2815
- /**
2816
- * Returns a Drizzle query builder instance.
2817
- *
2818
- * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
2819
- * The returned instance should NOT be used for direct database connections or query execution.
2820
- * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
2821
- *
2822
- * @returns A Drizzle query builder instance for query construction only.
2823
- */
2824
- getDrizzleQueryBuilder() {
2825
- return this.drizzle;
2826
- }
2827
- /**
2828
- * Creates a select query with unique field aliases to prevent field name collisions in joins.
2829
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
2830
- *
2831
- * @template TSelection - The type of the selected fields
2832
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
2833
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
2834
- * @throws {Error} If fields parameter is empty
2835
- * @example
2836
- * ```typescript
2837
- * await forgeSQL
2838
- * .select({user: users, order: orders})
2839
- * .from(orders)
2840
- * .innerJoin(users, eq(orders.userId, users.id));
2841
- * ```
2842
- */
2843
- select(fields) {
2844
- if (!fields) {
2845
- throw new Error("fields is empty");
2846
- }
2847
- return this.drizzle.selectAliased(fields);
2848
- }
2849
- /**
2850
- * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
2851
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
2852
- *
2853
- * @template TSelection - The type of the selected fields
2854
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
2855
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
2856
- * @throws {Error} If fields parameter is empty
2857
- * @example
2858
- * ```typescript
2859
- * await forgeSQL
2860
- * .selectDistinct({user: users, order: orders})
2861
- * .from(orders)
2862
- * .innerJoin(users, eq(orders.userId, users.id));
2863
- * ```
2864
- */
2865
- selectDistinct(fields) {
2866
- if (!fields) {
2867
- throw new Error("fields is empty");
2868
- }
2869
- return this.drizzle.selectAliasedDistinct(fields);
2870
- }
2871
- /**
2872
- * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
2873
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
2874
- *
2875
- * @template TSelection - The type of the selected fields
2876
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
2877
- * @param {number} cacheTTL - cache ttl optional default is 60 sec.
2878
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
2879
- * @throws {Error} If fields parameter is empty
2880
- * @example
2881
- * ```typescript
2882
- * await forgeSQL
2883
- * .selectCacheable({user: users, order: orders},60)
2884
- * .from(orders)
2885
- * .innerJoin(users, eq(orders.userId, users.id));
2886
- * ```
2887
- */
2888
- selectCacheable(fields, cacheTTL) {
2889
- if (!fields) {
2890
- throw new Error("fields is empty");
2891
- }
2892
- return this.drizzle.selectAliasedCacheable(fields, cacheTTL);
2893
- }
2894
- /**
2895
- * Creates a cacheable distinct select query with unique field aliases to prevent field name collisions in joins.
2896
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
2897
- *
2898
- * @template TSelection - The type of the selected fields
2899
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
2900
- * @param {number} cacheTTL - cache ttl optional default is 60 sec.
2901
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
2902
- * @throws {Error} If fields parameter is empty
2903
- * @example
2904
- * ```typescript
2905
- * await forgeSQL
2906
- * .selectDistinctCacheable({user: users, order: orders}, 60)
2907
- * .from(orders)
2908
- * .innerJoin(users, eq(orders.userId, users.id));
2909
- * ```
2910
- */
2911
- selectDistinctCacheable(fields, cacheTTL) {
2912
- if (!fields) {
2913
- throw new Error("fields is empty");
2914
- }
2915
- return this.drizzle.selectAliasedDistinctCacheable(fields, cacheTTL);
2916
- }
2917
- /**
2918
- * Creates a select query builder for all columns from a table with field aliasing support.
2919
- * This is a convenience method that automatically selects all columns from the specified table.
2920
- *
2921
- * @template T - The type of the table
2922
- * @param table - The table to select from
2923
- * @returns Select query builder with all table columns and field aliasing support
2924
- * @example
2925
- * ```typescript
2926
- * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
2927
- * ```
2928
- */
2929
- selectFrom(table2) {
2930
- return this.drizzle.selectFrom(table2);
2931
- }
2932
- /**
2933
- * Creates a select distinct query builder for all columns from a table with field aliasing support.
2934
- * This is a convenience method that automatically selects all distinct columns from the specified table.
2935
- *
2936
- * @template T - The type of the table
2937
- * @param table - The table to select from
2938
- * @returns Select distinct query builder with all table columns and field aliasing support
2939
- * @example
2940
- * ```typescript
2941
- * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
2942
- * ```
2943
- */
2944
- selectDistinctFrom(table2) {
2945
- return this.drizzle.selectDistinctFrom(table2);
2946
- }
2947
- /**
2948
- * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
2949
- * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
2950
- *
2951
- * @template T - The type of the table
2952
- * @param table - The table to select from
2953
- * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2954
- * @returns Select query builder with all table columns, field aliasing, and caching support
2955
- * @example
2956
- * ```typescript
2957
- * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
2958
- * ```
2959
- */
2960
- selectCacheableFrom(table2, cacheTTL) {
2961
- return this.drizzle.selectFromCacheable(table2, cacheTTL);
2962
- }
2963
- /**
2964
- * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
2965
- * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
2966
- *
2967
- * @template T - The type of the table
2968
- * @param table - The table to select from
2969
- * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2970
- * @returns Select distinct query builder with all table columns, field aliasing, and caching support
2971
- * @example
2972
- * ```typescript
2973
- * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
2974
- * ```
2975
- */
2976
- selectDistinctCacheableFrom(table2, cacheTTL) {
2977
- return this.drizzle.selectDistinctFromCacheable(table2, cacheTTL);
2978
- }
2979
- /**
2980
- * Executes a raw SQL query with local cache support.
2981
- * This method provides local caching for raw SQL queries within the current invocation context.
2982
- * Results are cached locally and will be returned from cache on subsequent identical queries.
2983
- *
2984
- * @param query - The SQL query to execute (SQLWrapper or string)
2985
- * @returns Promise with query results
2986
- * @example
2987
- * ```typescript
2988
- * // Using SQLWrapper
2989
- * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
2990
- *
2991
- * // Using string
2992
- * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
2993
- * ```
2994
- */
2995
- execute(query) {
2996
- return this.drizzle.executeQuery(query);
2997
- }
2998
- /**
2999
- * Executes a Data Definition Language (DDL) SQL query.
3000
- * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
3001
- *
3002
- * This method is specifically designed for DDL operations and provides:
3003
- * - Proper operation type context for DDL queries
3004
- * - No caching (DDL operations should not be cached)
3005
- * - Direct execution without query optimization
3006
- *
3007
- * @template T - The expected return type of the query result
3008
- * @param query - The DDL SQL query to execute (SQLWrapper or string)
3009
- * @returns Promise with query results
3010
- * @throws {Error} If the DDL operation fails
3011
- *
3012
- * @example
3013
- * ```typescript
3014
- * // Create a new table
3015
- * await forgeSQL.executeDDL(`
3016
- * CREATE TABLE users (
3017
- * id INT PRIMARY KEY AUTO_INCREMENT,
3018
- * name VARCHAR(255) NOT NULL,
3019
- * email VARCHAR(255) UNIQUE
3020
- * )
3021
- * `);
3022
- *
3023
- * // Alter table structure
3024
- * await forgeSQL.executeDDL(sql`
3025
- * ALTER TABLE users
3026
- * ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3027
- * `);
3028
- *
3029
- * // Drop a table
3030
- * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
3031
- * ```
3032
- */
3033
- async executeDDL(query) {
3034
- return this.executeDDLActions(async () => this.drizzle.executeQuery(query));
3035
- }
3036
- /**
3037
- * Executes a series of actions within a DDL operation context.
3038
- * This method provides a way to execute regular SQL queries that should be treated
3039
- * as DDL operations, ensuring proper operation type context for performance monitoring.
3040
- *
3041
- * This method is useful for:
3042
- * - Executing regular SQL queries in DDL context for monitoring purposes
3043
- * - Wrapping non-DDL operations that should be treated as DDL for analysis
3044
- * - Ensuring proper operation type context for complex workflows
3045
- * - Maintaining DDL operation context across multiple function calls
3046
- *
3047
- * @template T - The return type of the actions function
3048
- * @param actions - Function containing SQL operations to execute in DDL context
3049
- * @returns Promise that resolves to the return value of the actions function
3050
- *
3051
- * @example
3052
- * ```typescript
3053
- * // Execute regular SQL queries in DDL context for monitoring
3054
- * await forgeSQL.executeDDLActions(async () => {
3055
- * const slowQueries = await forgeSQL.execute(`
3056
- * SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
3057
- * WHERE AVG_LATENCY > 1000000
3058
- * `);
3059
- * return slowQueries;
3060
- * });
3061
- *
3062
- * // Execute complex analysis queries in DDL context
3063
- * const result = await forgeSQL.executeDDLActions(async () => {
3064
- * const tableInfo = await forgeSQL.execute("SHOW TABLES");
3065
- * const performanceData = await forgeSQL.execute(`
3066
- * SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
3067
- * WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
3068
- * `);
3069
- * return { tableInfo, performanceData };
3070
- * });
3071
- *
3072
- * // Execute monitoring queries with error handling
3073
- * try {
3074
- * await forgeSQL.executeDDLActions(async () => {
3075
- * const metrics = await forgeSQL.execute(`
3076
- * SELECT COUNT(*) as query_count
3077
- * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
3078
- * `);
3079
- * console.log(`Total queries: ${metrics[0].query_count}`);
3080
- * });
3081
- * } catch (error) {
3082
- * console.error("Monitoring query failed:", error);
3083
- * }
3084
- * ```
3085
- */
3086
- async executeDDLActions(actions) {
3087
- return operationTypeQueryContext.run({ operationType: "DDL" }, async () => actions());
3088
- }
3089
- /**
3090
- * Executes a raw SQL query with both local and global cache support.
3091
- * This method provides comprehensive caching for raw SQL queries:
3092
- * - Local cache: Within the current invocation context
3093
- * - Global cache: Cross-invocation caching using @forge/kvs
3094
- *
3095
- * @param query - The SQL query to execute (SQLWrapper or string)
3096
- * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
3097
- * @returns Promise with query results
3098
- * @example
3099
- * ```typescript
3100
- * // Using SQLWrapper with custom TTL
3101
- * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
3102
- *
3103
- * // Using string with default TTL
3104
- * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
3105
- * ```
3106
- */
3107
- executeCacheable(query, cacheTtl) {
3108
- return this.drizzle.executeQueryCacheable(query, cacheTtl);
3109
- }
3110
- /**
3111
- * Creates a Common Table Expression (CTE) builder for complex queries.
3112
- * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
3113
- *
3114
- * @returns WithBuilder for creating CTEs
3115
- * @example
3116
- * ```typescript
3117
- * const withQuery = forgeSQL.$with('userStats').as(
3118
- * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
3119
- * .from(users)
3120
- * .groupBy(users.id)
3121
- * );
3122
- * ```
3123
- */
3124
- get $with() {
3125
- return this.drizzle.$with;
3126
- }
3127
- /**
3128
- * Creates a query builder that uses Common Table Expressions (CTEs).
3129
- * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
3130
- *
3131
- * @param queries - Array of CTE queries created with $with()
3132
- * @returns Query builder with CTE support
3133
- * @example
3134
- * ```typescript
3135
- * const withQuery = forgeSQL.$with('userStats').as(
3136
- * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
3137
- * .from(users)
3138
- * .groupBy(users.id)
3139
- * );
3140
- *
3141
- * const result = await forgeSQL.with(withQuery)
3142
- * .select({ userId: withQuery.userId, count: withQuery.count })
3143
- * .from(withQuery);
3144
- * ```
3145
- */
3146
- with(...queries) {
3147
- return this.drizzle.with(...queries);
3148
- }
3149
- }
3150
- class ForgeSQLORM {
3151
- ormInstance;
3152
- constructor(options) {
3153
- this.ormInstance = ForgeSQLORMImpl.getInstance(options);
3154
- }
3155
- /**
3156
- * Executes a query and provides access to execution metadata with performance monitoring.
3157
- * This method allows you to capture detailed information about query execution
3158
- * including database execution time, response size, and query analysis capabilities.
3159
- *
3160
- * The method aggregates metrics across all database operations within the query function,
3161
- * making it ideal for monitoring resolver performance and detecting performance issues.
3162
- *
3163
- * @template T - The return type of the query
3164
- * @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
3165
- * @param onMetadata - Callback function that receives aggregated execution metadata
3166
- * @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
3167
- * @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
3168
- * @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
3169
- * @returns Promise with the query result
3170
- *
3171
- * @example
3172
- * ```typescript
3173
- * // Basic usage with performance monitoring
3174
- * const result = await forgeSQL.executeWithMetadata(
3175
- * async () => {
3176
- * const users = await forgeSQL.selectFrom(usersTable);
3177
- * const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
3178
- * return { users, orders };
3179
- * },
3180
- * (totalDbExecutionTime, totalResponseSize, printQueries) => {
3181
- * const threshold = 500; // ms baseline for this resolver
3182
- *
3183
- * if (totalDbExecutionTime > threshold * 1.5) {
3184
- * console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
3185
- * await printQueries(); // Analyze and print query execution plans
3186
- * } else if (totalDbExecutionTime > threshold) {
3187
- * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
3188
- * }
3189
- *
3190
- * console.log(`DB response size: ${totalResponseSize} bytes`);
3191
- * }
3192
- * );
3193
- * ```
3194
- *
3195
- * @example
3196
- * ```typescript
3197
- * // Resolver with performance monitoring
3198
- * resolver.define("fetch", async (req: Request) => {
3199
- * try {
3200
- * return await forgeSQL.executeWithMetadata(
3201
- * async () => {
3202
- * // Resolver logic with multiple queries
3203
- * const users = await forgeSQL.selectFrom(demoUsers);
3204
- * const orders = await forgeSQL.selectFrom(demoOrders)
3205
- * .where(eq(demoOrders.userId, demoUsers.id));
3206
- * return { users, orders };
3207
- * },
3208
- * async (totalDbExecutionTime, totalResponseSize, printQueries) => {
3209
- * const threshold = 500; // ms baseline for this resolver
3210
- *
3211
- * if (totalDbExecutionTime > threshold * 1.5) {
3212
- * console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
3213
- * await printQueries(); // Optionally log or capture diagnostics for further analysis
3214
- * } else if (totalDbExecutionTime > threshold) {
3215
- * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
3216
- * }
3217
- *
3218
- * console.log(`DB response size: ${totalResponseSize} bytes`);
3219
- * }
3220
- * );
3221
- * } catch (e) {
3222
- * const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
3223
- * console.error(error, e);
3224
- * throw error;
3225
- * }
3226
- * });
3227
- * ```
3228
- *
3229
- * @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.
3230
- */
3231
- async executeWithMetadata(query, onMetadata) {
3232
- return this.ormInstance.executeWithMetadata(query, onMetadata);
3233
- }
3234
- selectCacheable(fields, cacheTTL) {
3235
- return this.ormInstance.selectCacheable(fields, cacheTTL);
3236
- }
3237
- selectDistinctCacheable(fields, cacheTTL) {
3238
- return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
3239
- }
3240
- /**
3241
- * Creates a select query builder for all columns from a table with field aliasing support.
3242
- * This is a convenience method that automatically selects all columns from the specified table.
3243
- *
3244
- * @template T - The type of the table
3245
- * @param table - The table to select from
3246
- * @returns Select query builder with all table columns and field aliasing support
3247
- * @example
3248
- * ```typescript
3249
- * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
3250
- * ```
3251
- */
3252
- selectFrom(table2) {
3253
- return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table2);
3254
- }
3255
- /**
3256
- * Creates a select distinct query builder for all columns from a table with field aliasing support.
3257
- * This is a convenience method that automatically selects all distinct columns from the specified table.
3258
- *
3259
- * @template T - The type of the table
3260
- * @param table - The table to select from
3261
- * @returns Select distinct query builder with all table columns and field aliasing support
3262
- * @example
3263
- * ```typescript
3264
- * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
3265
- * ```
3266
- */
3267
- selectDistinctFrom(table2) {
3268
- return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table2);
3269
- }
3270
- /**
3271
- * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
3272
- * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
3273
- *
3274
- * @template T - The type of the table
3275
- * @param table - The table to select from
3276
- * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
3277
- * @returns Select query builder with all table columns, field aliasing, and caching support
3278
- * @example
3279
- * ```typescript
3280
- * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
3281
- * ```
3282
- */
3283
- selectCacheableFrom(table2, cacheTTL) {
3284
- return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table2, cacheTTL);
3285
- }
3286
- /**
3287
- * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
3288
- * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
3289
- *
3290
- * @template T - The type of the table
3291
- * @param table - The table to select from
3292
- * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
3293
- * @returns Select distinct query builder with all table columns, field aliasing, and caching support
3294
- * @example
3295
- * ```typescript
3296
- * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
3297
- * ```
3298
- */
3299
- selectDistinctCacheableFrom(table2, cacheTTL) {
3300
- return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table2, cacheTTL);
3301
- }
3302
- executeWithCacheContext(cacheContext) {
3303
- return this.ormInstance.executeWithCacheContext(cacheContext);
3304
- }
3305
- executeWithCacheContextAndReturnValue(cacheContext) {
3306
- return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
3307
- }
3308
- /**
3309
- * Executes operations within a local cache context.
3310
- * This provides in-memory caching for select queries within a single request scope.
3311
- *
3312
- * @param cacheContext - Function containing operations that will benefit from local caching
3313
- * @returns Promise that resolves when all operations are complete
3314
- */
3315
- executeWithLocalContext(cacheContext) {
3316
- return this.ormInstance.executeWithLocalContext(cacheContext);
3317
- }
3318
- /**
3319
- * Executes operations within a local cache context and returns a value.
3320
- * This provides in-memory caching for select queries within a single request scope.
3321
- *
3322
- * @param cacheContext - Function containing operations that will benefit from local caching
3323
- * @returns Promise that resolves to the return value of the cacheContext function
3324
- */
3325
- executeWithLocalCacheContextAndReturnValue(cacheContext) {
3326
- return this.ormInstance.executeWithLocalCacheContextAndReturnValue(cacheContext);
3327
- }
3328
- /**
3329
- * Creates an insert query builder.
3330
- *
3331
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3332
- * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
3333
- *
3334
- * @param table - The table to insert into
3335
- * @returns Insert query builder (no versioning, no cache management)
3336
- */
3337
- insert(table2) {
3338
- return this.ormInstance.insert(table2);
3339
- }
3340
- /**
3341
- * Creates an insert query builder that automatically evicts cache after execution.
3342
- *
3343
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3344
- * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
3345
- *
3346
- * @param table - The table to insert into
3347
- * @returns Insert query builder with automatic cache eviction (no versioning)
3348
- */
3349
- insertAndEvictCache(table2) {
3350
- return this.ormInstance.insertAndEvictCache(table2);
3351
- }
3352
- /**
3353
- * Creates an update query builder.
3354
- *
3355
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3356
- * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
3357
- *
3358
- * @param table - The table to update
3359
- * @returns Update query builder (no versioning, no cache management)
3360
- */
3361
- update(table2) {
3362
- return this.ormInstance.update(table2);
3363
- }
3364
- /**
3365
- * Creates an update query builder that automatically evicts cache after execution.
3366
- *
3367
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3368
- * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
3369
- *
3370
- * @param table - The table to update
3371
- * @returns Update query builder with automatic cache eviction (no versioning)
3372
- */
3373
- updateAndEvictCache(table2) {
3374
- return this.ormInstance.updateAndEvictCache(table2);
3375
- }
3376
- /**
3377
- * Creates a delete query builder.
3378
- *
3379
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3380
- * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
3381
- *
3382
- * @param table - The table to delete from
3383
- * @returns Delete query builder (no versioning, no cache management)
3384
- */
3385
- delete(table2) {
3386
- return this.ormInstance.delete(table2);
3387
- }
3388
- /**
3389
- * Creates a delete query builder that automatically evicts cache after execution.
3390
- *
3391
- * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
3392
- * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
3393
- *
3394
- * @param table - The table to delete from
3395
- * @returns Delete query builder with automatic cache eviction (no versioning)
3396
- */
3397
- deleteAndEvictCache(table2) {
3398
- return this.ormInstance.deleteAndEvictCache(table2);
3399
- }
3400
- /**
3401
- * Creates a select query with unique field aliases to prevent field name collisions in joins.
3402
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
3403
- *
3404
- * @template TSelection - The type of the selected fields
3405
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
3406
- * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
3407
- * @throws {Error} If fields parameter is empty
3408
- * @example
3409
- * ```typescript
3410
- * await forgeSQL
3411
- * .select({user: users, order: orders})
3412
- * .from(orders)
3413
- * .innerJoin(users, eq(orders.userId, users.id));
3414
- * ```
3415
- */
3416
- select(fields) {
3417
- return this.ormInstance.select(fields);
3418
- }
3419
- /**
3420
- * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
3421
- * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
3422
- *
3423
- * @template TSelection - The type of the selected fields
3424
- * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
3425
- * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
3426
- * @throws {Error} If fields parameter is empty
3427
- * @example
3428
- * ```typescript
3429
- * await forgeSQL
3430
- * .selectDistinct({user: users, order: orders})
3431
- * .from(orders)
3432
- * .innerJoin(users, eq(orders.userId, users.id));
3433
- * ```
3434
- */
3435
- selectDistinct(fields) {
3436
- return this.ormInstance.selectDistinct(fields);
3437
- }
3438
- /**
3439
- * Proxies the `modify` method from `ForgeSQLORMImpl`.
3440
- * @returns Modify operations.
3441
- */
3442
- modifyWithVersioning() {
3443
- return this.ormInstance.modifyWithVersioning();
3444
- }
3445
- /**
3446
- * Proxies the `fetch` method from `ForgeSQLORMImpl`.
3447
- * @returns Fetch operations.
3448
- */
3449
- fetch() {
3450
- return this.ormInstance.fetch();
3451
- }
3452
- /**
3453
- * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
3454
- * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
3455
- */
3456
- analyze() {
3457
- return this.ormInstance.analyze();
3458
- }
3459
- /**
3460
- * Provides schema-level SQL cacheable operations with type safety.
3461
- * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
3462
- */
3463
- modifyWithVersioningAndEvictCache() {
3464
- return this.ormInstance.modifyWithVersioningAndEvictCache();
3465
- }
3466
- /**
3467
- * Returns a Drizzle query builder instance.
3468
- *
3469
- * @returns A Drizzle query builder instance for query construction only.
3470
- */
3471
- getDrizzleQueryBuilder() {
3472
- return this.ormInstance.getDrizzleQueryBuilder();
3473
- }
3474
- /**
3475
- * Executes a raw SQL query with local cache support.
3476
- * This method provides local caching for raw SQL queries within the current invocation context.
3477
- * Results are cached locally and will be returned from cache on subsequent identical queries.
3478
- *
3479
- * @param query - The SQL query to execute (SQLWrapper or string)
3480
- * @returns Promise with query results
3481
- * @example
3482
- * ```typescript
3483
- * // Using SQLWrapper
3484
- * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
3485
- *
3486
- * // Using string
3487
- * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
3488
- * ```
3489
- */
3490
- execute(query) {
3491
- return this.ormInstance.execute(query);
3492
- }
3493
- /**
3494
- * Executes a Data Definition Language (DDL) SQL query.
3495
- * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
3496
- *
3497
- * This method is specifically designed for DDL operations and provides:
3498
- * - Proper operation type context for DDL queries
3499
- * - No caching (DDL operations should not be cached)
3500
- * - Direct execution without query optimization
3501
- *
3502
- * @template T - The expected return type of the query result
3503
- * @param query - The DDL SQL query to execute (SQLWrapper or string)
3504
- * @returns Promise with query results
3505
- * @throws {Error} If the DDL operation fails
3506
- *
3507
- * @example
3508
- * ```typescript
3509
- * // Create a new table
3510
- * await forgeSQL.executeDDL(`
3511
- * CREATE TABLE users (
3512
- * id INT PRIMARY KEY AUTO_INCREMENT,
3513
- * name VARCHAR(255) NOT NULL,
3514
- * email VARCHAR(255) UNIQUE
3515
- * )
3516
- * `);
3517
- *
3518
- * // Alter table structure
3519
- * await forgeSQL.executeDDL(sql`
3520
- * ALTER TABLE users
3521
- * ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3522
- * `);
3523
- *
3524
- * // Drop a table
3525
- * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
3526
- * ```
3527
- */
3528
- executeDDL(query) {
3529
- return this.ormInstance.executeDDL(query);
3530
- }
3531
- /**
3532
- * Executes a series of actions within a DDL operation context.
3533
- * This method provides a way to execute regular SQL queries that should be treated
3534
- * as DDL operations, ensuring proper operation type context for performance monitoring.
3535
- *
3536
- * This method is useful for:
3537
- * - Executing regular SQL queries in DDL context for monitoring purposes
3538
- * - Wrapping non-DDL operations that should be treated as DDL for analysis
3539
- * - Ensuring proper operation type context for complex workflows
3540
- * - Maintaining DDL operation context across multiple function calls
3541
- *
3542
- * @template T - The return type of the actions function
3543
- * @param actions - Function containing SQL operations to execute in DDL context
3544
- * @returns Promise that resolves to the return value of the actions function
3545
- *
3546
- * @example
3547
- * ```typescript
3548
- * // Execute regular SQL queries in DDL context for monitoring
3549
- * await forgeSQL.executeDDLActions(async () => {
3550
- * const slowQueries = await forgeSQL.execute(`
3551
- * SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
3552
- * WHERE AVG_LATENCY > 1000000
3553
- * `);
3554
- * return slowQueries;
3555
- * });
3556
- *
3557
- * // Execute complex analysis queries in DDL context
3558
- * const result = await forgeSQL.executeDDLActions(async () => {
3559
- * const tableInfo = await forgeSQL.execute("SHOW TABLES");
3560
- * const performanceData = await forgeSQL.execute(`
3561
- * SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
3562
- * WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
3563
- * `);
3564
- * return { tableInfo, performanceData };
3565
- * });
3566
- *
3567
- * // Execute monitoring queries with error handling
3568
- * try {
3569
- * await forgeSQL.executeDDLActions(async () => {
3570
- * const metrics = await forgeSQL.execute(`
3571
- * SELECT COUNT(*) as query_count
3572
- * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
3573
- * `);
3574
- * console.log(`Total queries: ${metrics[0].query_count}`);
3575
- * });
3576
- * } catch (error) {
3577
- * console.error("Monitoring query failed:", error);
3578
- * }
3579
- * ```
3580
- */
3581
- executeDDLActions(actions) {
3582
- return this.ormInstance.executeDDLActions(actions);
3583
- }
3584
- /**
3585
- * Executes a raw SQL query with both local and global cache support.
3586
- * This method provides comprehensive caching for raw SQL queries:
3587
- * - Local cache: Within the current invocation context
3588
- * - Global cache: Cross-invocation caching using @forge/kvs
3589
- *
3590
- * @param query - The SQL query to execute (SQLWrapper or string)
3591
- * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
3592
- * @returns Promise with query results
3593
- * @example
3594
- * ```typescript
3595
- * // Using SQLWrapper with custom TTL
3596
- * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
3597
- *
3598
- * // Using string with default TTL
3599
- * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
3600
- * ```
3601
- */
3602
- executeCacheable(query, cacheTtl) {
3603
- return this.ormInstance.executeCacheable(query, cacheTtl);
3604
- }
3605
- /**
3606
- * Creates a Common Table Expression (CTE) builder for complex queries.
3607
- * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
3608
- *
3609
- * @returns WithBuilder for creating CTEs
3610
- * @example
3611
- * ```typescript
3612
- * const withQuery = forgeSQL.$with('userStats').as(
3613
- * forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
3614
- * .from(users)
3615
- * .groupBy(users.id)
3616
- * );
3617
- * ```
3618
- */
3619
- get $with() {
3620
- return this.ormInstance.getDrizzleQueryBuilder().$with;
3621
- }
3622
- /**
3623
- * Creates a query builder that uses Common Table Expressions (CTEs).
3624
- * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
3625
- *
3626
- * @param queries - Array of CTE queries created with $with()
3627
- * @returns Query builder with CTE support
3628
- * @example
3629
- * ```typescript
3630
- * const withQuery = forgeSQL.$with('userStats').as(
3631
- * forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
3632
- * .from(users)
3633
- * .groupBy(users.id)
3634
- * );
3635
- *
3636
- * const result = await forgeSQL.with(withQuery)
3637
- * .select({ userId: withQuery.userId, count: withQuery.count })
3638
- * .from(withQuery);
3639
- * ```
3640
- */
3641
- with(...queries) {
3642
- return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
3643
- }
3644
- }
3645
- const forgeDateTimeString = mysqlCore.customType({
3646
- dataType() {
3647
- return "datetime";
3648
- },
3649
- toDriver(value) {
3650
- return formatDateTime(value, "yyyy-MM-dd' 'HH:mm:ss.SSS", false);
3651
- },
3652
- fromDriver(value) {
3653
- const format = "yyyy-MM-dd' 'HH:mm:ss.SSS";
3654
- return parseDateTime(value, format);
3655
- }
3656
- });
3657
- const forgeTimestampString = mysqlCore.customType({
3658
- dataType() {
3659
- return "timestamp";
3660
- },
3661
- toDriver(value) {
3662
- return formatDateTime(value, "yyyy-MM-dd' 'HH:mm:ss.SSS", true);
3663
- },
3664
- fromDriver(value) {
3665
- const format = "yyyy-MM-dd' 'HH:mm:ss.SSS";
3666
- return parseDateTime(value, format);
3667
- }
3668
- });
3669
- const forgeDateString = mysqlCore.customType({
3670
- dataType() {
3671
- return "date";
3672
- },
3673
- toDriver(value) {
3674
- return formatDateTime(value, "yyyy-MM-dd", false);
3675
- },
3676
- fromDriver(value) {
3677
- const format = "yyyy-MM-dd";
3678
- return parseDateTime(value, format);
3679
- }
3680
- });
3681
- const forgeTimeString = mysqlCore.customType({
3682
- dataType() {
3683
- return "time";
3684
- },
3685
- toDriver(value) {
3686
- return formatDateTime(value, "HH:mm:ss.SSS", false);
3687
- },
3688
- fromDriver(value) {
3689
- return parseDateTime(value, "HH:mm:ss.SSS");
3690
- }
3691
- });
3692
- async function dropSchemaMigrations() {
3693
- try {
3694
- const tables = await getTables();
3695
- const dropStatements = generateDropTableStatements(tables, { sequence: true, table: true });
3696
- for (const statement of dropStatements) {
3697
- console.debug(`execute DDL: ${statement}`);
3698
- await sql.sql.executeDDL(statement);
3699
- }
3700
- return getHttpResponse(
3701
- 200,
3702
- "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
3703
- );
3704
- } catch (error) {
3705
- const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred";
3706
- console.error(errorMessage);
3707
- return getHttpResponse(500, errorMessage);
3708
- }
3709
- }
3710
- const applySchemaMigrations = async (migration) => {
3711
- try {
3712
- if (typeof migration !== "function") {
3713
- throw new Error("migration is not a function");
3714
- }
3715
- console.debug("Provisioning the database");
3716
- await sql.sql._provision();
3717
- console.debug("Running schema migrations");
3718
- const migrations2 = await migration(sql.migrationRunner);
3719
- const successfulMigrations = await migrations2.run();
3720
- console.debug("Migrations applied:", successfulMigrations);
3721
- const migrationList = await sql.migrationRunner.list();
3722
- let migrationHistory = "No migrations found";
3723
- if (Array.isArray(migrationList) && migrationList.length > 0) {
3724
- const sortedMigrations = migrationList.toSorted(
3725
- (a, b) => a.migratedAt.getTime() - b.migratedAt.getTime()
3726
- );
3727
- migrationHistory = sortedMigrations.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
3728
- }
3729
- console.debug("Migrations history:\nid, name, migrated_at\n", migrationHistory);
3730
- return {
3731
- headers: { "Content-Type": ["application/json"] },
3732
- statusCode: 200,
3733
- statusText: "OK",
3734
- body: "Migrations successfully executed"
3735
- };
3736
- } catch (error) {
3737
- const errorMessage = error?.cause?.context?.debug?.sqlMessage ?? error?.cause?.context?.debug?.message ?? error?.debug?.context?.sqlMessage ?? error?.debug?.context?.message ?? error.message ?? "Unknown error occurred";
3738
- console.error("Error during migration:", errorMessage);
3739
- return {
3740
- headers: { "Content-Type": ["application/json"] },
3741
- statusCode: 500,
3742
- statusText: "Internal Server Error",
3743
- body: error instanceof Error ? errorMessage : "Unknown error during migration"
3744
- };
3745
- }
3746
- };
3747
- async function fetchSchemaWebTrigger() {
3748
- try {
3749
- const tables = await getTables();
3750
- const createTableStatements = await generateCreateTableStatements(tables);
3751
- const sqlStatements = wrapWithForeignKeyChecks(createTableStatements);
3752
- return getHttpResponse(200, sqlStatements.join(";\n"));
3753
- } catch (error) {
3754
- const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred";
3755
- console.error(errorMessage);
3756
- return getHttpResponse(500, errorMessage);
3757
- }
3758
- }
3759
- async function generateCreateTableStatements(tables) {
3760
- const statements = [];
3761
- for (const table2 of tables) {
3762
- const createTableResult = await sql.sql.executeDDL(`SHOW CREATE TABLE "${table2}"`);
3763
- const createTableStatements = createTableResult.rows.filter((row) => !isSystemTable(row.Table)).map((row) => formatCreateTableStatement(row["Create Table"]));
3764
- statements.push(...createTableStatements);
3765
- }
3766
- return statements;
3767
- }
3768
- function isSystemTable(tableName) {
3769
- return forgeSystemTables.some((st) => table.getTableName(st) === tableName);
3770
- }
3771
- function formatCreateTableStatement(statement) {
3772
- return statement.replace(/"/g, "").replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
3773
- }
3774
- function wrapWithForeignKeyChecks(statements) {
3775
- return ["SET foreign_key_checks = 0", ...statements, "SET foreign_key_checks = 1"];
3776
- }
3777
- async function dropTableSchemaMigrations() {
3778
- try {
3779
- const tables = await getTables();
3780
- const dropStatements = generateDropTableStatements(tables, { sequence: false, table: true });
3781
- for (const statement of dropStatements) {
3782
- console.debug(`execute DDL: ${statement}`);
3783
- await sql.sql.executeDDL(statement);
3784
- }
3785
- return getHttpResponse(
3786
- 200,
3787
- "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
3788
- );
3789
- } catch (error) {
3790
- const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred";
3791
- console.error(errorMessage);
3792
- return getHttpResponse(500, errorMessage);
3793
- }
3794
- }
3795
- const clearCacheSchedulerTrigger = async (options) => {
3796
- try {
3797
- const newOptions = options ?? {
3798
- logRawSqlQuery: false,
3799
- disableOptimisticLocking: false,
3800
- cacheTTL: 120,
3801
- cacheEntityName: "cache",
3802
- cacheEntityQueryName: "sql",
3803
- cacheEntityExpirationName: "expiration",
3804
- cacheEntityDataName: "data"
3805
- };
3806
- if (!newOptions.cacheEntityName) {
3807
- throw new Error("cacheEntityName is not configured");
3808
- }
3809
- await clearExpiredCache(newOptions);
3810
- return {
3811
- headers: { "Content-Type": ["application/json"] },
3812
- statusCode: 200,
3813
- statusText: "OK",
3814
- body: JSON.stringify({
3815
- success: true,
3816
- message: "Cache cleanup completed successfully",
3817
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3818
- })
3819
- };
3820
- } catch (error) {
3821
- console.error("Error during cache cleanup: ", JSON.stringify(error));
3822
- return {
3823
- headers: { "Content-Type": ["application/json"] },
3824
- statusCode: 500,
3825
- statusText: "Internal Server Error",
3826
- body: JSON.stringify({
3827
- success: false,
3828
- error: error instanceof Error ? error.message : "Unknown error during cache cleanup",
3829
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3830
- })
3831
- };
3832
- }
3833
- };
3834
- async function slowQuerySchedulerTrigger(forgeSQLORM, options) {
3835
- try {
3836
- return getHttpResponse(200, JSON.stringify(await slowQueryPerHours(forgeSQLORM, options?.hours ?? 1, options?.timeout ?? 3e3)));
3837
- } catch (error) {
3838
- const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred";
3839
- console.error(errorMessage);
3840
- return getHttpResponse(500, errorMessage);
3841
- }
3842
- }
3843
- const getHttpResponse = (statusCode, body) => {
3844
- let statusText = "";
3845
- if (statusCode === 200) {
3846
- statusText = "Ok";
3847
- } else {
3848
- statusText = "Bad Request";
3849
- }
3850
- return {
3851
- headers: { "Content-Type": ["application/json"] },
3852
- statusCode,
3853
- statusText,
3854
- body
3855
- };
3856
- };
3857
- exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
3858
- exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
3859
- exports.applyFromDriverTransform = applyFromDriverTransform;
3860
- exports.applySchemaMigrations = applySchemaMigrations;
3861
- exports.clearCacheSchedulerTrigger = clearCacheSchedulerTrigger;
3862
- exports.clusterStatementsSummary = clusterStatementsSummary;
3863
- exports.clusterStatementsSummaryHistory = clusterStatementsSummaryHistory;
3864
- exports.default = ForgeSQLORM;
3865
- exports.dropSchemaMigrations = dropSchemaMigrations;
3866
- exports.dropTableSchemaMigrations = dropTableSchemaMigrations;
3867
- exports.fetchSchemaWebTrigger = fetchSchemaWebTrigger;
3868
- exports.forgeDateString = forgeDateString;
3869
- exports.forgeDateTimeString = forgeDateTimeString;
3870
- exports.forgeDriver = forgeDriver;
3871
- exports.forgeSystemTables = forgeSystemTables;
3872
- exports.forgeTimeString = forgeTimeString;
3873
- exports.forgeTimestampString = forgeTimestampString;
3874
- exports.formatDateTime = formatDateTime;
3875
- exports.formatLimitOffset = formatLimitOffset;
3876
- exports.generateDropTableStatements = generateDropTableStatements;
3877
- exports.getHttpResponse = getHttpResponse;
3878
- exports.getPrimaryKeys = getPrimaryKeys;
3879
- exports.getTableMetadata = getTableMetadata;
3880
- exports.getTables = getTables;
3881
- exports.isUpdateQueryResponse = isUpdateQueryResponse;
3882
- exports.mapSelectAllFieldsToAlias = mapSelectAllFieldsToAlias;
3883
- exports.mapSelectFieldsWithAlias = mapSelectFieldsWithAlias;
3884
- exports.migrations = migrations;
3885
- exports.nextVal = nextVal;
3886
- exports.parseDateTime = parseDateTime;
3887
- exports.patchDbWithSelectAliased = patchDbWithSelectAliased;
3888
- exports.printQueriesWithPlan = printQueriesWithPlan;
3889
- exports.slowQuery = slowQuery;
3890
- exports.slowQueryPerHours = slowQueryPerHours;
3891
- exports.slowQuerySchedulerTrigger = slowQuerySchedulerTrigger;
3892
- exports.statementsSummary = statementsSummary;
3893
- exports.statementsSummaryHistory = statementsSummaryHistory;
3894
- exports.withTidbHint = withTidbHint;
3895
- exports.withTimeout = withTimeout;
3896
- //# sourceMappingURL=ForgeSQLORM.js.map