forge-sql-orm 2.1.3 → 2.1.5

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 (52) hide show
  1. package/README.md +291 -0
  2. package/dist/ForgeSQLORM.js +713 -12
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +716 -15
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  7. package/dist/core/ForgeSQLORM.d.ts +23 -0
  8. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  12. package/dist/core/SystemTables.d.ts +5039 -0
  13. package/dist/core/SystemTables.d.ts.map +1 -1
  14. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  15. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  16. package/dist/utils/cacheUtils.d.ts.map +1 -1
  17. package/dist/utils/forgeDriver.d.ts +21 -0
  18. package/dist/utils/forgeDriver.d.ts.map +1 -1
  19. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  20. package/dist/utils/metadataContextUtils.d.ts +11 -0
  21. package/dist/utils/metadataContextUtils.d.ts.map +1 -0
  22. package/dist/utils/sqlUtils.d.ts.map +1 -1
  23. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts +1 -1
  24. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  25. package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
  26. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  27. package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
  28. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
  29. package/dist/webtriggers/index.d.ts +1 -0
  30. package/dist/webtriggers/index.d.ts.map +1 -1
  31. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +89 -0
  32. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -0
  33. package/package.json +4 -3
  34. package/src/core/ForgeSQLCrudOperations.ts +3 -0
  35. package/src/core/ForgeSQLORM.ts +119 -51
  36. package/src/core/ForgeSQLQueryBuilder.ts +51 -17
  37. package/src/core/ForgeSQLSelectOperations.ts +2 -0
  38. package/src/core/SystemTables.ts +313 -1
  39. package/src/lib/drizzle/extensions/additionalActions.ts +2 -0
  40. package/src/utils/cacheContextUtils.ts +4 -2
  41. package/src/utils/cacheUtils.ts +20 -8
  42. package/src/utils/forgeDriver.ts +22 -1
  43. package/src/utils/forgeDriverProxy.ts +2 -0
  44. package/src/utils/metadataContextUtils.ts +24 -0
  45. package/src/utils/sqlUtils.ts +1 -0
  46. package/src/webtriggers/applyMigrationsWebTrigger.ts +10 -5
  47. package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
  48. package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
  49. package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
  50. package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
  51. package/src/webtriggers/index.ts +1 -0
  52. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +360 -0
@@ -410,7 +410,7 @@ async function clearCursorCache(tables, cursor, options) {
410
410
  entityQueryBuilder = entityQueryBuilder.cursor(cursor);
411
411
  }
412
412
  const listResult = await entityQueryBuilder.limit(100).getMany();
413
- if (options.logRawSqlQuery) {
413
+ if (options.logCache) {
414
414
  console.warn(`clear cache Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
415
415
  }
416
416
  await deleteCacheEntriesInBatches(listResult.results, cacheEntityName);
@@ -431,7 +431,7 @@ async function clearExpirationCursorCache(cursor, options) {
431
431
  entityQueryBuilder = entityQueryBuilder.cursor(cursor);
432
432
  }
433
433
  const listResult = await entityQueryBuilder.limit(100).getMany();
434
- if (options.logRawSqlQuery) {
434
+ if (options.logCache) {
435
435
  console.warn(`clear expired Records: ${JSON.stringify(listResult.results.map((r) => r.key))}`);
436
436
  }
437
437
  await deleteCacheEntriesInBatches(listResult.results, cacheEntityName);
@@ -480,7 +480,7 @@ async function clearTablesCache(tables, options) {
480
480
  "clearing cache"
481
481
  );
482
482
  } finally {
483
- if (options.logRawSqlQuery) {
483
+ if (options.logCache) {
484
484
  const duration = luxon.DateTime.now().toSeconds() - startTime.toSeconds();
485
485
  console.info(`Cleared ${totalRecords} cache records in ${duration} seconds`);
486
486
  }
@@ -499,7 +499,7 @@ async function clearExpiredCache(options) {
499
499
  );
500
500
  } finally {
501
501
  const duration = luxon.DateTime.now().toSeconds() - startTime.toSeconds();
502
- if (options?.logRawSqlQuery) {
502
+ if (options?.logCache) {
503
503
  console.debug(`Cleared ${totalRecords} expired cache records in ${duration} seconds`);
504
504
  }
505
505
  }
@@ -514,7 +514,7 @@ async function getFromCache(query, options) {
514
514
  const sqlQuery = query.toSQL();
515
515
  const key = hashKey(sqlQuery);
516
516
  if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
517
- if (options.logRawSqlQuery) {
517
+ if (options.logCache) {
518
518
  console.warn(`Context contains value to clear. Skip getting from cache`);
519
519
  }
520
520
  return void 0;
@@ -522,7 +522,7 @@ async function getFromCache(query, options) {
522
522
  try {
523
523
  const cacheResult = await kvs.kvs.entity(options.cacheEntityName).get(key);
524
524
  if (cacheResult && cacheResult[expirationName] >= getCurrentTime() && sqlQuery.sql.toLowerCase() === cacheResult[entityQueryName]) {
525
- if (options.logRawSqlQuery) {
525
+ if (options.logCache) {
526
526
  console.warn(`Get value from cache, cacheKey: ${key}`);
527
527
  }
528
528
  const results = cacheResult[dataName];
@@ -543,7 +543,7 @@ async function setCacheResult(query, options, results, cacheTtl) {
543
543
  const dataName = options.cacheEntityDataName ?? CACHE_CONSTANTS.DEFAULT_DATA_NAME;
544
544
  const sqlQuery = query.toSQL();
545
545
  if (await isTableContainsTableInCacheContext(sqlQuery.sql, options)) {
546
- if (options.logRawSqlQuery) {
546
+ if (options.logCache) {
547
547
  console.warn(`Context contains value to clear. Skip setting from cache`);
548
548
  }
549
549
  return;
@@ -558,7 +558,7 @@ async function setCacheResult(query, options, results, cacheTtl) {
558
558
  },
559
559
  { entityName: options.cacheEntityName }
560
560
  ).execute();
561
- if (options.logRawSqlQuery) {
561
+ if (options.logCache) {
562
562
  console.warn(`Store value to cache, cacheKey: ${key}`);
563
563
  }
564
564
  } catch (error) {
@@ -586,7 +586,7 @@ async function saveQueryLocalCacheQuery(query, rows, options) {
586
586
  sql: sql2.toSQL().sql.toLowerCase(),
587
587
  data: rows
588
588
  };
589
- if (options.logRawSqlQuery) {
589
+ if (options.logCache) {
590
590
  const q = sql2.toSQL();
591
591
  console.debug(
592
592
  `[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`
@@ -603,7 +603,7 @@ async function getQueryLocalCacheQuery(query, options) {
603
603
  const sql2 = query;
604
604
  const key = hashKey(sql2.toSQL());
605
605
  if (context.cache[key] && context.cache[key].sql === sql2.toSQL().sql.toLowerCase()) {
606
- if (options.logRawSqlQuery) {
606
+ if (options.logCache) {
607
607
  const q = sql2.toSQL();
608
608
  console.debug(
609
609
  `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`
@@ -1034,6 +1034,18 @@ class ForgeSQLSelectOperations {
1034
1034
  return updateQueryResponseResults.rows;
1035
1035
  }
1036
1036
  }
1037
+ const metadataQueryContext = new node_async_hooks.AsyncLocalStorage();
1038
+ async function saveMetaDataInContextContext(metadata) {
1039
+ const context = metadataQueryContext.getStore();
1040
+ if (context && metadata) {
1041
+ context.totalResponseSize += metadata.responseSize;
1042
+ context.totalDbExecutionTime += metadata.dbExecutionTime;
1043
+ context.lastMetadata = metadata;
1044
+ }
1045
+ }
1046
+ async function getLastestMetadata() {
1047
+ return metadataQueryContext.getStore();
1048
+ }
1037
1049
  const forgeDriver = async (query, params, method) => {
1038
1050
  if (method == "execute") {
1039
1051
  const sqlStatement = sql$1.sql.prepare(query);
@@ -1049,6 +1061,7 @@ const forgeDriver = async (query, params, method) => {
1049
1061
  await sqlStatement.bindParams(...params);
1050
1062
  }
1051
1063
  const result = await sqlStatement.execute();
1064
+ await saveMetaDataInContextContext(result.metadata);
1052
1065
  let rows;
1053
1066
  rows = result.rows.map((r) => Object.values(r));
1054
1067
  return { rows };
@@ -1798,6 +1811,7 @@ class ForgeSQLORMImpl {
1798
1811
  try {
1799
1812
  const newOptions = options ?? {
1800
1813
  logRawSqlQuery: false,
1814
+ logCache: false,
1801
1815
  disableOptimisticLocking: false,
1802
1816
  cacheWrapTable: true,
1803
1817
  cacheTTL: 120,
@@ -1823,6 +1837,42 @@ class ForgeSQLORMImpl {
1823
1837
  throw error;
1824
1838
  }
1825
1839
  }
1840
+ /**
1841
+ * Executes a query and provides access to execution metadata.
1842
+ * This method allows you to capture detailed information about query execution
1843
+ * including database execution time, response size, and Forge SQL metadata.
1844
+ *
1845
+ * @template T - The return type of the query
1846
+ * @param query - A function that returns a Promise with the query result
1847
+ * @param onMetadata - Callback function that receives execution metadata
1848
+ * @returns Promise with the query result
1849
+ * @example
1850
+ * ```typescript
1851
+ * const result = await forgeSQL.executeWithMetadata(
1852
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
1853
+ * (dbTime, responseSize, metadata) => {
1854
+ * console.log(`DB execution time: ${dbTime}ms`);
1855
+ * console.log(`Response size: ${responseSize} bytes`);
1856
+ * console.log('Forge metadata:', metadata);
1857
+ * }
1858
+ * );
1859
+ * ```
1860
+ */
1861
+ async executeWithMetadata(query, onMetadata) {
1862
+ return metadataQueryContext.run({
1863
+ totalDbExecutionTime: 0,
1864
+ totalResponseSize: 0
1865
+ }, async () => {
1866
+ try {
1867
+ return await query();
1868
+ } finally {
1869
+ const metadata = await getLastestMetadata();
1870
+ if (metadata && metadata.lastMetadata) {
1871
+ await onMetadata(metadata.totalDbExecutionTime, metadata.totalResponseSize, metadata.lastMetadata);
1872
+ }
1873
+ }
1874
+ });
1875
+ }
1826
1876
  /**
1827
1877
  * Executes operations within a cache context that collects cache eviction events.
1828
1878
  * All clearCache calls within the context are collected and executed in batch at the end.
@@ -2270,6 +2320,30 @@ class ForgeSQLORM {
2270
2320
  constructor(options) {
2271
2321
  this.ormInstance = ForgeSQLORMImpl.getInstance(options);
2272
2322
  }
2323
+ /**
2324
+ * Executes a query and provides access to execution metadata.
2325
+ * This method allows you to capture detailed information about query execution
2326
+ * including database execution time, response size, and Forge SQL metadata.
2327
+ *
2328
+ * @template T - The return type of the query
2329
+ * @param query - A function that returns a Promise with the query result
2330
+ * @param onMetadata - Callback function that receives execution metadata
2331
+ * @returns Promise with the query result
2332
+ * @example
2333
+ * ```typescript
2334
+ * const result = await forgeSQL.executeWithMetadata(
2335
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
2336
+ * (dbTime, responseSize, metadata) => {
2337
+ * console.log(`DB execution time: ${dbTime}ms`);
2338
+ * console.log(`Response size: ${responseSize} bytes`);
2339
+ * console.log('Forge metadata:', metadata);
2340
+ * }
2341
+ * );
2342
+ * ```
2343
+ */
2344
+ async executeWithMetadata(query, onMetadata) {
2345
+ return this.ormInstance.executeWithMetadata(query, onMetadata);
2346
+ }
2273
2347
  selectCacheable(fields, cacheTTL) {
2274
2348
  return this.ormInstance.selectCacheable(fields, cacheTTL);
2275
2349
  }
@@ -2642,6 +2716,435 @@ const migrations = mysqlCore.mysqlTable("__migrations", {
2642
2716
  name: mysqlCore.varchar("name", { length: 255 }).notNull(),
2643
2717
  migratedAt: mysqlCore.timestamp("migratedAt").defaultNow().notNull()
2644
2718
  });
2719
+ const informationSchema = mysqlCore.mysqlSchema("information_schema");
2720
+ const slowQuery = informationSchema.table("SLOW_QUERY", {
2721
+ time: mysqlCore.timestamp("Time", { fsp: 6, mode: "string" }).notNull(),
2722
+ // Timestamp when the slow query was recorded
2723
+ txnStartTs: mysqlCore.bigint("Txn_start_ts", { mode: "bigint", unsigned: true }),
2724
+ // Transaction start timestamp (TSO)
2725
+ user: mysqlCore.varchar("User", { length: 64 }),
2726
+ // User executing the query
2727
+ host: mysqlCore.varchar("Host", { length: 64 }),
2728
+ // Host from which the query originated
2729
+ connId: mysqlCore.bigint("Conn_ID", { mode: "bigint", unsigned: true }),
2730
+ // Connection ID
2731
+ sessionAlias: mysqlCore.varchar("Session_alias", { length: 64 }),
2732
+ // Session alias
2733
+ execRetryCount: mysqlCore.bigint("Exec_retry_count", { mode: "bigint", unsigned: true }),
2734
+ // Number of retries during execution
2735
+ execRetryTime: mysqlCore.double("Exec_retry_time"),
2736
+ // Time spent in retries
2737
+ queryTime: mysqlCore.double("Query_time"),
2738
+ // Total execution time
2739
+ parseTime: mysqlCore.double("Parse_time"),
2740
+ // Time spent parsing SQL
2741
+ compileTime: mysqlCore.double("Compile_time"),
2742
+ // Time spent compiling query plan
2743
+ rewriteTime: mysqlCore.double("Rewrite_time"),
2744
+ // Time spent rewriting query
2745
+ preprocSubqueries: mysqlCore.bigint("Preproc_subqueries", { mode: "bigint", unsigned: true }),
2746
+ // Number of subqueries preprocessed
2747
+ preprocSubqueriesTime: mysqlCore.double("Preproc_subqueries_time"),
2748
+ // Time spent preprocessing subqueries
2749
+ optimizeTime: mysqlCore.double("Optimize_time"),
2750
+ // Time spent in optimizer
2751
+ waitTs: mysqlCore.double("Wait_TS"),
2752
+ // Wait time for getting TSO
2753
+ prewriteTime: mysqlCore.double("Prewrite_time"),
2754
+ // Time spent in prewrite phase
2755
+ waitPrewriteBinlogTime: mysqlCore.double("Wait_prewrite_binlog_time"),
2756
+ // Time waiting for binlog prewrite
2757
+ commitTime: mysqlCore.double("Commit_time"),
2758
+ // Commit duration
2759
+ getCommitTsTime: mysqlCore.double("Get_commit_ts_time"),
2760
+ // Time waiting for commit TSO
2761
+ commitBackoffTime: mysqlCore.double("Commit_backoff_time"),
2762
+ // Backoff time during commit
2763
+ backoffTypes: mysqlCore.varchar("Backoff_types", { length: 64 }),
2764
+ // Types of backoff occurred
2765
+ resolveLockTime: mysqlCore.double("Resolve_lock_time"),
2766
+ // Time resolving locks
2767
+ localLatchWaitTime: mysqlCore.double("Local_latch_wait_time"),
2768
+ // Time waiting on local latch
2769
+ writeKeys: mysqlCore.bigint("Write_keys", { mode: "bigint" }),
2770
+ // Number of keys written
2771
+ writeSize: mysqlCore.bigint("Write_size", { mode: "bigint" }),
2772
+ // Amount of data written
2773
+ prewriteRegion: mysqlCore.bigint("Prewrite_region", { mode: "bigint" }),
2774
+ // Regions involved in prewrite
2775
+ txnRetry: mysqlCore.bigint("Txn_retry", { mode: "bigint" }),
2776
+ // Transaction retry count
2777
+ copTime: mysqlCore.double("Cop_time"),
2778
+ // Time spent in coprocessor
2779
+ processTime: mysqlCore.double("Process_time"),
2780
+ // Processing time
2781
+ waitTime: mysqlCore.double("Wait_time"),
2782
+ // Wait time in TiKV
2783
+ backoffTime: mysqlCore.double("Backoff_time"),
2784
+ // Backoff wait time
2785
+ lockKeysTime: mysqlCore.double("LockKeys_time"),
2786
+ // Time spent waiting for locks
2787
+ requestCount: mysqlCore.bigint("Request_count", { mode: "bigint", unsigned: true }),
2788
+ // Total number of requests
2789
+ totalKeys: mysqlCore.bigint("Total_keys", { mode: "bigint", unsigned: true }),
2790
+ // Total keys scanned
2791
+ processKeys: mysqlCore.bigint("Process_keys", { mode: "bigint", unsigned: true }),
2792
+ // Keys processed
2793
+ rocksdbDeleteSkippedCount: mysqlCore.bigint("Rocksdb_delete_skipped_count", {
2794
+ mode: "bigint",
2795
+ unsigned: true
2796
+ }),
2797
+ // RocksDB delete skips
2798
+ rocksdbKeySkippedCount: mysqlCore.bigint("Rocksdb_key_skipped_count", { mode: "bigint", unsigned: true }),
2799
+ // RocksDB key skips
2800
+ rocksdbBlockCacheHitCount: mysqlCore.bigint("Rocksdb_block_cache_hit_count", {
2801
+ mode: "bigint",
2802
+ unsigned: true
2803
+ }),
2804
+ // RocksDB block cache hits
2805
+ rocksdbBlockReadCount: mysqlCore.bigint("Rocksdb_block_read_count", { mode: "bigint", unsigned: true }),
2806
+ // RocksDB block reads
2807
+ rocksdbBlockReadByte: mysqlCore.bigint("Rocksdb_block_read_byte", { mode: "bigint", unsigned: true }),
2808
+ // RocksDB block read bytes
2809
+ db: mysqlCore.varchar("DB", { length: 64 }),
2810
+ // Database name
2811
+ indexNames: mysqlCore.varchar("Index_names", { length: 100 }),
2812
+ // Indexes used
2813
+ isInternal: mysqlCore.boolean("Is_internal"),
2814
+ // Whether the query is internal
2815
+ digest: mysqlCore.varchar("Digest", { length: 64 }),
2816
+ // SQL digest hash
2817
+ stats: mysqlCore.varchar("Stats", { length: 512 }),
2818
+ // Stats used during planning
2819
+ copProcAvg: mysqlCore.double("Cop_proc_avg"),
2820
+ // Coprocessor average processing time
2821
+ copProcP90: mysqlCore.double("Cop_proc_p90"),
2822
+ // Coprocessor 90th percentile processing time
2823
+ copProcMax: mysqlCore.double("Cop_proc_max"),
2824
+ // Coprocessor max processing time
2825
+ copProcAddr: mysqlCore.varchar("Cop_proc_addr", { length: 64 }),
2826
+ // Coprocessor address for processing
2827
+ copWaitAvg: mysqlCore.double("Cop_wait_avg"),
2828
+ // Coprocessor average wait time
2829
+ copWaitP90: mysqlCore.double("Cop_wait_p90"),
2830
+ // Coprocessor 90th percentile wait time
2831
+ copWaitMax: mysqlCore.double("Cop_wait_max"),
2832
+ // Coprocessor max wait time
2833
+ copWaitAddr: mysqlCore.varchar("Cop_wait_addr", { length: 64 }),
2834
+ // Coprocessor address for wait
2835
+ memMax: mysqlCore.bigint("Mem_max", { mode: "bigint" }),
2836
+ // Max memory usage
2837
+ diskMax: mysqlCore.bigint("Disk_max", { mode: "bigint" }),
2838
+ // Max disk usage
2839
+ kvTotal: mysqlCore.double("KV_total"),
2840
+ // Total KV request time
2841
+ pdTotal: mysqlCore.double("PD_total"),
2842
+ // Total PD request time
2843
+ backoffTotal: mysqlCore.double("Backoff_total"),
2844
+ // Total backoff time
2845
+ writeSqlResponseTotal: mysqlCore.double("Write_sql_response_total"),
2846
+ // SQL response write time
2847
+ resultRows: mysqlCore.bigint("Result_rows", { mode: "bigint" }),
2848
+ // Rows returned
2849
+ warnings: mysqlCore.longtext("Warnings"),
2850
+ // Warnings during execution
2851
+ backoffDetail: mysqlCore.varchar("Backoff_Detail", { length: 4096 }),
2852
+ // Detailed backoff info
2853
+ prepared: mysqlCore.boolean("Prepared"),
2854
+ // Whether query was prepared
2855
+ succ: mysqlCore.boolean("Succ"),
2856
+ // Success flag
2857
+ isExplicitTxn: mysqlCore.boolean("IsExplicitTxn"),
2858
+ // Whether explicit transaction
2859
+ isWriteCacheTable: mysqlCore.boolean("IsWriteCacheTable"),
2860
+ // Whether wrote to cache table
2861
+ planFromCache: mysqlCore.boolean("Plan_from_cache"),
2862
+ // Plan was from cache
2863
+ planFromBinding: mysqlCore.boolean("Plan_from_binding"),
2864
+ // Plan was from binding
2865
+ hasMoreResults: mysqlCore.boolean("Has_more_results"),
2866
+ // Query returned multiple results
2867
+ resourceGroup: mysqlCore.varchar("Resource_group", { length: 64 }),
2868
+ // Resource group name
2869
+ requestUnitRead: mysqlCore.double("Request_unit_read"),
2870
+ // RU consumed for read
2871
+ requestUnitWrite: mysqlCore.double("Request_unit_write"),
2872
+ // RU consumed for write
2873
+ timeQueuedByRc: mysqlCore.double("Time_queued_by_rc"),
2874
+ // Time queued by resource control
2875
+ tidbCpuTime: mysqlCore.double("Tidb_cpu_time"),
2876
+ // TiDB CPU time
2877
+ tikvCpuTime: mysqlCore.double("Tikv_cpu_time"),
2878
+ // TiKV CPU time
2879
+ plan: mysqlCore.longtext("Plan"),
2880
+ // Query execution plan
2881
+ planDigest: mysqlCore.varchar("Plan_digest", { length: 128 }),
2882
+ // Plan digest hash
2883
+ binaryPlan: mysqlCore.longtext("Binary_plan"),
2884
+ // Binary execution plan
2885
+ prevStmt: mysqlCore.longtext("Prev_stmt"),
2886
+ // Previous statement in session
2887
+ query: mysqlCore.longtext("Query")
2888
+ // Original SQL query
2889
+ });
2890
+ const createClusterStatementsSummarySchema = () => ({
2891
+ instance: mysqlCore.varchar("INSTANCE", { length: 64 }),
2892
+ // TiDB/TiKV instance address
2893
+ summaryBeginTime: mysqlCore.timestamp("SUMMARY_BEGIN_TIME", { mode: "string" }).notNull(),
2894
+ // Begin time of this summary window
2895
+ summaryEndTime: mysqlCore.timestamp("SUMMARY_END_TIME", { mode: "string" }).notNull(),
2896
+ // End time of this summary window
2897
+ stmtType: mysqlCore.varchar("STMT_TYPE", { length: 64 }).notNull(),
2898
+ // Statement type (e.g., Select/Insert/Update)
2899
+ schemaName: mysqlCore.varchar("SCHEMA_NAME", { length: 64 }),
2900
+ // Current schema name
2901
+ digest: mysqlCore.varchar("DIGEST", { length: 64 }),
2902
+ // SQL digest (normalized hash)
2903
+ digestText: mysqlCore.text("DIGEST_TEXT").notNull(),
2904
+ // Normalized SQL text
2905
+ tableNames: mysqlCore.text("TABLE_NAMES"),
2906
+ // Involved table names
2907
+ indexNames: mysqlCore.text("INDEX_NAMES"),
2908
+ // Used index names
2909
+ sampleUser: mysqlCore.varchar("SAMPLE_USER", { length: 64 }),
2910
+ // Sampled user who executed the statements
2911
+ execCount: mysqlCore.bigint("EXEC_COUNT", { mode: "bigint", unsigned: true }).notNull(),
2912
+ // Total executions
2913
+ sumErrors: mysqlCore.int("SUM_ERRORS", { unsigned: true }).notNull(),
2914
+ // Sum of errors
2915
+ sumWarnings: mysqlCore.int("SUM_WARNINGS", { unsigned: true }).notNull(),
2916
+ // Sum of warnings
2917
+ sumLatency: mysqlCore.bigint("SUM_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2918
+ // Sum of latency (ns)
2919
+ maxLatency: mysqlCore.bigint("MAX_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2920
+ // Max latency (ns)
2921
+ minLatency: mysqlCore.bigint("MIN_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2922
+ // Min latency (ns)
2923
+ avgLatency: mysqlCore.bigint("AVG_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2924
+ // Avg latency (ns)
2925
+ avgParseLatency: mysqlCore.bigint("AVG_PARSE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2926
+ // Avg parse time (ns)
2927
+ maxParseLatency: mysqlCore.bigint("MAX_PARSE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2928
+ // Max parse time (ns)
2929
+ avgCompileLatency: mysqlCore.bigint("AVG_COMPILE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2930
+ // Avg compile time (ns)
2931
+ maxCompileLatency: mysqlCore.bigint("MAX_COMPILE_LATENCY", { mode: "bigint", unsigned: true }).notNull(),
2932
+ // Max compile time (ns)
2933
+ sumCopTaskNum: mysqlCore.bigint("SUM_COP_TASK_NUM", { mode: "bigint", unsigned: true }).notNull(),
2934
+ // Total number of cop tasks
2935
+ maxCopProcessTime: mysqlCore.bigint("MAX_COP_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
2936
+ // Max TiKV coprocessor processing time (ns)
2937
+ maxCopProcessAddress: mysqlCore.varchar("MAX_COP_PROCESS_ADDRESS", { length: 256 }),
2938
+ // Address of cop task with max processing time
2939
+ maxCopWaitTime: mysqlCore.bigint("MAX_COP_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
2940
+ // Max TiKV coprocessor wait time (ns)
2941
+ maxCopWaitAddress: mysqlCore.varchar("MAX_COP_WAIT_ADDRESS", { length: 256 }),
2942
+ // Address of cop task with max wait time
2943
+ avgProcessTime: mysqlCore.bigint("AVG_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
2944
+ // Avg TiKV processing time (ns)
2945
+ maxProcessTime: mysqlCore.bigint("MAX_PROCESS_TIME", { mode: "bigint", unsigned: true }).notNull(),
2946
+ // Max TiKV processing time (ns)
2947
+ avgWaitTime: mysqlCore.bigint("AVG_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
2948
+ // Avg TiKV wait time (ns)
2949
+ maxWaitTime: mysqlCore.bigint("MAX_WAIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
2950
+ // Max TiKV wait time (ns)
2951
+ avgBackoffTime: mysqlCore.bigint("AVG_BACKOFF_TIME", { mode: "bigint", unsigned: true }).notNull(),
2952
+ // Avg backoff time before retry (ns)
2953
+ maxBackoffTime: mysqlCore.bigint("MAX_BACKOFF_TIME", { mode: "bigint", unsigned: true }).notNull(),
2954
+ // Max backoff time before retry (ns)
2955
+ avgTotalKeys: mysqlCore.bigint("AVG_TOTAL_KEYS", { mode: "bigint", unsigned: true }).notNull(),
2956
+ // Avg scanned keys
2957
+ maxTotalKeys: mysqlCore.bigint("MAX_TOTAL_KEYS", { mode: "bigint", unsigned: true }).notNull(),
2958
+ // Max scanned keys
2959
+ avgProcessedKeys: mysqlCore.bigint("AVG_PROCESSED_KEYS", { mode: "bigint", unsigned: true }).notNull(),
2960
+ // Avg processed keys
2961
+ maxProcessedKeys: mysqlCore.bigint("MAX_PROCESSED_KEYS", { mode: "bigint", unsigned: true }).notNull(),
2962
+ // Max processed keys
2963
+ avgRocksdbDeleteSkippedCount: mysqlCore.double("AVG_ROCKSDB_DELETE_SKIPPED_COUNT").notNull(),
2964
+ // Avg RocksDB deletes skipped
2965
+ maxRocksdbDeleteSkippedCount: mysqlCore.int("MAX_ROCKSDB_DELETE_SKIPPED_COUNT", {
2966
+ unsigned: true
2967
+ }).notNull(),
2968
+ // Max RocksDB deletes skipped
2969
+ avgRocksdbKeySkippedCount: mysqlCore.double("AVG_ROCKSDB_KEY_SKIPPED_COUNT").notNull(),
2970
+ // Avg RocksDB keys skipped
2971
+ maxRocksdbKeySkippedCount: mysqlCore.int("MAX_ROCKSDB_KEY_SKIPPED_COUNT", { unsigned: true }).notNull(),
2972
+ // Max RocksDB keys skipped
2973
+ avgRocksdbBlockCacheHitCount: mysqlCore.double("AVG_ROCKSDB_BLOCK_CACHE_HIT_COUNT").notNull(),
2974
+ // Avg RocksDB block cache hits
2975
+ maxRocksdbBlockCacheHitCount: mysqlCore.int("MAX_ROCKSDB_BLOCK_CACHE_HIT_COUNT", {
2976
+ unsigned: true
2977
+ }).notNull(),
2978
+ // Max RocksDB block cache hits
2979
+ avgRocksdbBlockReadCount: mysqlCore.double("AVG_ROCKSDB_BLOCK_READ_COUNT").notNull(),
2980
+ // Avg RocksDB block reads
2981
+ maxRocksdbBlockReadCount: mysqlCore.int("MAX_ROCKSDB_BLOCK_READ_COUNT", { unsigned: true }).notNull(),
2982
+ // Max RocksDB block reads
2983
+ avgRocksdbBlockReadByte: mysqlCore.double("AVG_ROCKSDB_BLOCK_READ_BYTE").notNull(),
2984
+ // Avg RocksDB block read bytes
2985
+ maxRocksdbBlockReadByte: mysqlCore.int("MAX_ROCKSDB_BLOCK_READ_BYTE", { unsigned: true }).notNull(),
2986
+ // Max RocksDB block read bytes
2987
+ avgPrewriteTime: mysqlCore.bigint("AVG_PREWRITE_TIME", { mode: "bigint", unsigned: true }).notNull(),
2988
+ // Avg prewrite phase time (ns)
2989
+ maxPrewriteTime: mysqlCore.bigint("MAX_PREWRITE_TIME", { mode: "bigint", unsigned: true }).notNull(),
2990
+ // Max prewrite phase time (ns)
2991
+ avgCommitTime: mysqlCore.bigint("AVG_COMMIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
2992
+ // Avg commit phase time (ns)
2993
+ maxCommitTime: mysqlCore.bigint("MAX_COMMIT_TIME", { mode: "bigint", unsigned: true }).notNull(),
2994
+ // Max commit phase time (ns)
2995
+ avgGetCommitTsTime: mysqlCore.bigint("AVG_GET_COMMIT_TS_TIME", {
2996
+ mode: "bigint",
2997
+ unsigned: true
2998
+ }).notNull(),
2999
+ // Avg get commit_ts time (ns)
3000
+ maxGetCommitTsTime: mysqlCore.bigint("MAX_GET_COMMIT_TS_TIME", {
3001
+ mode: "bigint",
3002
+ unsigned: true
3003
+ }).notNull(),
3004
+ // Max get commit_ts time (ns)
3005
+ avgCommitBackoffTime: mysqlCore.bigint("AVG_COMMIT_BACKOFF_TIME", {
3006
+ mode: "bigint",
3007
+ unsigned: true
3008
+ }).notNull(),
3009
+ // Avg backoff during commit (ns)
3010
+ maxCommitBackoffTime: mysqlCore.bigint("MAX_COMMIT_BACKOFF_TIME", {
3011
+ mode: "bigint",
3012
+ unsigned: true
3013
+ }).notNull(),
3014
+ // Max backoff during commit (ns)
3015
+ avgResolveLockTime: mysqlCore.bigint("AVG_RESOLVE_LOCK_TIME", {
3016
+ mode: "bigint",
3017
+ unsigned: true
3018
+ }).notNull(),
3019
+ // Avg resolve lock time (ns)
3020
+ maxResolveLockTime: mysqlCore.bigint("MAX_RESOLVE_LOCK_TIME", {
3021
+ mode: "bigint",
3022
+ unsigned: true
3023
+ }).notNull(),
3024
+ // Max resolve lock time (ns)
3025
+ avgLocalLatchWaitTime: mysqlCore.bigint("AVG_LOCAL_LATCH_WAIT_TIME", {
3026
+ mode: "bigint",
3027
+ unsigned: true
3028
+ }).notNull(),
3029
+ // Avg local latch wait (ns)
3030
+ maxLocalLatchWaitTime: mysqlCore.bigint("MAX_LOCAL_LATCH_WAIT_TIME", {
3031
+ mode: "bigint",
3032
+ unsigned: true
3033
+ }).notNull(),
3034
+ // Max local latch wait (ns)
3035
+ avgWriteKeys: mysqlCore.double("AVG_WRITE_KEYS").notNull(),
3036
+ // Avg number of written keys
3037
+ maxWriteKeys: mysqlCore.bigint("MAX_WRITE_KEYS", { mode: "bigint", unsigned: true }).notNull(),
3038
+ // Max written keys
3039
+ avgWriteSize: mysqlCore.double("AVG_WRITE_SIZE").notNull(),
3040
+ // Avg written bytes
3041
+ maxWriteSize: mysqlCore.bigint("MAX_WRITE_SIZE", { mode: "bigint", unsigned: true }).notNull(),
3042
+ // Max written bytes
3043
+ avgPrewriteRegions: mysqlCore.double("AVG_PREWRITE_REGIONS").notNull(),
3044
+ // Avg regions in prewrite
3045
+ maxPrewriteRegions: mysqlCore.int("MAX_PREWRITE_REGIONS", { unsigned: true }).notNull(),
3046
+ // Max regions in prewrite
3047
+ avgTxnRetry: mysqlCore.double("AVG_TXN_RETRY").notNull(),
3048
+ // Avg transaction retry count
3049
+ maxTxnRetry: mysqlCore.int("MAX_TXN_RETRY", { unsigned: true }).notNull(),
3050
+ // Max transaction retry count
3051
+ sumExecRetry: mysqlCore.bigint("SUM_EXEC_RETRY", { mode: "bigint", unsigned: true }).notNull(),
3052
+ // Sum of execution retries (pessimistic)
3053
+ sumExecRetryTime: mysqlCore.bigint("SUM_EXEC_RETRY_TIME", { mode: "bigint", unsigned: true }).notNull(),
3054
+ // Sum time of execution retries (ns)
3055
+ sumBackoffTimes: mysqlCore.bigint("SUM_BACKOFF_TIMES", { mode: "bigint", unsigned: true }).notNull(),
3056
+ // Sum of backoff retries
3057
+ backoffTypes: mysqlCore.varchar("BACKOFF_TYPES", { length: 1024 }),
3058
+ // Backoff types with counts
3059
+ avgMem: mysqlCore.bigint("AVG_MEM", { mode: "bigint", unsigned: true }).notNull(),
3060
+ // Avg memory used (bytes)
3061
+ maxMem: mysqlCore.bigint("MAX_MEM", { mode: "bigint", unsigned: true }).notNull(),
3062
+ // Max memory used (bytes)
3063
+ avgDisk: mysqlCore.bigint("AVG_DISK", { mode: "bigint", unsigned: true }).notNull(),
3064
+ // Avg disk used (bytes)
3065
+ maxDisk: mysqlCore.bigint("MAX_DISK", { mode: "bigint", unsigned: true }).notNull(),
3066
+ // Max disk used (bytes)
3067
+ avgKvTime: mysqlCore.bigint("AVG_KV_TIME", { mode: "bigint", unsigned: true }).notNull(),
3068
+ // Avg time spent in TiKV (ns)
3069
+ avgPdTime: mysqlCore.bigint("AVG_PD_TIME", { mode: "bigint", unsigned: true }).notNull(),
3070
+ // Avg time spent in PD (ns)
3071
+ avgBackoffTotalTime: mysqlCore.bigint("AVG_BACKOFF_TOTAL_TIME", {
3072
+ mode: "bigint",
3073
+ unsigned: true
3074
+ }).notNull(),
3075
+ // Avg total backoff time (ns)
3076
+ avgWriteSqlRespTime: mysqlCore.bigint("AVG_WRITE_SQL_RESP_TIME", {
3077
+ mode: "bigint",
3078
+ unsigned: true
3079
+ }).notNull(),
3080
+ // Avg write SQL response time (ns)
3081
+ avgTidbCpuTime: mysqlCore.bigint("AVG_TIDB_CPU_TIME", { mode: "bigint", unsigned: true }).notNull(),
3082
+ // Avg TiDB CPU time (ns)
3083
+ avgTikvCpuTime: mysqlCore.bigint("AVG_TIKV_CPU_TIME", { mode: "bigint", unsigned: true }).notNull(),
3084
+ // Avg TiKV CPU time (ns)
3085
+ maxResultRows: mysqlCore.bigint("MAX_RESULT_ROWS", { mode: "bigint" }).notNull(),
3086
+ // Max number of result rows
3087
+ minResultRows: mysqlCore.bigint("MIN_RESULT_ROWS", { mode: "bigint" }).notNull(),
3088
+ // Min number of result rows
3089
+ avgResultRows: mysqlCore.bigint("AVG_RESULT_ROWS", { mode: "bigint" }).notNull(),
3090
+ // Avg number of result rows
3091
+ prepared: mysqlCore.boolean("PREPARED").notNull(),
3092
+ // Whether statements are prepared
3093
+ avgAffectedRows: mysqlCore.double("AVG_AFFECTED_ROWS").notNull(),
3094
+ // Avg affected rows
3095
+ firstSeen: mysqlCore.timestamp("FIRST_SEEN", { mode: "string" }).notNull(),
3096
+ // First time statements observed
3097
+ lastSeen: mysqlCore.timestamp("LAST_SEEN", { mode: "string" }).notNull(),
3098
+ // Last time statements observed
3099
+ planInCache: mysqlCore.boolean("PLAN_IN_CACHE").notNull(),
3100
+ // Whether last stmt hit plan cache
3101
+ planCacheHits: mysqlCore.bigint("PLAN_CACHE_HITS", { mode: "bigint" }).notNull(),
3102
+ // Number of plan cache hits
3103
+ planInBinding: mysqlCore.boolean("PLAN_IN_BINDING").notNull(),
3104
+ // Whether matched bindings
3105
+ querySampleText: mysqlCore.text("QUERY_SAMPLE_TEXT"),
3106
+ // Sampled original SQL
3107
+ prevSampleText: mysqlCore.text("PREV_SAMPLE_TEXT"),
3108
+ // Sampled previous SQL before commit
3109
+ planDigest: mysqlCore.varchar("PLAN_DIGEST", { length: 64 }),
3110
+ // Plan digest hash
3111
+ plan: mysqlCore.text("PLAN"),
3112
+ // Sampled textual plan
3113
+ binaryPlan: mysqlCore.text("BINARY_PLAN"),
3114
+ // Sampled binary plan
3115
+ charset: mysqlCore.varchar("CHARSET", { length: 64 }),
3116
+ // Sampled charset
3117
+ collation: mysqlCore.varchar("COLLATION", { length: 64 }),
3118
+ // Sampled collation
3119
+ planHint: mysqlCore.varchar("PLAN_HINT", { length: 64 }),
3120
+ // Sampled plan hint
3121
+ maxRequestUnitRead: mysqlCore.double("MAX_REQUEST_UNIT_READ").notNull(),
3122
+ // Max RU cost (read)
3123
+ avgRequestUnitRead: mysqlCore.double("AVG_REQUEST_UNIT_READ").notNull(),
3124
+ // Avg RU cost (read)
3125
+ maxRequestUnitWrite: mysqlCore.double("MAX_REQUEST_UNIT_WRITE").notNull(),
3126
+ // Max RU cost (write)
3127
+ avgRequestUnitWrite: mysqlCore.double("AVG_REQUEST_UNIT_WRITE").notNull(),
3128
+ // Avg RU cost (write)
3129
+ maxQueuedRcTime: mysqlCore.bigint("MAX_QUEUED_RC_TIME", { mode: "bigint", unsigned: true }).notNull(),
3130
+ // Max queued time waiting for RU (ns)
3131
+ avgQueuedRcTime: mysqlCore.bigint("AVG_QUEUED_RC_TIME", { mode: "bigint", unsigned: true }).notNull(),
3132
+ // Avg queued time waiting for RU (ns)
3133
+ resourceGroup: mysqlCore.varchar("RESOURCE_GROUP", { length: 64 }),
3134
+ // Bound resource group name
3135
+ planCacheUnqualified: mysqlCore.bigint("PLAN_CACHE_UNQUALIFIED", { mode: "bigint" }).notNull(),
3136
+ // Times not eligible for plan cache
3137
+ planCacheUnqualifiedLastReason: mysqlCore.text("PLAN_CACHE_UNQUALIFIED_LAST_REASON")
3138
+ // Last reason of plan cache ineligibility
3139
+ });
3140
+ const clusterStatementsSummaryHistory = informationSchema.table(
3141
+ "CLUSTER_STATEMENTS_SUMMARY_HISTORY",
3142
+ createClusterStatementsSummarySchema()
3143
+ );
3144
+ const clusterStatementsSummary = informationSchema.table(
3145
+ "CLUSTER_STATEMENTS_SUMMARY",
3146
+ createClusterStatementsSummarySchema()
3147
+ );
2645
3148
  async function getTables() {
2646
3149
  const tables = await sql$1.sql.executeDDL("SHOW TABLES");
2647
3150
  return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
@@ -2692,13 +3195,13 @@ const applySchemaMigrations = async (migration) => {
2692
3195
  body: "Migrations successfully executed"
2693
3196
  };
2694
3197
  } catch (error) {
2695
- const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred";
3198
+ 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";
2696
3199
  console.error("Error during migration:", errorMessage);
2697
3200
  return {
2698
3201
  headers: { "Content-Type": ["application/json"] },
2699
3202
  statusCode: 500,
2700
3203
  statusText: "Internal Server Error",
2701
- body: error instanceof Error ? error.message : "Unknown error during migration"
3204
+ body: error instanceof Error ? errorMessage : "Unknown error during migration"
2702
3205
  };
2703
3206
  }
2704
3207
  };
@@ -2789,6 +3292,200 @@ const clearCacheSchedulerTrigger = async (options) => {
2789
3292
  };
2790
3293
  }
2791
3294
  };
3295
+ const DEFAULT_MEMORY_THRESHOLD = 8 * 1024 * 1024;
3296
+ const DEFAULT_TIMEOUT = 300;
3297
+ const topSlowestStatementLastHourTrigger = async (orm, options) => {
3298
+ if (!orm) {
3299
+ return {
3300
+ statusCode: 500,
3301
+ headers: { "Content-Type": ["application/json"] },
3302
+ body: JSON.stringify({
3303
+ success: false,
3304
+ message: "ORM instance is required",
3305
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3306
+ })
3307
+ };
3308
+ }
3309
+ let newOptions = options ?? {
3310
+ warnThresholdMs: DEFAULT_TIMEOUT,
3311
+ memoryThresholdBytes: DEFAULT_MEMORY_THRESHOLD,
3312
+ showPlan: false
3313
+ };
3314
+ const nsToMs = (v) => {
3315
+ const n = Number(v);
3316
+ return Number.isFinite(n) ? n / 1e6 : NaN;
3317
+ };
3318
+ const bytesToMB = (v) => {
3319
+ const n = Number(v);
3320
+ return Number.isFinite(n) ? n / (1024 * 1024) : NaN;
3321
+ };
3322
+ const jsonSafeStringify = (value) => JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
3323
+ function sanitizeSQL(sql2, maxLen = 1e3) {
3324
+ let s = sql2;
3325
+ s = s.replace(/--[^\n\r]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
3326
+ s = s.replace(/'(?:\\'|[^'])*'/g, "?");
3327
+ s = s.replace(/\b-?\d+(?:\.\d+)?\b/g, "?");
3328
+ s = s.replace(/\s+/g, " ").trim();
3329
+ if (s.length > maxLen) {
3330
+ s = s.slice(0, maxLen) + " …[truncated]";
3331
+ }
3332
+ return s;
3333
+ }
3334
+ const TOP_N = 1;
3335
+ try {
3336
+ const summaryHistory = clusterStatementsSummaryHistory;
3337
+ const summary = clusterStatementsSummary;
3338
+ const selectShape = (t) => ({
3339
+ digest: t.digest,
3340
+ stmtType: t.stmtType,
3341
+ schemaName: t.schemaName,
3342
+ execCount: t.execCount,
3343
+ avgLatencyNs: t.avgLatency,
3344
+ maxLatencyNs: t.maxLatency,
3345
+ minLatencyNs: t.minLatency,
3346
+ avgProcessTimeNs: t.avgProcessTime,
3347
+ avgWaitTimeNs: t.avgWaitTime,
3348
+ avgBackoffTimeNs: t.avgBackoffTime,
3349
+ avgTotalKeys: t.avgTotalKeys,
3350
+ firstSeen: t.firstSeen,
3351
+ lastSeen: t.lastSeen,
3352
+ planInCache: t.planInCache,
3353
+ planCacheHits: t.planCacheHits,
3354
+ digestText: t.digestText,
3355
+ plan: t.plan,
3356
+ avgMemBytes: t.avgMem,
3357
+ maxMemBytes: t.maxMem
3358
+ });
3359
+ const lastHourFilterHistory = drizzleOrm.gte(
3360
+ summaryHistory.summaryEndTime,
3361
+ drizzleOrm.sql`DATE_SUB(NOW(), INTERVAL 1 HOUR)`
3362
+ );
3363
+ const lastHourFilterSummary = drizzleOrm.gte(
3364
+ summary.summaryEndTime,
3365
+ drizzleOrm.sql`DATE_SUB(NOW(), INTERVAL 1 HOUR)`
3366
+ );
3367
+ const qHistory = orm.getDrizzleQueryBuilder().select(selectShape(summaryHistory)).from(summaryHistory).where(lastHourFilterHistory);
3368
+ const qSummary = orm.getDrizzleQueryBuilder().select(selectShape(summary)).from(summary).where(lastHourFilterSummary);
3369
+ const combined = mysqlCore.unionAll(qHistory, qSummary).as("combined");
3370
+ const thresholdNs = Math.floor((newOptions.warnThresholdMs ?? DEFAULT_TIMEOUT) * 1e6);
3371
+ const memoryThresholdBytes = newOptions.memoryThresholdBytes ?? DEFAULT_MEMORY_THRESHOLD;
3372
+ const grouped = orm.getDrizzleQueryBuilder().select({
3373
+ digest: combined.digest,
3374
+ stmtType: combined.stmtType,
3375
+ schemaName: combined.schemaName,
3376
+ execCount: drizzleOrm.sql`SUM(${combined.execCount})`.as("execCount"),
3377
+ avgLatencyNs: drizzleOrm.sql`MAX(${combined.avgLatencyNs})`.as("avgLatencyNs"),
3378
+ maxLatencyNs: drizzleOrm.sql`MAX(${combined.maxLatencyNs})`.as("maxLatencyNs"),
3379
+ minLatencyNs: drizzleOrm.sql`MIN(${combined.minLatencyNs})`.as("minLatencyNs"),
3380
+ avgProcessTimeNs: drizzleOrm.sql`MAX(${combined.avgProcessTimeNs})`.as("avgProcessTimeNs"),
3381
+ avgWaitTimeNs: drizzleOrm.sql`MAX(${combined.avgWaitTimeNs})`.as("avgWaitTimeNs"),
3382
+ avgBackoffTimeNs: drizzleOrm.sql`MAX(${combined.avgBackoffTimeNs})`.as("avgBackoffTimeNs"),
3383
+ avgMemBytes: drizzleOrm.sql`MAX(${combined.avgMemBytes})`.as("avgMemBytes"),
3384
+ maxMemBytes: drizzleOrm.sql`MAX(${combined.maxMemBytes})`.as("maxMemBytes"),
3385
+ avgTotalKeys: drizzleOrm.sql`MAX(${combined.avgTotalKeys})`.as("avgTotalKeys"),
3386
+ firstSeen: drizzleOrm.sql`MIN(${combined.firstSeen})`.as("firstSeen"),
3387
+ lastSeen: drizzleOrm.sql`MAX(${combined.lastSeen})`.as("lastSeen"),
3388
+ planInCache: drizzleOrm.sql`MAX(${combined.planInCache})`.as("planInCache"),
3389
+ planCacheHits: drizzleOrm.sql`SUM(${combined.planCacheHits})`.as("planCacheHits"),
3390
+ // Prefer a non-empty sample text/plan via MAX; acceptable for de-dup
3391
+ digestText: drizzleOrm.sql`MAX(${combined.digestText})`.as("digestText"),
3392
+ plan: drizzleOrm.sql`MAX(${combined.plan})`.as("plan")
3393
+ }).from(combined).where(
3394
+ drizzleOrm.sql`COALESCE(${combined.digest}, '') <> '' AND COALESCE(${combined.digestText}, '') <> '' AND COALESCE(${combined.stmtType}, '') NOT IN ('Use','Set','Show')`
3395
+ ).groupBy(combined.digest, combined.stmtType, combined.schemaName).as("grouped");
3396
+ const rows = await orm.getDrizzleQueryBuilder().select({
3397
+ digest: grouped.digest,
3398
+ stmtType: grouped.stmtType,
3399
+ schemaName: grouped.schemaName,
3400
+ execCount: grouped.execCount,
3401
+ avgLatencyNs: grouped.avgLatencyNs,
3402
+ maxLatencyNs: grouped.maxLatencyNs,
3403
+ minLatencyNs: grouped.minLatencyNs,
3404
+ avgProcessTimeNs: grouped.avgProcessTimeNs,
3405
+ avgWaitTimeNs: grouped.avgWaitTimeNs,
3406
+ avgBackoffTimeNs: grouped.avgBackoffTimeNs,
3407
+ avgMemBytes: grouped.avgMemBytes,
3408
+ maxMemBytes: grouped.maxMemBytes,
3409
+ avgTotalKeys: grouped.avgTotalKeys,
3410
+ firstSeen: grouped.firstSeen,
3411
+ lastSeen: grouped.lastSeen,
3412
+ planInCache: grouped.planInCache,
3413
+ planCacheHits: grouped.planCacheHits,
3414
+ digestText: grouped.digestText,
3415
+ plan: grouped.plan
3416
+ }).from(grouped).where(
3417
+ drizzleOrm.sql`${grouped.avgLatencyNs} > ${thresholdNs} OR ${grouped.avgMemBytes} > ${memoryThresholdBytes}`
3418
+ ).orderBy(drizzleOrm.desc(grouped.avgLatencyNs)).limit(formatLimitOffset(TOP_N));
3419
+ const formatted = rows.map((r, i) => ({
3420
+ rank: i + 1,
3421
+ // 1-based rank in the top N
3422
+ digest: r.digest,
3423
+ stmtType: r.stmtType,
3424
+ schemaName: r.schemaName,
3425
+ execCount: r.execCount,
3426
+ avgLatencyMs: nsToMs(r.avgLatencyNs),
3427
+ // Convert ns to ms for readability
3428
+ maxLatencyMs: nsToMs(r.maxLatencyNs),
3429
+ minLatencyMs: nsToMs(r.minLatencyNs),
3430
+ avgProcessTimeMs: nsToMs(r.avgProcessTimeNs),
3431
+ avgWaitTimeMs: nsToMs(r.avgWaitTimeNs),
3432
+ avgBackoffTimeMs: nsToMs(r.avgBackoffTimeNs),
3433
+ avgMemMB: bytesToMB(r.avgMemBytes),
3434
+ maxMemMB: bytesToMB(r.maxMemBytes),
3435
+ avgMemBytes: r.avgMemBytes,
3436
+ maxMemBytes: r.maxMemBytes,
3437
+ avgTotalKeys: r.avgTotalKeys,
3438
+ firstSeen: r.firstSeen,
3439
+ lastSeen: r.lastSeen,
3440
+ planInCache: r.planInCache,
3441
+ planCacheHits: r.planCacheHits,
3442
+ digestText: sanitizeSQL(r.digestText),
3443
+ plan: newOptions.showPlan ? r.plan : void 0
3444
+ }));
3445
+ for (const f of formatted) {
3446
+ console.warn(
3447
+ `${f.rank}. ${f.stmtType} avg=${f.avgLatencyMs?.toFixed?.(2)}ms max=${f.maxLatencyMs?.toFixed?.(2)}ms mem≈${f.avgMemMB?.toFixed?.(2)}MB(max ${f.maxMemMB?.toFixed?.(2)}MB) exec=${f.execCount}
3448
+ digest=${f.digest}
3449
+ sql=${(f.digestText || "").slice(0, 300)}${f.digestText && f.digestText.length > 300 ? "…" : ""}`
3450
+ );
3451
+ if (newOptions.showPlan && f.plan) {
3452
+ console.warn(` full plan:
3453
+ ${f.plan}`);
3454
+ }
3455
+ }
3456
+ return {
3457
+ headers: { "Content-Type": ["application/json"] },
3458
+ statusCode: 200,
3459
+ statusText: "OK",
3460
+ body: jsonSafeStringify({
3461
+ success: true,
3462
+ window: "last_1h",
3463
+ top: TOP_N,
3464
+ warnThresholdMs: newOptions.warnThresholdMs,
3465
+ memoryThresholdBytes: newOptions.memoryThresholdBytes,
3466
+ showPlan: newOptions.showPlan,
3467
+ rows: formatted,
3468
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3469
+ })
3470
+ };
3471
+ } catch (error) {
3472
+ console.error(
3473
+ "Error in topSlowestStatementLastHourTrigger:",
3474
+ error?.cause?.context?.debug?.sqlMessage ?? error?.cause ?? error
3475
+ );
3476
+ return {
3477
+ headers: { "Content-Type": ["application/json"] },
3478
+ statusCode: 500,
3479
+ statusText: "Internal Server Error",
3480
+ body: jsonSafeStringify({
3481
+ success: false,
3482
+ message: "Failed to fetch or log slow queries",
3483
+ error: error?.cause?.context?.debug?.sqlMessage ?? error?.cause?.message,
3484
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3485
+ })
3486
+ };
3487
+ }
3488
+ };
2792
3489
  const getHttpResponse = (statusCode, body) => {
2793
3490
  let statusText = "";
2794
3491
  if (statusCode === 200) {
@@ -2808,6 +3505,8 @@ exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
2808
3505
  exports.applyFromDriverTransform = applyFromDriverTransform;
2809
3506
  exports.applySchemaMigrations = applySchemaMigrations;
2810
3507
  exports.clearCacheSchedulerTrigger = clearCacheSchedulerTrigger;
3508
+ exports.clusterStatementsSummary = clusterStatementsSummary;
3509
+ exports.clusterStatementsSummaryHistory = clusterStatementsSummaryHistory;
2811
3510
  exports.default = ForgeSQLORM;
2812
3511
  exports.dropSchemaMigrations = dropSchemaMigrations;
2813
3512
  exports.dropTableSchemaMigrations = dropTableSchemaMigrations;
@@ -2831,4 +3530,6 @@ exports.migrations = migrations;
2831
3530
  exports.nextVal = nextVal;
2832
3531
  exports.parseDateTime = parseDateTime;
2833
3532
  exports.patchDbWithSelectAliased = patchDbWithSelectAliased;
3533
+ exports.slowQuery = slowQuery;
3534
+ exports.topSlowestStatementLastHourTrigger = topSlowestStatementLastHourTrigger;
2834
3535
  //# sourceMappingURL=ForgeSQLORM.js.map