forge-sql-orm 2.1.22 → 2.1.24
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 +6 -2
- package/dist/core/ForgeSQLAnalyseOperations.d.ts +4 -0
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.js +17 -21
- package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts +16 -0
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.js +60 -28
- package/dist/core/ForgeSQLCrudOperations.js.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +15 -28
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js +20 -47
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/Rovo.d.ts +32 -0
- package/dist/core/Rovo.d.ts.map +1 -1
- package/dist/core/Rovo.js +116 -67
- package/dist/core/Rovo.js.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +168 -118
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
- package/dist/utils/cacheTableUtils.d.ts +0 -8
- package/dist/utils/cacheTableUtils.d.ts.map +1 -1
- package/dist/utils/cacheTableUtils.js +183 -126
- package/dist/utils/cacheTableUtils.js.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.js +31 -20
- package/dist/utils/forgeDriverProxy.js.map +1 -1
- package/dist/utils/sqlHints.d.ts.map +1 -1
- package/dist/utils/sqlHints.js +19 -29
- package/dist/utils/sqlHints.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +0 -29
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +107 -78
- package/dist/utils/sqlUtils.js.map +1 -1
- package/package.json +13 -13
- package/src/core/ForgeSQLAnalyseOperations.ts +18 -21
- package/src/core/ForgeSQLCrudOperations.ts +83 -33
- package/src/core/ForgeSQLQueryBuilder.ts +59 -154
- package/src/core/Rovo.ts +158 -98
- package/src/lib/drizzle/extensions/additionalActions.ts +287 -382
- package/src/utils/cacheTableUtils.ts +202 -144
- package/src/utils/forgeDriverProxy.ts +39 -21
- package/src/utils/sqlHints.ts +21 -26
- package/src/utils/sqlUtils.ts +151 -101
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// qlty-ignore: +qlty:file-complexity
|
|
1
2
|
import {
|
|
2
3
|
and,
|
|
3
4
|
AnyColumn,
|
|
@@ -60,40 +61,46 @@ interface ConfigBuilderData {
|
|
|
60
61
|
[key: string]: any;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Parses a string value using multiple DateTime parsers.
|
|
66
|
+
*/
|
|
67
|
+
function parseStringToDate(value: string, format: string): Date {
|
|
68
|
+
// 1. Try to parse using the provided format (strict mode)
|
|
69
|
+
const dt = DateTime.fromFormat(value, format);
|
|
70
|
+
if (dt.isValid) {
|
|
71
|
+
return dt.toJSDate();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Try to parse as SQL string
|
|
75
|
+
const sqlDt = DateTime.fromSQL(value);
|
|
76
|
+
if (sqlDt.isValid) {
|
|
77
|
+
return sqlDt.toJSDate();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Try to parse as RFC2822 string
|
|
81
|
+
const isoDt = DateTime.fromRFC2822(value);
|
|
82
|
+
if (isoDt.isValid) {
|
|
83
|
+
return isoDt.toJSDate();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. Fallback: use native Date constructor
|
|
87
|
+
return new Date(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
/**
|
|
64
91
|
* Parses a date string into a Date object using the specified format
|
|
65
92
|
* @param value - The date string to parse or Date
|
|
66
93
|
* @param format - The format to use for parsing
|
|
67
94
|
* @returns Date object
|
|
68
95
|
*/
|
|
69
|
-
|
|
70
96
|
export const parseDateTime = (value: string | Date, format: string): Date => {
|
|
71
97
|
let result: Date;
|
|
72
98
|
if (value instanceof Date) {
|
|
73
99
|
result = value;
|
|
74
100
|
} else {
|
|
75
|
-
|
|
76
|
-
const dt = DateTime.fromFormat(value, format);
|
|
77
|
-
if (dt.isValid) {
|
|
78
|
-
result = dt.toJSDate();
|
|
79
|
-
} else {
|
|
80
|
-
// 2. Try to parse as SQL string
|
|
81
|
-
const sqlDt = DateTime.fromSQL(value);
|
|
82
|
-
if (sqlDt.isValid) {
|
|
83
|
-
result = sqlDt.toJSDate();
|
|
84
|
-
} else {
|
|
85
|
-
// 3. Try to parse as RFC2822 string
|
|
86
|
-
const isoDt = DateTime.fromRFC2822(value);
|
|
87
|
-
if (isoDt.isValid) {
|
|
88
|
-
result = isoDt.toJSDate();
|
|
89
|
-
} else {
|
|
90
|
-
// 4. Fallback: use native Date constructor
|
|
91
|
-
result = new Date(value);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
101
|
+
result = parseStringToDate(value, format);
|
|
95
102
|
}
|
|
96
|
-
//
|
|
103
|
+
// Ensure the result is a valid Date object
|
|
97
104
|
if (Number.isNaN(result.getTime())) {
|
|
98
105
|
result = new Date(value);
|
|
99
106
|
}
|
|
@@ -667,38 +674,61 @@ export function applyFromDriverTransform<T, TSelection>(
|
|
|
667
674
|
});
|
|
668
675
|
}
|
|
669
676
|
|
|
677
|
+
/**
|
|
678
|
+
* Checks if an object is a plain object (not Date, Array, etc.).
|
|
679
|
+
*/
|
|
680
|
+
function isPlainObject(obj: unknown): boolean {
|
|
681
|
+
return (
|
|
682
|
+
obj !== null &&
|
|
683
|
+
typeof obj === "object" &&
|
|
684
|
+
(!obj.constructor || obj.constructor.name === "Object")
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Processes a single value in the null branches processing.
|
|
690
|
+
*/
|
|
691
|
+
function processValueEntry(
|
|
692
|
+
key: string,
|
|
693
|
+
value: unknown,
|
|
694
|
+
result: Record<string, unknown>,
|
|
695
|
+
allNull: { value: boolean },
|
|
696
|
+
): void {
|
|
697
|
+
if (value === null || value === undefined) {
|
|
698
|
+
result[key] = null;
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (typeof value === "object" && isPlainObject(value)) {
|
|
703
|
+
const processed = processNullBranches(value as Record<string, unknown>);
|
|
704
|
+
result[key] = processed;
|
|
705
|
+
if (processed !== null) {
|
|
706
|
+
allNull.value = false;
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
result[key] = value;
|
|
710
|
+
allNull.value = false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
670
714
|
function processNullBranches(obj: Record<string, unknown>): Record<string, unknown> | null {
|
|
671
715
|
if (obj === null || typeof obj !== "object") {
|
|
672
716
|
return obj;
|
|
673
717
|
}
|
|
674
718
|
|
|
675
719
|
// Skip built-in objects like Date, Array, etc.
|
|
676
|
-
if (obj
|
|
720
|
+
if (!isPlainObject(obj)) {
|
|
677
721
|
return obj;
|
|
678
722
|
}
|
|
679
723
|
|
|
680
724
|
const result: Record<string, unknown> = {};
|
|
681
|
-
|
|
725
|
+
const allNull = { value: true };
|
|
682
726
|
|
|
683
727
|
for (const [key, value] of Object.entries(obj)) {
|
|
684
|
-
|
|
685
|
-
result[key] = null;
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (typeof value === "object") {
|
|
690
|
-
const processed = processNullBranches(value as Record<string, unknown>);
|
|
691
|
-
result[key] = processed;
|
|
692
|
-
if (processed !== null) {
|
|
693
|
-
allNull = false;
|
|
694
|
-
}
|
|
695
|
-
} else {
|
|
696
|
-
result[key] = value;
|
|
697
|
-
allNull = false;
|
|
698
|
-
}
|
|
728
|
+
processValueEntry(key, value, result, allNull);
|
|
699
729
|
}
|
|
700
730
|
|
|
701
|
-
return allNull ? null : result;
|
|
731
|
+
return allNull.value ? null : result;
|
|
702
732
|
}
|
|
703
733
|
|
|
704
734
|
export function formatLimitOffset(limitOrOffset: number): number {
|
|
@@ -745,6 +775,37 @@ function buildClusterStatementsSummaryQuery(forgeSQLORM: ForgeSqlOperation, time
|
|
|
745
775
|
);
|
|
746
776
|
}
|
|
747
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Formats and logs query performance result.
|
|
780
|
+
*/
|
|
781
|
+
function formatAndLogQueryResult(result: {
|
|
782
|
+
digestText: string;
|
|
783
|
+
avgLatency: number | bigint;
|
|
784
|
+
avgMem: number | bigint;
|
|
785
|
+
stmtType: string;
|
|
786
|
+
execCount: number | bigint;
|
|
787
|
+
plan: string | null;
|
|
788
|
+
}): void {
|
|
789
|
+
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
|
|
790
|
+
const avgMemMB = Number(result.avgMem) / 1_000_000;
|
|
791
|
+
|
|
792
|
+
// eslint-disable-next-line no-console
|
|
793
|
+
console.warn(
|
|
794
|
+
`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${Number(result.execCount)}\n Plan:${result.plan}`,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Handles errors in query execution plan retrieval.
|
|
800
|
+
*/
|
|
801
|
+
function handleQueryPlanError(error: unknown): void {
|
|
802
|
+
// eslint-disable-next-line no-console
|
|
803
|
+
console.debug(
|
|
804
|
+
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
|
|
805
|
+
error,
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
748
809
|
/**
|
|
749
810
|
* Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
|
|
750
811
|
*
|
|
@@ -785,22 +846,10 @@ export async function printQueriesWithPlan(
|
|
|
785
846
|
);
|
|
786
847
|
|
|
787
848
|
for (const result of results) {
|
|
788
|
-
|
|
789
|
-
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
|
|
790
|
-
const avgMemMB = Number(result.avgMem) / 1_000_000;
|
|
791
|
-
|
|
792
|
-
// 1. Query info: SQL, memory, time, executions
|
|
793
|
-
// eslint-disable-next-line no-console
|
|
794
|
-
console.warn(
|
|
795
|
-
`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`,
|
|
796
|
-
);
|
|
849
|
+
formatAndLogQueryResult(result);
|
|
797
850
|
}
|
|
798
851
|
} catch (error) {
|
|
799
|
-
|
|
800
|
-
console.debug(
|
|
801
|
-
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
|
|
802
|
-
error,
|
|
803
|
-
);
|
|
852
|
+
handleQueryPlanError(error);
|
|
804
853
|
}
|
|
805
854
|
}
|
|
806
855
|
|
|
@@ -823,22 +872,10 @@ export async function handleErrorsWithPlan(
|
|
|
823
872
|
);
|
|
824
873
|
|
|
825
874
|
for (const result of results) {
|
|
826
|
-
|
|
827
|
-
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
|
|
828
|
-
const avgMemMB = Number(result.avgMem) / 1_000_000;
|
|
829
|
-
|
|
830
|
-
// 1. Query info: SQL, memory, time, executions
|
|
831
|
-
// eslint-disable-next-line no-console
|
|
832
|
-
console.warn(
|
|
833
|
-
`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`,
|
|
834
|
-
);
|
|
875
|
+
formatAndLogQueryResult(result);
|
|
835
876
|
}
|
|
836
877
|
} catch (error) {
|
|
837
|
-
|
|
838
|
-
console.debug(
|
|
839
|
-
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
|
|
840
|
-
error,
|
|
841
|
-
);
|
|
878
|
+
handleQueryPlanError(error);
|
|
842
879
|
}
|
|
843
880
|
}
|
|
844
881
|
|
|
@@ -873,6 +910,48 @@ const SESSION_ALIAS_NAME_ORM = "orm";
|
|
|
873
910
|
*
|
|
874
911
|
* @throws Does not throw - errors are logged to console.debug instead
|
|
875
912
|
*/
|
|
913
|
+
/**
|
|
914
|
+
* Builds slow query query for specified hours.
|
|
915
|
+
*/
|
|
916
|
+
function buildSlowQueryQuery(forgeSQLORM: ForgeSqlOperation, hours: number) {
|
|
917
|
+
return forgeSQLORM
|
|
918
|
+
.getDrizzleQueryBuilder()
|
|
919
|
+
.select({
|
|
920
|
+
query: withTidbHint(slowQuery.query),
|
|
921
|
+
queryTime: slowQuery.queryTime,
|
|
922
|
+
memMax: slowQuery.memMax,
|
|
923
|
+
plan: slowQuery.plan,
|
|
924
|
+
})
|
|
925
|
+
.from(slowQuery)
|
|
926
|
+
.where(
|
|
927
|
+
and(
|
|
928
|
+
isNotNull(slowQuery.digest),
|
|
929
|
+
ne(slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM),
|
|
930
|
+
gte(
|
|
931
|
+
slowQuery.time,
|
|
932
|
+
sql`DATE_SUB
|
|
933
|
+
(NOW(), INTERVAL
|
|
934
|
+
${hours}
|
|
935
|
+
HOUR
|
|
936
|
+
)`,
|
|
937
|
+
),
|
|
938
|
+
),
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Formats slow query result message.
|
|
944
|
+
*/
|
|
945
|
+
function formatSlowQueryMessage(result: {
|
|
946
|
+
query: string | null;
|
|
947
|
+
queryTime: number | null;
|
|
948
|
+
memMax: number | bigint | null;
|
|
949
|
+
plan: string | null;
|
|
950
|
+
}): string {
|
|
951
|
+
const memMaxMB = result.memMax ? Number(result.memMax) / 1_000_000 : 0;
|
|
952
|
+
return `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms\n Plan:${result.plan}`;
|
|
953
|
+
}
|
|
954
|
+
|
|
876
955
|
export async function slowQueryPerHours(
|
|
877
956
|
forgeSQLORM: ForgeSqlOperation,
|
|
878
957
|
hours: number,
|
|
@@ -880,51 +959,22 @@ export async function slowQueryPerHours(
|
|
|
880
959
|
) {
|
|
881
960
|
try {
|
|
882
961
|
const timeoutMs = timeout ?? 1500;
|
|
962
|
+
const query = buildSlowQueryQuery(forgeSQLORM, hours);
|
|
883
963
|
const results = await withTimeout(
|
|
884
|
-
|
|
885
|
-
.getDrizzleQueryBuilder()
|
|
886
|
-
.select({
|
|
887
|
-
query: withTidbHint(slowQuery.query),
|
|
888
|
-
queryTime: slowQuery.queryTime,
|
|
889
|
-
memMax: slowQuery.memMax,
|
|
890
|
-
plan: slowQuery.plan,
|
|
891
|
-
})
|
|
892
|
-
.from(slowQuery)
|
|
893
|
-
.where(
|
|
894
|
-
and(
|
|
895
|
-
isNotNull(slowQuery.digest),
|
|
896
|
-
ne(slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM),
|
|
897
|
-
gte(
|
|
898
|
-
slowQuery.time,
|
|
899
|
-
sql`DATE_SUB
|
|
900
|
-
(NOW(), INTERVAL
|
|
901
|
-
${hours}
|
|
902
|
-
HOUR
|
|
903
|
-
)`,
|
|
904
|
-
),
|
|
905
|
-
),
|
|
906
|
-
),
|
|
964
|
+
query,
|
|
907
965
|
`Timeout ${timeoutMs}ms in slowQueryPerHours - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
|
|
908
966
|
timeoutMs,
|
|
909
967
|
);
|
|
910
968
|
const response: string[] = [];
|
|
911
969
|
for (const result of results) {
|
|
912
|
-
|
|
913
|
-
const memMaxMB = result.memMax ? Number(result.memMax) / 1_000_000 : 0;
|
|
914
|
-
|
|
915
|
-
const message = `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms\n Plan:${result.plan}`;
|
|
970
|
+
const message = formatSlowQueryMessage(result);
|
|
916
971
|
response.push(message);
|
|
917
|
-
// 1. Query info: SQL, memory, time, executions
|
|
918
972
|
// eslint-disable-next-line no-console
|
|
919
973
|
console.warn(message);
|
|
920
974
|
}
|
|
921
975
|
return response;
|
|
922
976
|
} catch (error) {
|
|
923
|
-
|
|
924
|
-
console.debug(
|
|
925
|
-
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`,
|
|
926
|
-
error,
|
|
927
|
-
);
|
|
977
|
+
handleQueryPlanError(error);
|
|
928
978
|
return [
|
|
929
979
|
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
930
980
|
];
|