forge-sql-orm 2.0.18 → 2.0.20
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 +95 -4
- package/dist/ForgeSQLORM.js +315 -49
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +315 -49
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.d.ts +250 -0
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -0
- package/dist/core/ForgeSQLORM.d.ts +12 -2
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +105 -9
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +167 -0
- package/dist/core/SystemTables.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +2 -2
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts +2 -4
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/package.json +11 -19
- package/src/core/ForgeSQLAnalyseOperations.ts +462 -0
- package/src/core/ForgeSQLORM.ts +43 -7
- package/src/core/ForgeSQLQueryBuilder.ts +121 -18
- package/src/core/ForgeSQLSelectOperations.ts +4 -6
- package/src/core/SystemTables.ts +175 -0
- package/src/index.ts +1 -0
- package/src/utils/forgeDriverProxy.ts +1 -1
- package/src/utils/sqlUtils.ts +10 -16
- package/src/webtriggers/applyMigrationsWebTrigger.ts +32 -16
- package/src/webtriggers/dropMigrationWebTrigger.ts +5 -6
- package/src/webtriggers/fetchSchemaWebTrigger.ts +2 -10
package/dist/ForgeSQLORM.mjs
CHANGED
|
@@ -62,7 +62,7 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
|
|
|
62
62
|
const configBuilderData = extraConfigBuilder(table);
|
|
63
63
|
if (configBuilderData) {
|
|
64
64
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
65
|
-
(item) => item.value
|
|
65
|
+
(item) => item.value ?? item
|
|
66
66
|
);
|
|
67
67
|
configBuilders.forEach((builder) => {
|
|
68
68
|
if (!builder?.constructor) return;
|
|
@@ -97,7 +97,7 @@ function getTableMetadata(table) {
|
|
|
97
97
|
const configBuilderData = extraConfigBuilder(table);
|
|
98
98
|
if (configBuilderData) {
|
|
99
99
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
100
|
-
(item) => item.value
|
|
100
|
+
(item) => item.value ?? item
|
|
101
101
|
);
|
|
102
102
|
configBuilders.forEach((builder) => {
|
|
103
103
|
if (!builder?.constructor) return;
|
|
@@ -127,13 +127,9 @@ function getTableMetadata(table) {
|
|
|
127
127
|
}
|
|
128
128
|
function generateDropTableStatements(tables) {
|
|
129
129
|
const dropStatements = [];
|
|
130
|
-
tables.forEach((
|
|
131
|
-
|
|
132
|
-
if (tableMetadata.tableName) {
|
|
133
|
-
dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
|
|
134
|
-
}
|
|
130
|
+
tables.forEach((tableName) => {
|
|
131
|
+
dropStatements.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
|
|
135
132
|
});
|
|
136
|
-
dropStatements.push(`DELETE FROM __migrations;`);
|
|
137
133
|
return dropStatements;
|
|
138
134
|
}
|
|
139
135
|
function mapSelectTableToAlias(table, uniqPrefix, aliasMap) {
|
|
@@ -191,9 +187,9 @@ function getAliasFromDrizzleAlias(value) {
|
|
|
191
187
|
const aliasNameChunk = queryChunks[queryChunks.length - 2];
|
|
192
188
|
if (isSQLWrapper(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
|
|
193
189
|
const aliasNameChunkSql = aliasNameChunk;
|
|
194
|
-
if (aliasNameChunkSql
|
|
190
|
+
if (aliasNameChunkSql.queryChunks?.length === 1 && aliasNameChunkSql.queryChunks[0]) {
|
|
195
191
|
const queryChunksStringChunc = aliasNameChunkSql.queryChunks[0];
|
|
196
|
-
if (
|
|
192
|
+
if ("value" in queryChunksStringChunc) {
|
|
197
193
|
const values = queryChunksStringChunc.value;
|
|
198
194
|
if (values && values.length === 1) {
|
|
199
195
|
return values[0];
|
|
@@ -245,7 +241,7 @@ function applyFromDriverTransform(rows, selections, aliasMap) {
|
|
|
245
241
|
});
|
|
246
242
|
}
|
|
247
243
|
function processNullBranches(obj) {
|
|
248
|
-
if (obj === null || typeof obj !== "object"
|
|
244
|
+
if (obj === null || typeof obj !== "object") {
|
|
249
245
|
return obj;
|
|
250
246
|
}
|
|
251
247
|
if (obj.constructor && obj.constructor.name !== "Object") {
|
|
@@ -258,7 +254,7 @@ function processNullBranches(obj) {
|
|
|
258
254
|
result[key] = null;
|
|
259
255
|
continue;
|
|
260
256
|
}
|
|
261
|
-
if (typeof value === "object"
|
|
257
|
+
if (typeof value === "object") {
|
|
262
258
|
const processed = processNullBranches(value);
|
|
263
259
|
result[key] = processed;
|
|
264
260
|
if (processed !== null) {
|
|
@@ -608,9 +604,8 @@ class ForgeSQLSelectOperations {
|
|
|
608
604
|
*/
|
|
609
605
|
async executeRawSQL(query, params) {
|
|
610
606
|
if (this.options.logRawSqlQuery) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
);
|
|
607
|
+
const paramsStr = params ? `, with params: ${JSON.stringify(params)}` : "";
|
|
608
|
+
console.debug(`Executing with SQL ${query}${paramsStr}`);
|
|
614
609
|
}
|
|
615
610
|
const sqlStatement = sql$1.prepare(query);
|
|
616
611
|
if (params) {
|
|
@@ -632,7 +627,7 @@ class ForgeSQLSelectOperations {
|
|
|
632
627
|
}
|
|
633
628
|
if (this.options.logRawSqlQuery) {
|
|
634
629
|
console.debug(
|
|
635
|
-
`Executing Update with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
|
|
630
|
+
`Executing Update with SQL ${query}` + (params ? `, with params: ${JSON.stringify(params)}` : "")
|
|
636
631
|
);
|
|
637
632
|
}
|
|
638
633
|
const updateQueryResponseResults = await sqlStatement.execute();
|
|
@@ -697,7 +692,7 @@ function injectSqlHints(query, hints) {
|
|
|
697
692
|
function createForgeDriverProxy(options, logRawSqlQuery) {
|
|
698
693
|
return async (query, params, method) => {
|
|
699
694
|
const modifiedQuery = injectSqlHints(query, options);
|
|
700
|
-
if (options && logRawSqlQuery) {
|
|
695
|
+
if (options && logRawSqlQuery && modifiedQuery !== query) {
|
|
701
696
|
console.warn("modified query: " + modifiedQuery);
|
|
702
697
|
}
|
|
703
698
|
return forgeDriver(modifiedQuery, params, method);
|
|
@@ -746,11 +741,240 @@ function patchDbWithSelectAliased(db) {
|
|
|
746
741
|
};
|
|
747
742
|
return db;
|
|
748
743
|
}
|
|
744
|
+
class ForgeSQLAnalyseOperation {
|
|
745
|
+
forgeOperations;
|
|
746
|
+
/**
|
|
747
|
+
* Creates a new instance of ForgeSQLAnalizeOperation.
|
|
748
|
+
* @param {ForgeSqlOperation} forgeOperations - The ForgeSQL operations instance
|
|
749
|
+
*/
|
|
750
|
+
constructor(forgeOperations) {
|
|
751
|
+
this.forgeOperations = forgeOperations;
|
|
752
|
+
this.mapToCamelCaseClusterStatement = this.mapToCamelCaseClusterStatement.bind(this);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Executes EXPLAIN on a raw SQL query.
|
|
756
|
+
* @param {string} query - The SQL query to analyze
|
|
757
|
+
* @param {unknown[]} bindParams - The query parameters
|
|
758
|
+
* @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
|
|
759
|
+
*/
|
|
760
|
+
async explainRaw(query, bindParams) {
|
|
761
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ${query}`, bindParams);
|
|
762
|
+
return results.map((row) => ({
|
|
763
|
+
id: row.id,
|
|
764
|
+
estRows: row.estRows,
|
|
765
|
+
actRows: row.actRows,
|
|
766
|
+
task: row.task,
|
|
767
|
+
accessObject: row["access object"],
|
|
768
|
+
executionInfo: row["execution info"],
|
|
769
|
+
operatorInfo: row["operator info"],
|
|
770
|
+
memory: row.memory,
|
|
771
|
+
disk: row.disk
|
|
772
|
+
}));
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Executes EXPLAIN on a Drizzle query.
|
|
776
|
+
* @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
|
|
777
|
+
* @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
|
|
778
|
+
*/
|
|
779
|
+
async explain(query) {
|
|
780
|
+
const { sql: sql2, params } = query.toSQL();
|
|
781
|
+
return this.explainRaw(sql2, params);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Executes EXPLAIN ANALYZE on a raw SQL query.
|
|
785
|
+
* @param {string} query - The SQL query to analyze
|
|
786
|
+
* @param {unknown[]} bindParams - The query parameters
|
|
787
|
+
* @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
|
|
788
|
+
*/
|
|
789
|
+
async explainAnalyzeRaw(query, bindParams) {
|
|
790
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(`EXPLAIN ANALYZE ${query}`, bindParams);
|
|
791
|
+
return results.map((row) => ({
|
|
792
|
+
id: row.id,
|
|
793
|
+
estRows: row.estRows,
|
|
794
|
+
actRows: row.actRows,
|
|
795
|
+
task: row.task,
|
|
796
|
+
accessObject: row["access object"],
|
|
797
|
+
executionInfo: row["execution info"],
|
|
798
|
+
operatorInfo: row["operator info"],
|
|
799
|
+
memory: row.memory,
|
|
800
|
+
disk: row.disk
|
|
801
|
+
}));
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Executes EXPLAIN ANALYZE on a Drizzle query.
|
|
805
|
+
* @param {{ toSQL: () => Query }} query - The Drizzle query to analyze
|
|
806
|
+
* @returns {Promise<ExplainAnalyzeRow[]>} The execution plan analysis results
|
|
807
|
+
*/
|
|
808
|
+
async explainAnalyze(query) {
|
|
809
|
+
const { sql: sql2, params } = query.toSQL();
|
|
810
|
+
return this.explainAnalyzeRaw(sql2, params);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Decodes a query execution plan from its string representation.
|
|
814
|
+
* @param {string} input - The raw execution plan string
|
|
815
|
+
* @returns {ExplainAnalyzeRow[]} The decoded execution plan rows
|
|
816
|
+
*/
|
|
817
|
+
decodedPlan(input) {
|
|
818
|
+
if (!input) {
|
|
819
|
+
return [];
|
|
820
|
+
}
|
|
821
|
+
const lines = input.trim().split("\n");
|
|
822
|
+
if (lines.length < 2) return [];
|
|
823
|
+
const headersRaw = lines[0].split(" ").map((h) => h.trim()).filter(Boolean);
|
|
824
|
+
const headers = headersRaw.map((h) => {
|
|
825
|
+
return h.replace(/\s+/g, " ").replace(/[-\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (s) => s.toLowerCase());
|
|
826
|
+
});
|
|
827
|
+
return lines.slice(1).map((line) => {
|
|
828
|
+
const values = line.split(" ").map((s) => s.trim()).filter(Boolean);
|
|
829
|
+
const row = {};
|
|
830
|
+
headers.forEach((key, i) => {
|
|
831
|
+
row[key] = values[i] ?? "";
|
|
832
|
+
});
|
|
833
|
+
return row;
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Normalizes a raw slow query row into a more structured format.
|
|
838
|
+
* @param {SlowQueryRaw} row - The raw slow query data
|
|
839
|
+
* @returns {SlowQueryNormalized} The normalized slow query data
|
|
840
|
+
*/
|
|
841
|
+
normalizeSlowQuery(row) {
|
|
842
|
+
return {
|
|
843
|
+
time: row.Time,
|
|
844
|
+
txnStartTs: row.Txn_start_ts,
|
|
845
|
+
user: row.User,
|
|
846
|
+
host: row.Host,
|
|
847
|
+
connId: row.Conn_ID,
|
|
848
|
+
db: row.DB,
|
|
849
|
+
query: row.Query,
|
|
850
|
+
digest: row.Digest,
|
|
851
|
+
queryTime: row.Query_time,
|
|
852
|
+
compileTime: row.Compile_time,
|
|
853
|
+
optimizeTime: row.Optimize_time,
|
|
854
|
+
processTime: row.Process_time,
|
|
855
|
+
waitTime: row.Wait_time,
|
|
856
|
+
parseTime: row.Parse_time,
|
|
857
|
+
rewriteTime: row.Rewrite_time,
|
|
858
|
+
copTime: row.Cop_time,
|
|
859
|
+
copProcAvg: row.Cop_proc_avg,
|
|
860
|
+
copProcMax: row.Cop_proc_max,
|
|
861
|
+
copProcP90: row.Cop_proc_p90,
|
|
862
|
+
copProcAddr: row.Cop_proc_addr,
|
|
863
|
+
copWaitAvg: row.Cop_wait_avg,
|
|
864
|
+
copWaitMax: row.Cop_wait_max,
|
|
865
|
+
copWaitP90: row.Cop_wait_p90,
|
|
866
|
+
copWaitAddr: row.Cop_wait_addr,
|
|
867
|
+
memMax: row.Mem_max,
|
|
868
|
+
diskMax: row.Disk_max,
|
|
869
|
+
totalKeys: row.Total_keys,
|
|
870
|
+
processKeys: row.Process_keys,
|
|
871
|
+
requestCount: row.Request_count,
|
|
872
|
+
kvTotal: row.KV_total,
|
|
873
|
+
pdTotal: row.PD_total,
|
|
874
|
+
resultRows: row.Result_rows,
|
|
875
|
+
rocksdbBlockCacheHitCount: row.Rocksdb_block_cache_hit_count,
|
|
876
|
+
rocksdbBlockReadCount: row.Rocksdb_block_read_count,
|
|
877
|
+
rocksdbBlockReadByte: row.Rocksdb_block_read_byte,
|
|
878
|
+
plan: row.Plan,
|
|
879
|
+
binaryPlan: row.Binary_plan,
|
|
880
|
+
planDigest: row.Plan_digest,
|
|
881
|
+
parsedPlan: this.decodedPlan(row.Plan)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Builds a SQL query for retrieving cluster statement history.
|
|
886
|
+
* @param {string[]} tables - The tables to analyze
|
|
887
|
+
* @param {Date} [from] - The start date for the analysis
|
|
888
|
+
* @param {Date} [to] - The end date for the analysis
|
|
889
|
+
* @returns {string} The SQL query for cluster statement history
|
|
890
|
+
*/
|
|
891
|
+
buildClusterStatementQuery(tables, from, to) {
|
|
892
|
+
const formatDateTime = (date) => moment(date).format("YYYY-MM-DDTHH:mm:ss.SSS");
|
|
893
|
+
const tableConditions = tables.map((table) => `TABLE_NAMES LIKE CONCAT(SCHEMA_NAME, '.', '%', '${table}', '%')`).join(" OR ");
|
|
894
|
+
const timeConditions = [];
|
|
895
|
+
if (from) {
|
|
896
|
+
timeConditions.push(`SUMMARY_BEGIN_TIME >= '${formatDateTime(from)}'`);
|
|
897
|
+
}
|
|
898
|
+
if (to) {
|
|
899
|
+
timeConditions.push(`SUMMARY_END_TIME <= '${formatDateTime(to)}'`);
|
|
900
|
+
}
|
|
901
|
+
let whereClauses;
|
|
902
|
+
if (tableConditions?.length) {
|
|
903
|
+
whereClauses = [tableConditions ? `(${tableConditions})` : "", ...timeConditions];
|
|
904
|
+
} else {
|
|
905
|
+
whereClauses = timeConditions;
|
|
906
|
+
}
|
|
907
|
+
return `
|
|
908
|
+
SELECT *
|
|
909
|
+
FROM (
|
|
910
|
+
SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY
|
|
911
|
+
UNION ALL
|
|
912
|
+
SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
|
|
913
|
+
) AS combined
|
|
914
|
+
${whereClauses?.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""}
|
|
915
|
+
`;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Retrieves and analyzes slow queries from the database.
|
|
919
|
+
* @returns {Promise<SlowQueryNormalized[]>} The normalized slow query data
|
|
920
|
+
*/
|
|
921
|
+
// CLUSTER_SLOW_QUERY STATISTICS
|
|
922
|
+
async analyzeSlowQueries() {
|
|
923
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(`
|
|
924
|
+
SELECT *
|
|
925
|
+
FROM information_schema.slow_query
|
|
926
|
+
ORDER BY time DESC
|
|
927
|
+
`);
|
|
928
|
+
return results.map((row) => this.normalizeSlowQuery(row));
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Converts a cluster statement row to camelCase format.
|
|
932
|
+
* @param {Record<string, any>} input - The input row data
|
|
933
|
+
* @returns {ClusterStatementRowCamelCase} The converted row data
|
|
934
|
+
*/
|
|
935
|
+
mapToCamelCaseClusterStatement(input) {
|
|
936
|
+
if (!input) {
|
|
937
|
+
return {};
|
|
938
|
+
}
|
|
939
|
+
const result = {};
|
|
940
|
+
result.parsedPlan = this.decodedPlan(input["PLAN"] ?? "");
|
|
941
|
+
for (const key in input) {
|
|
942
|
+
const camelKey = key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
943
|
+
result[camelKey] = input[key];
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Analyzes query history for specific tables using raw table names.
|
|
949
|
+
* @param {string[]} tables - The table names to analyze
|
|
950
|
+
* @param {Date} [fromDate] - The start date for the analysis
|
|
951
|
+
* @param {Date} [toDate] - The end date for the analysis
|
|
952
|
+
* @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
|
|
953
|
+
*/
|
|
954
|
+
async analyzeQueriesHistoryRaw(tables, fromDate, toDate) {
|
|
955
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(
|
|
956
|
+
this.buildClusterStatementQuery(tables ?? [], fromDate, toDate)
|
|
957
|
+
);
|
|
958
|
+
return results.map((r) => this.mapToCamelCaseClusterStatement(r));
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Analyzes query history for specific tables using Drizzle table objects.
|
|
962
|
+
* @param {AnyMySqlTable[]} tables - The Drizzle table objects to analyze
|
|
963
|
+
* @param {Date} [fromDate] - The start date for the analysis
|
|
964
|
+
* @param {Date} [toDate] - The end date for the analysis
|
|
965
|
+
* @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
|
|
966
|
+
*/
|
|
967
|
+
async analyzeQueriesHistory(tables, fromDate, toDate) {
|
|
968
|
+
const tableNames = tables?.map((table) => getTableName(table)) ?? [];
|
|
969
|
+
return this.analyzeQueriesHistoryRaw(tableNames, fromDate, toDate);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
749
972
|
class ForgeSQLORMImpl {
|
|
750
973
|
static instance = null;
|
|
751
974
|
drizzle;
|
|
752
975
|
crudOperations;
|
|
753
976
|
fetchOperations;
|
|
977
|
+
analyzeOperations;
|
|
754
978
|
/**
|
|
755
979
|
* Private constructor to enforce singleton behavior.
|
|
756
980
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
@@ -770,20 +994,26 @@ class ForgeSQLORMImpl {
|
|
|
770
994
|
);
|
|
771
995
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
772
996
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
997
|
+
this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
|
|
773
998
|
} catch (error) {
|
|
774
999
|
console.error("ForgeSQLORM initialization failed:", error);
|
|
775
1000
|
throw error;
|
|
776
1001
|
}
|
|
777
1002
|
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Create the modify operations instance.
|
|
1005
|
+
* @returns modify operations.
|
|
1006
|
+
*/
|
|
1007
|
+
modify() {
|
|
1008
|
+
return this.crudOperations;
|
|
1009
|
+
}
|
|
778
1010
|
/**
|
|
779
1011
|
* Returns the singleton instance of ForgeSQLORMImpl.
|
|
780
1012
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
781
1013
|
* @returns The singleton instance of ForgeSQLORMImpl.
|
|
782
1014
|
*/
|
|
783
1015
|
static getInstance(options) {
|
|
784
|
-
|
|
785
|
-
ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
|
|
786
|
-
}
|
|
1016
|
+
ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
|
|
787
1017
|
return ForgeSQLORMImpl.instance;
|
|
788
1018
|
}
|
|
789
1019
|
/**
|
|
@@ -791,7 +1021,7 @@ class ForgeSQLORMImpl {
|
|
|
791
1021
|
* @returns CRUD operations.
|
|
792
1022
|
*/
|
|
793
1023
|
crud() {
|
|
794
|
-
return this.
|
|
1024
|
+
return this.modify();
|
|
795
1025
|
}
|
|
796
1026
|
/**
|
|
797
1027
|
* Retrieves the fetch operations instance.
|
|
@@ -800,6 +1030,9 @@ class ForgeSQLORMImpl {
|
|
|
800
1030
|
fetch() {
|
|
801
1031
|
return this.fetchOperations;
|
|
802
1032
|
}
|
|
1033
|
+
analyze() {
|
|
1034
|
+
return this.analyzeOperations;
|
|
1035
|
+
}
|
|
803
1036
|
/**
|
|
804
1037
|
* Returns a Drizzle query builder instance.
|
|
805
1038
|
*
|
|
@@ -887,7 +1120,7 @@ class ForgeSQLORM {
|
|
|
887
1120
|
*
|
|
888
1121
|
* @template TSelection - The type of the selected fields
|
|
889
1122
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
890
|
-
* @returns {MySqlSelectBuilder<TSelection,
|
|
1123
|
+
* @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
891
1124
|
* @throws {Error} If fields parameter is empty
|
|
892
1125
|
* @example
|
|
893
1126
|
* ```typescript
|
|
@@ -905,7 +1138,14 @@ class ForgeSQLORM {
|
|
|
905
1138
|
* @returns CRUD operations.
|
|
906
1139
|
*/
|
|
907
1140
|
crud() {
|
|
908
|
-
return this.ormInstance.
|
|
1141
|
+
return this.ormInstance.modify();
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Proxies the `modify` method from `ForgeSQLORMImpl`.
|
|
1145
|
+
* @returns Modify operations.
|
|
1146
|
+
*/
|
|
1147
|
+
modify() {
|
|
1148
|
+
return this.ormInstance.modify();
|
|
909
1149
|
}
|
|
910
1150
|
/**
|
|
911
1151
|
* Proxies the `fetch` method from `ForgeSQLORMImpl`.
|
|
@@ -914,6 +1154,13 @@ class ForgeSQLORM {
|
|
|
914
1154
|
fetch() {
|
|
915
1155
|
return this.ormInstance.fetch();
|
|
916
1156
|
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
|
|
1159
|
+
* @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
|
|
1160
|
+
*/
|
|
1161
|
+
analyze() {
|
|
1162
|
+
return this.ormInstance.analyze();
|
|
1163
|
+
}
|
|
917
1164
|
/**
|
|
918
1165
|
* Returns a Drizzle query builder instance.
|
|
919
1166
|
*
|
|
@@ -974,8 +1221,19 @@ const forgeTimeString = customType({
|
|
|
974
1221
|
return parseDateTime(value, "HH:mm:ss.SSS");
|
|
975
1222
|
}
|
|
976
1223
|
});
|
|
977
|
-
|
|
1224
|
+
const migrations = mysqlTable("__migrations", {
|
|
1225
|
+
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
1226
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
1227
|
+
migratedAt: timestamp("migratedAt").defaultNow().notNull()
|
|
1228
|
+
});
|
|
1229
|
+
async function getTables() {
|
|
1230
|
+
const tables = await sql$1.executeDDL("SHOW TABLES");
|
|
1231
|
+
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
1232
|
+
}
|
|
1233
|
+
const forgeSystemTables = [migrations];
|
|
1234
|
+
async function dropSchemaMigrations() {
|
|
978
1235
|
try {
|
|
1236
|
+
const tables = await getTables();
|
|
979
1237
|
const dropStatements = generateDropTableStatements(tables);
|
|
980
1238
|
for (const statement of dropStatements) {
|
|
981
1239
|
console.warn(statement);
|
|
@@ -986,32 +1244,41 @@ async function dropSchemaMigrations(tables) {
|
|
|
986
1244
|
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
|
|
987
1245
|
);
|
|
988
1246
|
} catch (error) {
|
|
1247
|
+
console.error(error);
|
|
989
1248
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
990
1249
|
return getHttpResponse(500, errorMessage);
|
|
991
1250
|
}
|
|
992
1251
|
}
|
|
993
1252
|
const applySchemaMigrations = async (migration) => {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1253
|
+
try {
|
|
1254
|
+
if (typeof migration !== "function") {
|
|
1255
|
+
throw new Error("migration is not a function");
|
|
1256
|
+
}
|
|
1257
|
+
console.log("Provisioning the database");
|
|
1258
|
+
await sql$1._provision();
|
|
1259
|
+
console.info("Running schema migrations");
|
|
1260
|
+
const migrations2 = await migration(migrationRunner);
|
|
1261
|
+
const successfulMigrations = await migrations2.run();
|
|
1262
|
+
console.info("Migrations applied:", successfulMigrations);
|
|
1263
|
+
const migrationList = await migrationRunner.list();
|
|
1264
|
+
const migrationHistory = Array.isArray(migrationList) && migrationList.length > 0 ? migrationList.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n") : "No migrations found";
|
|
1265
|
+
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
1266
|
+
return {
|
|
1267
|
+
headers: { "Content-Type": ["application/json"] },
|
|
1268
|
+
statusCode: 200,
|
|
1269
|
+
statusText: "OK",
|
|
1270
|
+
body: "Migrations successfully executed"
|
|
1271
|
+
};
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error("Error during migration:", error);
|
|
1274
|
+
return {
|
|
1275
|
+
headers: { "Content-Type": ["application/json"] },
|
|
1276
|
+
statusCode: 500,
|
|
1277
|
+
statusText: "Internal Server Error",
|
|
1278
|
+
body: error instanceof Error ? error.message : "Unknown error during migration"
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1008
1281
|
};
|
|
1009
|
-
const migrations = mysqlTable("__migrations", {
|
|
1010
|
-
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
1011
|
-
name: varchar("name", { length: 255 }).notNull(),
|
|
1012
|
-
migratedAt: timestamp("migratedAt").defaultNow().notNull()
|
|
1013
|
-
});
|
|
1014
|
-
const forgeSystemTables = [migrations];
|
|
1015
1282
|
async function fetchSchemaWebTrigger() {
|
|
1016
1283
|
try {
|
|
1017
1284
|
const tables = await getTables();
|
|
@@ -1024,14 +1291,10 @@ async function fetchSchemaWebTrigger() {
|
|
|
1024
1291
|
return getHttpResponse(500, errorMessage);
|
|
1025
1292
|
}
|
|
1026
1293
|
}
|
|
1027
|
-
async function getTables() {
|
|
1028
|
-
const tables = await sql$1.executeDDL("SHOW TABLES");
|
|
1029
|
-
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
1030
|
-
}
|
|
1031
1294
|
async function generateCreateTableStatements(tables) {
|
|
1032
1295
|
const statements = [];
|
|
1033
1296
|
for (const table of tables) {
|
|
1034
|
-
const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE ${table}`);
|
|
1297
|
+
const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE "${table}"`);
|
|
1035
1298
|
const createTableStatements = createTableResult.rows.filter((row) => !isSystemTable(row.Table)).map((row) => formatCreateTableStatement(row["Create Table"]));
|
|
1036
1299
|
statements.push(...createTableStatements);
|
|
1037
1300
|
}
|
|
@@ -1071,14 +1334,17 @@ export {
|
|
|
1071
1334
|
forgeDateString,
|
|
1072
1335
|
forgeDateTimeString,
|
|
1073
1336
|
forgeDriver,
|
|
1337
|
+
forgeSystemTables,
|
|
1074
1338
|
forgeTimeString,
|
|
1075
1339
|
forgeTimestampString,
|
|
1076
1340
|
generateDropTableStatements,
|
|
1077
1341
|
getHttpResponse,
|
|
1078
1342
|
getPrimaryKeys,
|
|
1079
1343
|
getTableMetadata,
|
|
1344
|
+
getTables,
|
|
1080
1345
|
mapSelectAllFieldsToAlias,
|
|
1081
1346
|
mapSelectFieldsWithAlias,
|
|
1347
|
+
migrations,
|
|
1082
1348
|
parseDateTime,
|
|
1083
1349
|
patchDbWithSelectAliased
|
|
1084
1350
|
};
|