forge-sql-orm 2.0.18 → 2.0.19
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 +314 -49
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +314 -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 +4 -12
- package/src/core/ForgeSQLAnalyseOperations.ts +461 -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,239 @@ 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
|
+
async analyzeSlowQueries() {
|
|
922
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(`
|
|
923
|
+
SELECT *
|
|
924
|
+
FROM information_schema.slow_query
|
|
925
|
+
ORDER BY time DESC
|
|
926
|
+
`);
|
|
927
|
+
return results.map((row) => this.normalizeSlowQuery(row));
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Converts a cluster statement row to camelCase format.
|
|
931
|
+
* @param {Record<string, any>} input - The input row data
|
|
932
|
+
* @returns {ClusterStatementRowCamelCase} The converted row data
|
|
933
|
+
*/
|
|
934
|
+
mapToCamelCaseClusterStatement(input) {
|
|
935
|
+
if (!input) {
|
|
936
|
+
return {};
|
|
937
|
+
}
|
|
938
|
+
const result = {};
|
|
939
|
+
result.parsedPlan = this.decodedPlan(input["PLAN"] ?? "");
|
|
940
|
+
for (const key in input) {
|
|
941
|
+
const camelKey = key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
942
|
+
result[camelKey] = input[key];
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Analyzes query history for specific tables using raw table names.
|
|
948
|
+
* @param {string[]} tables - The table names to analyze
|
|
949
|
+
* @param {Date} [fromDate] - The start date for the analysis
|
|
950
|
+
* @param {Date} [toDate] - The end date for the analysis
|
|
951
|
+
* @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
|
|
952
|
+
*/
|
|
953
|
+
async analyzeQueriesHistoryRaw(tables, fromDate, toDate) {
|
|
954
|
+
const results = await this.forgeOperations.fetch().executeRawSQL(
|
|
955
|
+
this.buildClusterStatementQuery(tables ?? [], fromDate, toDate)
|
|
956
|
+
);
|
|
957
|
+
return results.map((r) => this.mapToCamelCaseClusterStatement(r));
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Analyzes query history for specific tables using Drizzle table objects.
|
|
961
|
+
* @param {AnyMySqlTable[]} tables - The Drizzle table objects to analyze
|
|
962
|
+
* @param {Date} [fromDate] - The start date for the analysis
|
|
963
|
+
* @param {Date} [toDate] - The end date for the analysis
|
|
964
|
+
* @returns {Promise<ClusterStatementRowCamelCase[]>} The analyzed query history
|
|
965
|
+
*/
|
|
966
|
+
async analyzeQueriesHistory(tables, fromDate, toDate) {
|
|
967
|
+
const tableNames = tables?.map((table) => getTableName(table)) ?? [];
|
|
968
|
+
return this.analyzeQueriesHistoryRaw(tableNames, fromDate, toDate);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
749
971
|
class ForgeSQLORMImpl {
|
|
750
972
|
static instance = null;
|
|
751
973
|
drizzle;
|
|
752
974
|
crudOperations;
|
|
753
975
|
fetchOperations;
|
|
976
|
+
analyzeOperations;
|
|
754
977
|
/**
|
|
755
978
|
* Private constructor to enforce singleton behavior.
|
|
756
979
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
@@ -770,20 +993,26 @@ class ForgeSQLORMImpl {
|
|
|
770
993
|
);
|
|
771
994
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
772
995
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
996
|
+
this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
|
|
773
997
|
} catch (error) {
|
|
774
998
|
console.error("ForgeSQLORM initialization failed:", error);
|
|
775
999
|
throw error;
|
|
776
1000
|
}
|
|
777
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Create the modify operations instance.
|
|
1004
|
+
* @returns modify operations.
|
|
1005
|
+
*/
|
|
1006
|
+
modify() {
|
|
1007
|
+
return this.crudOperations;
|
|
1008
|
+
}
|
|
778
1009
|
/**
|
|
779
1010
|
* Returns the singleton instance of ForgeSQLORMImpl.
|
|
780
1011
|
* @param options - Options for configuring ForgeSQL ORM behavior.
|
|
781
1012
|
* @returns The singleton instance of ForgeSQLORMImpl.
|
|
782
1013
|
*/
|
|
783
1014
|
static getInstance(options) {
|
|
784
|
-
|
|
785
|
-
ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
|
|
786
|
-
}
|
|
1015
|
+
ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
|
|
787
1016
|
return ForgeSQLORMImpl.instance;
|
|
788
1017
|
}
|
|
789
1018
|
/**
|
|
@@ -791,7 +1020,7 @@ class ForgeSQLORMImpl {
|
|
|
791
1020
|
* @returns CRUD operations.
|
|
792
1021
|
*/
|
|
793
1022
|
crud() {
|
|
794
|
-
return this.
|
|
1023
|
+
return this.modify();
|
|
795
1024
|
}
|
|
796
1025
|
/**
|
|
797
1026
|
* Retrieves the fetch operations instance.
|
|
@@ -800,6 +1029,9 @@ class ForgeSQLORMImpl {
|
|
|
800
1029
|
fetch() {
|
|
801
1030
|
return this.fetchOperations;
|
|
802
1031
|
}
|
|
1032
|
+
analyze() {
|
|
1033
|
+
return this.analyzeOperations;
|
|
1034
|
+
}
|
|
803
1035
|
/**
|
|
804
1036
|
* Returns a Drizzle query builder instance.
|
|
805
1037
|
*
|
|
@@ -887,7 +1119,7 @@ class ForgeSQLORM {
|
|
|
887
1119
|
*
|
|
888
1120
|
* @template TSelection - The type of the selected fields
|
|
889
1121
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
890
|
-
* @returns {MySqlSelectBuilder<TSelection,
|
|
1122
|
+
* @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
891
1123
|
* @throws {Error} If fields parameter is empty
|
|
892
1124
|
* @example
|
|
893
1125
|
* ```typescript
|
|
@@ -905,7 +1137,14 @@ class ForgeSQLORM {
|
|
|
905
1137
|
* @returns CRUD operations.
|
|
906
1138
|
*/
|
|
907
1139
|
crud() {
|
|
908
|
-
return this.ormInstance.
|
|
1140
|
+
return this.ormInstance.modify();
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Proxies the `modify` method from `ForgeSQLORMImpl`.
|
|
1144
|
+
* @returns Modify operations.
|
|
1145
|
+
*/
|
|
1146
|
+
modify() {
|
|
1147
|
+
return this.ormInstance.modify();
|
|
909
1148
|
}
|
|
910
1149
|
/**
|
|
911
1150
|
* Proxies the `fetch` method from `ForgeSQLORMImpl`.
|
|
@@ -914,6 +1153,13 @@ class ForgeSQLORM {
|
|
|
914
1153
|
fetch() {
|
|
915
1154
|
return this.ormInstance.fetch();
|
|
916
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
|
|
1158
|
+
* @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
|
|
1159
|
+
*/
|
|
1160
|
+
analyze() {
|
|
1161
|
+
return this.ormInstance.analyze();
|
|
1162
|
+
}
|
|
917
1163
|
/**
|
|
918
1164
|
* Returns a Drizzle query builder instance.
|
|
919
1165
|
*
|
|
@@ -974,8 +1220,19 @@ const forgeTimeString = customType({
|
|
|
974
1220
|
return parseDateTime(value, "HH:mm:ss.SSS");
|
|
975
1221
|
}
|
|
976
1222
|
});
|
|
977
|
-
|
|
1223
|
+
const migrations = mysqlTable("__migrations", {
|
|
1224
|
+
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
1225
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
1226
|
+
migratedAt: timestamp("migratedAt").defaultNow().notNull()
|
|
1227
|
+
});
|
|
1228
|
+
async function getTables() {
|
|
1229
|
+
const tables = await sql$1.executeDDL("SHOW TABLES");
|
|
1230
|
+
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
1231
|
+
}
|
|
1232
|
+
const forgeSystemTables = [migrations];
|
|
1233
|
+
async function dropSchemaMigrations() {
|
|
978
1234
|
try {
|
|
1235
|
+
const tables = await getTables();
|
|
979
1236
|
const dropStatements = generateDropTableStatements(tables);
|
|
980
1237
|
for (const statement of dropStatements) {
|
|
981
1238
|
console.warn(statement);
|
|
@@ -986,32 +1243,41 @@ async function dropSchemaMigrations(tables) {
|
|
|
986
1243
|
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
|
|
987
1244
|
);
|
|
988
1245
|
} catch (error) {
|
|
1246
|
+
console.error(error);
|
|
989
1247
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
990
1248
|
return getHttpResponse(500, errorMessage);
|
|
991
1249
|
}
|
|
992
1250
|
}
|
|
993
1251
|
const applySchemaMigrations = async (migration) => {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1252
|
+
try {
|
|
1253
|
+
if (typeof migration !== "function") {
|
|
1254
|
+
throw new Error("migration is not a function");
|
|
1255
|
+
}
|
|
1256
|
+
console.log("Provisioning the database");
|
|
1257
|
+
await sql$1._provision();
|
|
1258
|
+
console.info("Running schema migrations");
|
|
1259
|
+
const migrations2 = await migration(migrationRunner);
|
|
1260
|
+
const successfulMigrations = await migrations2.run();
|
|
1261
|
+
console.info("Migrations applied:", successfulMigrations);
|
|
1262
|
+
const migrationList = await migrationRunner.list();
|
|
1263
|
+
const migrationHistory = Array.isArray(migrationList) && migrationList.length > 0 ? migrationList.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n") : "No migrations found";
|
|
1264
|
+
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
1265
|
+
return {
|
|
1266
|
+
headers: { "Content-Type": ["application/json"] },
|
|
1267
|
+
statusCode: 200,
|
|
1268
|
+
statusText: "OK",
|
|
1269
|
+
body: "Migrations successfully executed"
|
|
1270
|
+
};
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
console.error("Error during migration:", error);
|
|
1273
|
+
return {
|
|
1274
|
+
headers: { "Content-Type": ["application/json"] },
|
|
1275
|
+
statusCode: 500,
|
|
1276
|
+
statusText: "Internal Server Error",
|
|
1277
|
+
body: error instanceof Error ? error.message : "Unknown error during migration"
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1008
1280
|
};
|
|
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
1281
|
async function fetchSchemaWebTrigger() {
|
|
1016
1282
|
try {
|
|
1017
1283
|
const tables = await getTables();
|
|
@@ -1024,14 +1290,10 @@ async function fetchSchemaWebTrigger() {
|
|
|
1024
1290
|
return getHttpResponse(500, errorMessage);
|
|
1025
1291
|
}
|
|
1026
1292
|
}
|
|
1027
|
-
async function getTables() {
|
|
1028
|
-
const tables = await sql$1.executeDDL("SHOW TABLES");
|
|
1029
|
-
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
1030
|
-
}
|
|
1031
1293
|
async function generateCreateTableStatements(tables) {
|
|
1032
1294
|
const statements = [];
|
|
1033
1295
|
for (const table of tables) {
|
|
1034
|
-
const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE ${table}`);
|
|
1296
|
+
const createTableResult = await sql$1.executeDDL(`SHOW CREATE TABLE "${table}"`);
|
|
1035
1297
|
const createTableStatements = createTableResult.rows.filter((row) => !isSystemTable(row.Table)).map((row) => formatCreateTableStatement(row["Create Table"]));
|
|
1036
1298
|
statements.push(...createTableStatements);
|
|
1037
1299
|
}
|
|
@@ -1071,14 +1333,17 @@ export {
|
|
|
1071
1333
|
forgeDateString,
|
|
1072
1334
|
forgeDateTimeString,
|
|
1073
1335
|
forgeDriver,
|
|
1336
|
+
forgeSystemTables,
|
|
1074
1337
|
forgeTimeString,
|
|
1075
1338
|
forgeTimestampString,
|
|
1076
1339
|
generateDropTableStatements,
|
|
1077
1340
|
getHttpResponse,
|
|
1078
1341
|
getPrimaryKeys,
|
|
1079
1342
|
getTableMetadata,
|
|
1343
|
+
getTables,
|
|
1080
1344
|
mapSelectAllFieldsToAlias,
|
|
1081
1345
|
mapSelectFieldsWithAlias,
|
|
1346
|
+
migrations,
|
|
1082
1347
|
parseDateTime,
|
|
1083
1348
|
patchDbWithSelectAliased
|
|
1084
1349
|
};
|