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.
- package/README.md +195 -27
- package/dist/ForgeSQLORM.js +632 -192
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +632 -192
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +114 -3
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +125 -7
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +3654 -0
- package/dist/core/SystemTables.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts +2 -2
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/utils/cacheContextUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +71 -3
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +11 -0
- package/dist/utils/metadataContextUtils.d.ts.map +1 -0
- package/dist/utils/requestTypeContextUtils.d.ts +8 -0
- package/dist/utils/requestTypeContextUtils.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +85 -43
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/core/ForgeSQLCrudOperations.ts +3 -0
- package/src/core/ForgeSQLORM.ts +287 -9
- package/src/core/ForgeSQLQueryBuilder.ts +138 -8
- package/src/core/ForgeSQLSelectOperations.ts +2 -0
- package/src/core/SystemTables.ts +16 -0
- package/src/lib/drizzle/extensions/additionalActions.ts +10 -12
- package/src/utils/cacheContextUtils.ts +4 -2
- package/src/utils/cacheUtils.ts +20 -8
- package/src/utils/forgeDriver.ts +223 -23
- package/src/utils/forgeDriverProxy.ts +2 -0
- package/src/utils/metadataContextUtils.ts +22 -0
- package/src/utils/requestTypeContextUtils.ts +11 -0
- package/src/utils/sqlUtils.ts +1 -0
- package/src/webtriggers/applyMigrationsWebTrigger.ts +9 -6
- package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
- package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
- package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
- package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +515 -257
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -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(
|
|
913
|
-
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
|
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)}` : ""),
|
package/src/core/SystemTables.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
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,
|
|
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
|
|
619
|
-
sql =
|
|
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,
|