forge-sql-orm 2.1.4 → 2.1.6

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 +195 -27
  2. package/dist/ForgeSQLORM.js +632 -192
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +632 -192
  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 +114 -3
  8. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts +125 -7
  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 +3654 -0
  13. package/dist/core/SystemTables.d.ts.map +1 -1
  14. package/dist/lib/drizzle/extensions/additionalActions.d.ts +2 -2
  15. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  16. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  17. package/dist/utils/cacheUtils.d.ts.map +1 -1
  18. package/dist/utils/forgeDriver.d.ts +71 -3
  19. package/dist/utils/forgeDriver.d.ts.map +1 -1
  20. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  21. package/dist/utils/metadataContextUtils.d.ts +11 -0
  22. package/dist/utils/metadataContextUtils.d.ts.map +1 -0
  23. package/dist/utils/requestTypeContextUtils.d.ts +8 -0
  24. package/dist/utils/requestTypeContextUtils.d.ts.map +1 -0
  25. package/dist/utils/sqlUtils.d.ts.map +1 -1
  26. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  27. package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
  28. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  29. package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
  30. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
  31. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +85 -43
  32. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -1
  33. package/package.json +9 -9
  34. package/src/core/ForgeSQLCrudOperations.ts +3 -0
  35. package/src/core/ForgeSQLORM.ts +287 -9
  36. package/src/core/ForgeSQLQueryBuilder.ts +138 -8
  37. package/src/core/ForgeSQLSelectOperations.ts +2 -0
  38. package/src/core/SystemTables.ts +16 -0
  39. package/src/lib/drizzle/extensions/additionalActions.ts +10 -12
  40. package/src/utils/cacheContextUtils.ts +4 -2
  41. package/src/utils/cacheUtils.ts +20 -8
  42. package/src/utils/forgeDriver.ts +223 -23
  43. package/src/utils/forgeDriverProxy.ts +2 -0
  44. package/src/utils/metadataContextUtils.ts +22 -0
  45. package/src/utils/requestTypeContextUtils.ts +11 -0
  46. package/src/utils/sqlUtils.ts +1 -0
  47. package/src/webtriggers/applyMigrationsWebTrigger.ts +9 -6
  48. package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
  49. package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
  50. package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
  51. package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
  52. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +515 -257
@@ -38,6 +38,10 @@ import { cacheApplicationContext, localCacheApplicationContext } from "../utils/
38
38
  import { clearTablesCache } from "../utils/cacheUtils";
39
39
  import { SQLWrapper } from "drizzle-orm/sql/sql";
40
40
  import { WithSubquery } from "drizzle-orm/subquery";
41
+ import { ForgeSQLMetadata } from "../utils/forgeDriver";
42
+ import { getLastestMetadata, metadataQueryContext } from "../utils/metadataContextUtils";
43
+ import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
44
+ import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
41
45
 
42
46
  /**
43
47
  * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
@@ -72,6 +76,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
72
76
  try {
73
77
  const newOptions: ForgeSqlOrmOptions = options ?? {
74
78
  logRawSqlQuery: false,
79
+ logCache: false,
75
80
  disableOptimisticLocking: false,
76
81
  cacheWrapTable: true,
77
82
  cacheTTL: 120,
@@ -81,6 +86,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
81
86
  };
82
87
  this.options = newOptions;
83
88
  if (newOptions.logRawSqlQuery) {
89
+ // eslint-disable-next-line no-console
84
90
  console.debug("Initializing ForgeSQLORM...");
85
91
  }
86
92
  // Initialize Drizzle instance with our custom driver
@@ -94,11 +100,63 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
94
100
  this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
95
101
  this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
96
102
  } catch (error) {
103
+ // eslint-disable-next-line no-console
97
104
  console.error("ForgeSQLORM initialization failed:", error);
98
105
  throw error;
99
106
  }
100
107
  }
101
108
 
109
+ /**
110
+ * Executes a query and provides access to execution metadata.
111
+ * This method allows you to capture detailed information about query execution
112
+ * including database execution time, response size, and Forge SQL metadata.
113
+ *
114
+ * @template T - The return type of the query
115
+ * @param query - A function that returns a Promise with the query result
116
+ * @param onMetadata - Callback function that receives execution metadata
117
+ * @returns Promise with the query result
118
+ * @example
119
+ * ```typescript
120
+ * const result = await forgeSQL.executeWithMetadata(
121
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
122
+ * (dbTime, responseSize, metadata) => {
123
+ * console.log(`DB execution time: ${dbTime}ms`);
124
+ * console.log(`Response size: ${responseSize} bytes`);
125
+ * console.log('Forge metadata:', metadata);
126
+ * }
127
+ * );
128
+ * ```
129
+ */
130
+ async executeWithMetadata<T>(
131
+ query: () => Promise<T>,
132
+ onMetadata: (
133
+ totalDbExecutionTime: number,
134
+ totalResponseSize: number,
135
+ forgeMetadata: ForgeSQLMetadata,
136
+ ) => Promise<void> | void,
137
+ ): Promise<T> {
138
+ return metadataQueryContext.run(
139
+ {
140
+ totalDbExecutionTime: 0,
141
+ totalResponseSize: 0,
142
+ },
143
+ async () => {
144
+ try {
145
+ return await query();
146
+ } finally {
147
+ const metadata = await getLastestMetadata();
148
+ if (metadata && metadata.lastMetadata) {
149
+ await onMetadata(
150
+ metadata.totalDbExecutionTime,
151
+ metadata.totalResponseSize,
152
+ metadata.lastMetadata,
153
+ );
154
+ }
155
+ }
156
+ },
157
+ );
158
+ }
159
+
102
160
  /**
103
161
  * Executes operations within a cache context that collects cache eviction events.
104
162
  * All clearCache calls within the context are collected and executed in batch at the end.
@@ -531,8 +589,101 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
531
589
  * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
532
590
  * ```
533
591
  */
534
- execute(query: SQLWrapper | string) {
535
- return this.drizzle.executeQuery(query);
592
+ execute<T>(query: SQLWrapper | string) {
593
+ return this.drizzle.executeQuery<T>(query);
594
+ }
595
+
596
+ /**
597
+ * Executes a Data Definition Language (DDL) SQL query.
598
+ * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
599
+ *
600
+ * This method is specifically designed for DDL operations and provides:
601
+ * - Proper operation type context for DDL queries
602
+ * - No caching (DDL operations should not be cached)
603
+ * - Direct execution without query optimization
604
+ *
605
+ * @template T - The expected return type of the query result
606
+ * @param query - The DDL SQL query to execute (SQLWrapper or string)
607
+ * @returns Promise with query results
608
+ * @throws {Error} If the DDL operation fails
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * // Create a new table
613
+ * await forgeSQL.executeDDL(`
614
+ * CREATE TABLE users (
615
+ * id INT PRIMARY KEY AUTO_INCREMENT,
616
+ * name VARCHAR(255) NOT NULL,
617
+ * email VARCHAR(255) UNIQUE
618
+ * )
619
+ * `);
620
+ *
621
+ * // Alter table structure
622
+ * await forgeSQL.executeDDL(sql`
623
+ * ALTER TABLE users
624
+ * ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
625
+ * `);
626
+ *
627
+ * // Drop a table
628
+ * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
629
+ * ```
630
+ */
631
+ async executeDDL<T>(query: SQLWrapper | string) {
632
+ return this.executeDDLActions(async () => this.drizzle.executeQuery<T>(query));
633
+ }
634
+
635
+ /**
636
+ * Executes a series of actions within a DDL operation context.
637
+ * This method provides a way to execute regular SQL queries that should be treated
638
+ * as DDL operations, ensuring proper operation type context for performance monitoring.
639
+ *
640
+ * This method is useful for:
641
+ * - Executing regular SQL queries in DDL context for monitoring purposes
642
+ * - Wrapping non-DDL operations that should be treated as DDL for analysis
643
+ * - Ensuring proper operation type context for complex workflows
644
+ * - Maintaining DDL operation context across multiple function calls
645
+ *
646
+ * @template T - The return type of the actions function
647
+ * @param actions - Function containing SQL operations to execute in DDL context
648
+ * @returns Promise that resolves to the return value of the actions function
649
+ *
650
+ * @example
651
+ * ```typescript
652
+ * // Execute regular SQL queries in DDL context for monitoring
653
+ * await forgeSQL.executeDDLActions(async () => {
654
+ * const slowQueries = await forgeSQL.execute(`
655
+ * SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
656
+ * WHERE AVG_LATENCY > 1000000
657
+ * `);
658
+ * return slowQueries;
659
+ * });
660
+ *
661
+ * // Execute complex analysis queries in DDL context
662
+ * const result = await forgeSQL.executeDDLActions(async () => {
663
+ * const tableInfo = await forgeSQL.execute("SHOW TABLES");
664
+ * const performanceData = await forgeSQL.execute(`
665
+ * SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
666
+ * WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
667
+ * `);
668
+ * return { tableInfo, performanceData };
669
+ * });
670
+ *
671
+ * // Execute monitoring queries with error handling
672
+ * try {
673
+ * await forgeSQL.executeDDLActions(async () => {
674
+ * const metrics = await forgeSQL.execute(`
675
+ * SELECT COUNT(*) as query_count
676
+ * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
677
+ * `);
678
+ * console.log(`Total queries: ${metrics[0].query_count}`);
679
+ * });
680
+ * } catch (error) {
681
+ * console.error("Monitoring query failed:", error);
682
+ * }
683
+ * ```
684
+ */
685
+ async executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
686
+ return operationTypeQueryContext.run({ operationType: "DDL" }, async () => actions());
536
687
  }
537
688
 
538
689
  /**
@@ -553,8 +704,8 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
553
704
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
554
705
  * ```
555
706
  */
556
- executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
557
- return this.drizzle.executeQueryCacheable(query, cacheTtl);
707
+ executeCacheable<T>(query: SQLWrapper | string, cacheTtl?: number) {
708
+ return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
558
709
  }
559
710
 
560
711
  /**
@@ -610,6 +761,38 @@ class ForgeSQLORM implements ForgeSqlOperation {
610
761
  this.ormInstance = ForgeSQLORMImpl.getInstance(options);
611
762
  }
612
763
 
764
+ /**
765
+ * Executes a query and provides access to execution metadata.
766
+ * This method allows you to capture detailed information about query execution
767
+ * including database execution time, response size, and Forge SQL metadata.
768
+ *
769
+ * @template T - The return type of the query
770
+ * @param query - A function that returns a Promise with the query result
771
+ * @param onMetadata - Callback function that receives execution metadata
772
+ * @returns Promise with the query result
773
+ * @example
774
+ * ```typescript
775
+ * const result = await forgeSQL.executeWithMetadata(
776
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
777
+ * (dbTime, responseSize, metadata) => {
778
+ * console.log(`DB execution time: ${dbTime}ms`);
779
+ * console.log(`Response size: ${responseSize} bytes`);
780
+ * console.log('Forge metadata:', metadata);
781
+ * }
782
+ * );
783
+ * ```
784
+ */
785
+ async executeWithMetadata<T>(
786
+ query: () => Promise<T>,
787
+ onMetadata: (
788
+ totalDbExecutionTime: number,
789
+ totalResponseSize: number,
790
+ forgeMetadata: ForgeSQLMetadata,
791
+ ) => Promise<void> | void,
792
+ ): Promise<T> {
793
+ return this.ormInstance.executeWithMetadata(query, onMetadata);
794
+ }
795
+
613
796
  selectCacheable<TSelection extends SelectedFields>(
614
797
  fields: TSelection,
615
798
  cacheTTL?: number,
@@ -909,8 +1092,103 @@ class ForgeSQLORM implements ForgeSqlOperation {
909
1092
  * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
910
1093
  * ```
911
1094
  */
912
- execute(query: SQLWrapper | string) {
913
- return this.ormInstance.getDrizzleQueryBuilder().executeQuery(query);
1095
+ execute<T>(
1096
+ query: SQLWrapper | string,
1097
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1098
+ return this.ormInstance.execute(query);
1099
+ }
1100
+
1101
+ /**
1102
+ * Executes a Data Definition Language (DDL) SQL query.
1103
+ * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
1104
+ *
1105
+ * This method is specifically designed for DDL operations and provides:
1106
+ * - Proper operation type context for DDL queries
1107
+ * - No caching (DDL operations should not be cached)
1108
+ * - Direct execution without query optimization
1109
+ *
1110
+ * @template T - The expected return type of the query result
1111
+ * @param query - The DDL SQL query to execute (SQLWrapper or string)
1112
+ * @returns Promise with query results
1113
+ * @throws {Error} If the DDL operation fails
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * // Create a new table
1118
+ * await forgeSQL.executeDDL(`
1119
+ * CREATE TABLE users (
1120
+ * id INT PRIMARY KEY AUTO_INCREMENT,
1121
+ * name VARCHAR(255) NOT NULL,
1122
+ * email VARCHAR(255) UNIQUE
1123
+ * )
1124
+ * `);
1125
+ *
1126
+ * // Alter table structure
1127
+ * await forgeSQL.executeDDL(sql`
1128
+ * ALTER TABLE users
1129
+ * ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1130
+ * `);
1131
+ *
1132
+ * // Drop a table
1133
+ * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
1134
+ * ```
1135
+ */
1136
+ executeDDL(query: SQLWrapper | string) {
1137
+ return this.ormInstance.executeDDL(query);
1138
+ }
1139
+
1140
+ /**
1141
+ * Executes a series of actions within a DDL operation context.
1142
+ * This method provides a way to execute regular SQL queries that should be treated
1143
+ * as DDL operations, ensuring proper operation type context for performance monitoring.
1144
+ *
1145
+ * This method is useful for:
1146
+ * - Executing regular SQL queries in DDL context for monitoring purposes
1147
+ * - Wrapping non-DDL operations that should be treated as DDL for analysis
1148
+ * - Ensuring proper operation type context for complex workflows
1149
+ * - Maintaining DDL operation context across multiple function calls
1150
+ *
1151
+ * @template T - The return type of the actions function
1152
+ * @param actions - Function containing SQL operations to execute in DDL context
1153
+ * @returns Promise that resolves to the return value of the actions function
1154
+ *
1155
+ * @example
1156
+ * ```typescript
1157
+ * // Execute regular SQL queries in DDL context for monitoring
1158
+ * await forgeSQL.executeDDLActions(async () => {
1159
+ * const slowQueries = await forgeSQL.execute(`
1160
+ * SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1161
+ * WHERE AVG_LATENCY > 1000000
1162
+ * `);
1163
+ * return slowQueries;
1164
+ * });
1165
+ *
1166
+ * // Execute complex analysis queries in DDL context
1167
+ * const result = await forgeSQL.executeDDLActions(async () => {
1168
+ * const tableInfo = await forgeSQL.execute("SHOW TABLES");
1169
+ * const performanceData = await forgeSQL.execute(`
1170
+ * SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
1171
+ * WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
1172
+ * `);
1173
+ * return { tableInfo, performanceData };
1174
+ * });
1175
+ *
1176
+ * // Execute monitoring queries with error handling
1177
+ * try {
1178
+ * await forgeSQL.executeDDLActions(async () => {
1179
+ * const metrics = await forgeSQL.execute(`
1180
+ * SELECT COUNT(*) as query_count
1181
+ * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1182
+ * `);
1183
+ * console.log(`Total queries: ${metrics[0].query_count}`);
1184
+ * });
1185
+ * } catch (error) {
1186
+ * console.error("Monitoring query failed:", error);
1187
+ * }
1188
+ * ```
1189
+ */
1190
+ executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
1191
+ return this.ormInstance.executeDDLActions(actions);
914
1192
  }
915
1193
 
916
1194
  /**
@@ -932,7 +1210,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
932
1210
  * ```
933
1211
  */
934
1212
  executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
935
- return this.ormInstance.getDrizzleQueryBuilder().executeQueryCacheable(query, cacheTtl);
1213
+ return this.ormInstance.executeCacheable(query, cacheTtl);
936
1214
  }
937
1215
 
938
1216
  /**
@@ -943,7 +1221,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
943
1221
  * @example
944
1222
  * ```typescript
945
1223
  * const withQuery = forgeSQL.$with('userStats').as(
946
- * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
1224
+ * forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
947
1225
  * .from(users)
948
1226
  * .groupBy(users.id)
949
1227
  * );
@@ -962,7 +1240,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
962
1240
  * @example
963
1241
  * ```typescript
964
1242
  * const withQuery = forgeSQL.$with('userStats').as(
965
- * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
1243
+ * forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
966
1244
  * .from(users)
967
1245
  * .groupBy(users.id)
968
1246
  * );
@@ -29,6 +29,7 @@ import {
29
29
  DeleteAndEvictCacheType,
30
30
  ExecuteQuery,
31
31
  ExecuteQueryCacheable,
32
+ ForgeSQLMetadata,
32
33
  InsertAndEvictCacheType,
33
34
  SelectAliasedCacheableType,
34
35
  SelectAliasedDistinctCacheableType,
@@ -497,6 +498,35 @@ export interface QueryBuilderForgeSql {
497
498
  */
498
499
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T>;
499
500
 
501
+ /**
502
+ * Executes a query and provides access to execution metadata.
503
+ * This method allows you to capture detailed information about query execution
504
+ * including database execution time, response size, and Forge SQL metadata.
505
+ *
506
+ * @template T - The return type of the query
507
+ * @param query - A function that returns a Promise with the query result
508
+ * @param onMetadata - Callback function that receives execution metadata
509
+ * @returns Promise with the query result
510
+ * @example
511
+ * ```typescript
512
+ * const result = await forgeSQL.executeWithMetadata(
513
+ * async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
514
+ * (dbTime, responseSize, metadata) => {
515
+ * console.log(`DB execution time: ${dbTime}ms`);
516
+ * console.log(`Response size: ${responseSize} bytes`);
517
+ * console.log('Forge metadata:', metadata);
518
+ * }
519
+ * );
520
+ * ```
521
+ */
522
+ executeWithMetadata<T>(
523
+ query: () => Promise<T>,
524
+ onMetadata: (
525
+ totalDbExecutionTime: number,
526
+ totalResponseSize: number,
527
+ forgeMetadata: ForgeSQLMetadata,
528
+ ) => Promise<void> | void,
529
+ ): Promise<T>;
500
530
  /**
501
531
  * Executes a raw SQL query with local cache support.
502
532
  * This method provides local caching for raw SQL queries within the current invocation context.
@@ -513,9 +543,100 @@ export interface QueryBuilderForgeSql {
513
543
  * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
514
544
  * ```
515
545
  */
516
- execute(
546
+ execute<T>(
547
+ query: SQLWrapper | string,
548
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>;
549
+
550
+ /**
551
+ * Executes a Data Definition Language (DDL) SQL query.
552
+ * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
553
+ *
554
+ * This method is specifically designed for DDL operations and provides:
555
+ * - Proper operation type context for DDL queries
556
+ * - No caching (DDL operations should not be cached)
557
+ * - Direct execution without query optimization
558
+ *
559
+ * @template T - The expected return type of the query result
560
+ * @param query - The DDL SQL query to execute (SQLWrapper or string)
561
+ * @returns Promise with query results
562
+ * @throws {Error} If the DDL operation fails
563
+ *
564
+ * @example
565
+ * ```typescript
566
+ * // Create a new table
567
+ * await forgeSQL.executeDDL(`
568
+ * CREATE TABLE users (
569
+ * id INT PRIMARY KEY AUTO_INCREMENT,
570
+ * name VARCHAR(255) NOT NULL,
571
+ * email VARCHAR(255) UNIQUE
572
+ * )
573
+ * `);
574
+ *
575
+ * // Alter table structure
576
+ * await forgeSQL.executeDDL(sql`
577
+ * ALTER TABLE users
578
+ * ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
579
+ * `);
580
+ *
581
+ * // Drop a table
582
+ * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
583
+ * ```
584
+ */
585
+ executeDDL<T>(
517
586
  query: SQLWrapper | string,
518
- ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, unknown>>;
587
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>;
588
+
589
+ /**
590
+ * Executes a series of actions within a DDL operation context.
591
+ * This method provides a way to execute regular SQL queries that should be treated
592
+ * as DDL operations, ensuring proper operation type context for performance monitoring.
593
+ *
594
+ * This method is useful for:
595
+ * - Executing regular SQL queries in DDL context for monitoring purposes
596
+ * - Wrapping non-DDL operations that should be treated as DDL for analysis
597
+ * - Ensuring proper operation type context for complex workflows
598
+ * - Maintaining DDL operation context across multiple function calls
599
+ *
600
+ * @template T - The return type of the actions function
601
+ * @param actions - Function containing SQL operations to execute in DDL context
602
+ * @returns Promise that resolves to the return value of the actions function
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * // Execute regular SQL queries in DDL context for monitoring
607
+ * await forgeSQL.executeDDLActions(async () => {
608
+ * const slowQueries = await forgeSQL.execute(`
609
+ * SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
610
+ * WHERE AVG_LATENCY > 1000000
611
+ * `);
612
+ * return slowQueries;
613
+ * });
614
+ *
615
+ * // Execute complex analysis queries in DDL context
616
+ * const result = await forgeSQL.executeDDLActions(async () => {
617
+ * const tableInfo = await forgeSQL.execute("SHOW TABLES");
618
+ * const performanceData = await forgeSQL.execute(`
619
+ * SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
620
+ * WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
621
+ * `);
622
+ * return { tableInfo, performanceData };
623
+ * });
624
+ *
625
+ * // Execute monitoring queries with error handling
626
+ * try {
627
+ * await forgeSQL.executeDDLActions(async () => {
628
+ * const metrics = await forgeSQL.execute(`
629
+ * SELECT COUNT(*) as query_count
630
+ * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
631
+ * `);
632
+ * console.log(`Total queries: ${metrics[0].query_count}`);
633
+ * });
634
+ * } catch (error) {
635
+ * console.error("Monitoring query failed:", error);
636
+ * }
637
+ * ```
638
+ */
639
+ executeDDLActions<T>(actions: () => Promise<T>): Promise<T>;
519
640
 
520
641
  /**
521
642
  * Executes a raw SQL query with both local and global cache support.
@@ -535,10 +656,10 @@ export interface QueryBuilderForgeSql {
535
656
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
536
657
  * ```
537
658
  */
538
- executeCacheable(
659
+ executeCacheable<T>(
539
660
  query: SQLWrapper | string,
540
661
  cacheTtl?: number,
541
- ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, unknown>>;
662
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>;
542
663
  /**
543
664
  * Creates a Common Table Expression (CTE) builder for complex queries.
544
665
  * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
@@ -816,22 +937,31 @@ export type AdditionalMetadata = Record<string, TableMetadata>;
816
937
  * @interface ForgeSqlOrmOptions
817
938
  */
818
939
  export interface ForgeSqlOrmOptions {
819
- /** Whether to log raw SQL queries */
940
+ /** Whether to log raw SQL queries to the console */
820
941
  logRawSqlQuery?: boolean;
821
- /** Whether to disable optimistic locking */
942
+ /** Whether to log cache operations (hits, misses, evictions) */
943
+ logCache?: boolean;
944
+ /** Whether to disable optimistic locking for update operations */
822
945
  disableOptimisticLocking?: boolean;
823
- /** SQL hints to be applied to queries */
946
+ /** SQL hints to be applied to queries for optimization */
824
947
  hints?: SqlHints;
948
+ /** Default Cache TTL (Time To Live) in seconds */
825
949
  cacheTTL?: number;
950
+ /** Name of the KVS entity used for cache storage */
826
951
  cacheEntityName?: string;
952
+ /** Name of the field in cache entity that stores SQL query */
827
953
  cacheEntityQueryName?: string;
954
+ /** Whether to wrap table names with backticks in cache keys */
828
955
  cacheWrapTable?: boolean;
956
+ /** Name of the field in cache entity that stores expiration timestamp */
829
957
  cacheEntityExpirationName?: string;
958
+ /** Name of the field in cache entity that stores cached data */
830
959
  cacheEntityDataName?: string;
831
960
 
832
961
  /**
833
962
  * Additional metadata for table configuration.
834
- * Allows specifying table-specific settings and behaviors.
963
+ * Allows specifying table-specific settings and behaviors such as version fields for optimistic locking.
964
+ *
835
965
  * @example
836
966
  * ```typescript
837
967
  * {
@@ -58,6 +58,7 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
58
58
  async executeRawSQL<T extends object | unknown>(query: string, params?: unknown[]): Promise<T[]> {
59
59
  if (this.options.logRawSqlQuery) {
60
60
  const paramsStr = params ? `, with params: ${JSON.stringify(params)}` : "";
61
+ // eslint-disable-next-line no-console
61
62
  console.debug(`Executing with SQL ${query}${paramsStr}`);
62
63
  }
63
64
  const sqlStatement = sql.prepare<T>(query);
@@ -80,6 +81,7 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
80
81
  sqlStatement.bindParams(...params);
81
82
  }
82
83
  if (this.options.logRawSqlQuery) {
84
+ // eslint-disable-next-line no-console
83
85
  console.debug(
84
86
  `Executing Update with SQL ${query}` +
85
87
  (params ? `, with params: ${JSON.stringify(params)}` : ""),
@@ -312,6 +312,22 @@ export const clusterStatementsSummaryHistory = informationSchema.table(
312
312
  // Types
313
313
  export type ClusterStatementsSummaryHistory = typeof clusterStatementsSummaryHistory.$inferSelect;
314
314
 
315
+ export const statementsSummaryHistory = informationSchema.table(
316
+ "STATEMENTS_SUMMARY_HISTORY",
317
+ createClusterStatementsSummarySchema(),
318
+ );
319
+
320
+ // Types
321
+ export type StatementsSummaryHistory = typeof statementsSummaryHistory.$inferSelect;
322
+
323
+ export const statementsSummary = informationSchema.table(
324
+ "STATEMENTS_SUMMARY",
325
+ createClusterStatementsSummarySchema(),
326
+ );
327
+
328
+ // Types
329
+ export type StatementsSummary = typeof statementsSummary.$inferSelect;
330
+
315
331
  export const clusterStatementsSummary = informationSchema.table(
316
332
  "CLUSTER_STATEMENTS_SUMMARY",
317
333
  createClusterStatementsSummarySchema(),
@@ -22,9 +22,9 @@ import {
22
22
  saveQueryLocalCacheQuery,
23
23
  saveTableIfInsideCacheContext,
24
24
  } from "../../../utils/cacheContextUtils";
25
- import { BuildQueryConfig, isSQLWrapper, SQLWrapper } from "drizzle-orm/sql/sql";
25
+ import { isSQLWrapper, SQLWrapper } from "drizzle-orm/sql/sql";
26
26
  import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
27
- import { getTableColumns, Query } from "drizzle-orm";
27
+ import { getTableColumns, Query, SQL } from "drizzle-orm";
28
28
  import { MySqlDialect } from "drizzle-orm/mysql-core/dialect";
29
29
  import type {
30
30
  GetSelectTableName,
@@ -204,17 +204,17 @@ export type SelectAllDistinctFromCacheableAliasedType = <T extends MySqlTable>(
204
204
  /**
205
205
  * Type for executing raw SQL queries with local cache
206
206
  */
207
- export type ExecuteQuery = (
207
+ export type ExecuteQuery = <T>(
208
208
  query: SQLWrapper | string,
209
- ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, unknown>>;
209
+ ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>;
210
210
 
211
211
  /**
212
212
  * Type for executing raw SQL queries with local and global cache
213
213
  */
214
- export type ExecuteQueryCacheable = (
214
+ export type ExecuteQueryCacheable = <T>(
215
215
  query: SQLWrapper | string,
216
216
  cacheTtl?: number,
217
- ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, unknown>>;
217
+ ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>;
218
218
 
219
219
  /**
220
220
  * Type for insert operations with cache eviction
@@ -268,6 +268,7 @@ async function handleSuccessfulExecution(
268
268
  await evictLocalCacheQuery(table, options);
269
269
  if (isCached) {
270
270
  await clearCache(table, options).catch((e) => {
271
+ // eslint-disable-next-line no-console
271
272
  console.warn("Ignore cache clear errors", e);
272
273
  });
273
274
  } else {
@@ -457,6 +458,7 @@ async function handleCachedQuery(
457
458
  await saveQueryLocalCacheQuery(target, transformed, options);
458
459
  await setCacheResult(target, options, transformed, cacheTtl).catch((cacheError) => {
459
460
  // Log cache error but don't fail the query
461
+ // eslint-disable-next-line no-console
460
462
  console.warn("Cache set error:", cacheError);
461
463
  });
462
464
 
@@ -615,12 +617,8 @@ function createRawQueryExecutor(
615
617
  let sql: Query;
616
618
 
617
619
  if (isSQLWrapper(query)) {
618
- const sqlWrapper = query as SQLWrapper;
619
- sql = sqlWrapper
620
- .getSQL()
621
- .toQuery(
622
- (db as unknown as { dialect: MySqlDialect }).dialect as unknown as BuildQueryConfig,
623
- );
620
+ const dialect = (db as unknown as { dialect: MySqlDialect }).dialect;
621
+ sql = dialect.sqlToQuery(query as SQL);
624
622
  } else {
625
623
  sql = {
626
624
  sql: query,