forge-sql-orm 2.1.15 → 2.1.17
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 +194 -1
- package/dist/async/PrintQueryConsumer.d.ts +98 -0
- package/dist/async/PrintQueryConsumer.d.ts.map +1 -0
- package/dist/async/PrintQueryConsumer.js +89 -0
- package/dist/async/PrintQueryConsumer.js.map +1 -0
- package/dist/core/ForgeSQLQueryBuilder.d.ts +2 -3
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +2 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.js.map +1 -1
- package/dist/core/Rovo.d.ts +40 -0
- package/dist/core/Rovo.d.ts.map +1 -1
- package/dist/core/Rovo.js +164 -138
- package/dist/core/Rovo.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +72 -22
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
- package/dist/utils/cacheTableUtils.d.ts +11 -0
- package/dist/utils/cacheTableUtils.d.ts.map +1 -0
- package/dist/utils/cacheTableUtils.js +450 -0
- package/dist/utils/cacheTableUtils.js.map +1 -0
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.js +3 -22
- package/dist/utils/cacheUtils.js.map +1 -1
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriver.js +5 -12
- package/dist/utils/forgeDriver.js.map +1 -1
- package/dist/utils/forgeDriverProxy.js +7 -5
- package/dist/utils/forgeDriverProxy.js.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +44 -4
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +155 -50
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/utils/sqlUtils.d.ts +3 -1
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +264 -144
- package/dist/utils/sqlUtils.js.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.js +1 -1
- package/package.json +14 -13
- package/src/async/PrintQueryConsumer.ts +114 -0
- package/src/core/ForgeSQLQueryBuilder.ts +2 -2
- package/src/core/ForgeSQLSelectOperations.ts +2 -1
- package/src/core/Rovo.ts +209 -167
- package/src/index.ts +2 -3
- package/src/lib/drizzle/extensions/additionalActions.ts +98 -42
- package/src/utils/cacheTableUtils.ts +511 -0
- package/src/utils/cacheUtils.ts +3 -25
- package/src/utils/forgeDriver.ts +5 -11
- package/src/utils/forgeDriverProxy.ts +9 -9
- package/src/utils/metadataContextUtils.ts +169 -52
- package/src/utils/sqlUtils.ts +372 -177
- package/src/webtriggers/applyMigrationsWebTrigger.ts +1 -1
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
and,
|
|
3
3
|
AnyColumn,
|
|
4
4
|
Column,
|
|
5
|
+
desc,
|
|
5
6
|
gte,
|
|
6
7
|
ilike,
|
|
7
8
|
isNotNull,
|
|
@@ -100,37 +101,37 @@ export const parseDateTime = (value: string | Date, format: string): Date => {
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
/**
|
|
103
|
-
*
|
|
104
|
-
* @param value - Date object, ISO/RFC2822/SQL/HTTP string, or timestamp (number|string).
|
|
105
|
-
* @param format - DateTime format string (Luxon format tokens).
|
|
106
|
-
* @returns Formatted date string.
|
|
107
|
-
* @throws Error if value cannot be parsed as a valid date.
|
|
104
|
+
* Parses a string value into DateTime using multiple format parsers
|
|
108
105
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
function parseStringToDateTime(value: string): DateTime | null {
|
|
107
|
+
const parsers = [DateTime.fromISO, DateTime.fromRFC2822, DateTime.fromSQL, DateTime.fromHTTP];
|
|
108
|
+
|
|
109
|
+
for (const parser of parsers) {
|
|
110
|
+
const dt = parser(value);
|
|
111
|
+
if (dt.isValid) {
|
|
112
|
+
return dt;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Try parsing as number string
|
|
117
|
+
const parsed = Number(value);
|
|
118
|
+
if (!Number.isNaN(parsed)) {
|
|
119
|
+
return DateTime.fromMillis(parsed);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Converts a value to DateTime
|
|
127
|
+
*/
|
|
128
|
+
function valueToDateTime(value: Date | string | number): DateTime {
|
|
114
129
|
let dt: DateTime | null = null;
|
|
115
130
|
|
|
116
131
|
if (value instanceof Date) {
|
|
117
132
|
dt = DateTime.fromJSDate(value);
|
|
118
133
|
} else if (typeof value === "string") {
|
|
119
|
-
|
|
120
|
-
DateTime.fromISO,
|
|
121
|
-
DateTime.fromRFC2822,
|
|
122
|
-
DateTime.fromSQL,
|
|
123
|
-
DateTime.fromHTTP,
|
|
124
|
-
]) {
|
|
125
|
-
dt = parser(value);
|
|
126
|
-
if (dt.isValid) break;
|
|
127
|
-
}
|
|
128
|
-
if (!dt?.isValid) {
|
|
129
|
-
const parsed = Number(value);
|
|
130
|
-
if (!Number.isNaN(parsed)) {
|
|
131
|
-
dt = DateTime.fromMillis(parsed);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
+
dt = parseStringToDateTime(value);
|
|
134
135
|
} else if (typeof value === "number") {
|
|
135
136
|
dt = DateTime.fromMillis(value);
|
|
136
137
|
} else {
|
|
@@ -140,20 +141,47 @@ export function formatDateTime(
|
|
|
140
141
|
if (!dt?.isValid) {
|
|
141
142
|
throw new Error("Invalid Date");
|
|
142
143
|
}
|
|
144
|
+
|
|
145
|
+
return dt;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validates timestamp range for Atlassian Forge compatibility
|
|
150
|
+
*/
|
|
151
|
+
function validateTimestampRange(dt: DateTime): void {
|
|
143
152
|
const minDate = DateTime.fromSeconds(1);
|
|
144
153
|
const maxDate = DateTime.fromMillis(2147483647 * 1000); // 2038-01-19 03:14:07.999 UTC
|
|
145
154
|
|
|
155
|
+
if (dt < minDate) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"Atlassian Forge does not support zero or negative timestamps. Allowed range: from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'.",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (dt > maxDate) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"Atlassian Forge does not support timestamps beyond 2038-01-19 03:14:07.999999. Please use a smaller date within the supported range.",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Helper function to validate and format a date-like value using Luxon DateTime.
|
|
170
|
+
* @param value - Date object, ISO/RFC2822/SQL/HTTP string, or timestamp (number|string).
|
|
171
|
+
* @param format - DateTime format string (Luxon format tokens).
|
|
172
|
+
* @param isTimeStamp - Whether to validate timestamp range
|
|
173
|
+
* @returns Formatted date string.
|
|
174
|
+
* @throws Error if value cannot be parsed as a valid date.
|
|
175
|
+
*/
|
|
176
|
+
export function formatDateTime(
|
|
177
|
+
value: Date | string | number,
|
|
178
|
+
format: string,
|
|
179
|
+
isTimeStamp: boolean,
|
|
180
|
+
): string {
|
|
181
|
+
const dt = valueToDateTime(value);
|
|
182
|
+
|
|
146
183
|
if (isTimeStamp) {
|
|
147
|
-
|
|
148
|
-
throw new Error(
|
|
149
|
-
"Atlassian Forge does not support zero or negative timestamps. Allowed range: from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'.",
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
if (dt > maxDate) {
|
|
153
|
-
throw new Error(
|
|
154
|
-
"Atlassian Forge does not support timestamps beyond 2038-01-19 03:14:07.999999. Please use a smaller date within the supported range.",
|
|
155
|
-
);
|
|
156
|
-
}
|
|
184
|
+
validateTimestampRange(dt);
|
|
157
185
|
}
|
|
158
186
|
|
|
159
187
|
return dt.toFormat(format);
|
|
@@ -169,10 +197,7 @@ export function getPrimaryKeys<T extends AnyMySqlTable>(table: T): [string, AnyC
|
|
|
169
197
|
const { columns, primaryKeys } = getTableMetadata(table);
|
|
170
198
|
|
|
171
199
|
// First try to find primary keys in columns
|
|
172
|
-
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary)
|
|
173
|
-
string,
|
|
174
|
-
AnyColumn,
|
|
175
|
-
][];
|
|
200
|
+
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
|
|
176
201
|
|
|
177
202
|
if (columnPrimaryKeys.length > 0) {
|
|
178
203
|
return columnPrimaryKeys;
|
|
@@ -199,6 +224,85 @@ export function getPrimaryKeys<T extends AnyMySqlTable>(table: T): [string, AnyC
|
|
|
199
224
|
return [];
|
|
200
225
|
}
|
|
201
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Processes foreign keys from foreignKeysSymbol
|
|
229
|
+
*/
|
|
230
|
+
function processForeignKeysFromSymbol(
|
|
231
|
+
table: AnyMySqlTable,
|
|
232
|
+
foreignKeysSymbol: symbol,
|
|
233
|
+
): ForeignKeyBuilder[] {
|
|
234
|
+
const foreignKeys: ForeignKeyBuilder[] = [];
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
const fkArray: any[] = table[foreignKeysSymbol];
|
|
237
|
+
|
|
238
|
+
if (!fkArray) {
|
|
239
|
+
return foreignKeys;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const fk of fkArray) {
|
|
243
|
+
if (fk.reference) {
|
|
244
|
+
const item = fk.reference(fk);
|
|
245
|
+
foreignKeys.push(item);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return foreignKeys;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Extracts config builders from config builder data
|
|
254
|
+
*/
|
|
255
|
+
function extractConfigBuilders(configBuilderData: any): any[] {
|
|
256
|
+
if (Array.isArray(configBuilderData)) {
|
|
257
|
+
return configBuilderData;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return Object.values(configBuilderData).map((item) => (item as ConfigBuilderData).value ?? item);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Checks if a builder is a ForeignKeyBuilder
|
|
265
|
+
*/
|
|
266
|
+
function isForeignKeyBuilder(builder: any): boolean {
|
|
267
|
+
if (!builder?.constructor) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const builderName = builder.constructor.name.toLowerCase();
|
|
272
|
+
return builderName.includes("foreignkeybuilder");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Processes foreign keys from extraSymbol
|
|
277
|
+
*/
|
|
278
|
+
function processForeignKeysFromExtra(
|
|
279
|
+
table: AnyMySqlTable,
|
|
280
|
+
extraSymbol: symbol,
|
|
281
|
+
): ForeignKeyBuilder[] {
|
|
282
|
+
const foreignKeys: ForeignKeyBuilder[] = [];
|
|
283
|
+
// @ts-ignore
|
|
284
|
+
const extraConfigBuilder = table[extraSymbol];
|
|
285
|
+
|
|
286
|
+
if (!extraConfigBuilder || typeof extraConfigBuilder !== "function") {
|
|
287
|
+
return foreignKeys;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const configBuilderData = extraConfigBuilder(table);
|
|
291
|
+
if (!configBuilderData) {
|
|
292
|
+
return foreignKeys;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const configBuilders = extractConfigBuilders(configBuilderData);
|
|
296
|
+
|
|
297
|
+
for (const builder of configBuilders) {
|
|
298
|
+
if (isForeignKeyBuilder(builder)) {
|
|
299
|
+
foreignKeys.push(builder);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return foreignKeys;
|
|
304
|
+
}
|
|
305
|
+
|
|
202
306
|
/**
|
|
203
307
|
* Processes foreign keys from both foreignKeysSymbol and extraSymbol
|
|
204
308
|
* @param table - The table schema
|
|
@@ -215,57 +319,117 @@ function processForeignKeys(
|
|
|
215
319
|
|
|
216
320
|
// Process foreign keys from foreignKeysSymbol
|
|
217
321
|
if (foreignKeysSymbol) {
|
|
218
|
-
|
|
219
|
-
const fkArray: any[] = table[foreignKeysSymbol];
|
|
220
|
-
if (fkArray) {
|
|
221
|
-
for (const fk of fkArray) {
|
|
222
|
-
if (fk.reference) {
|
|
223
|
-
const item = fk.reference(fk);
|
|
224
|
-
foreignKeys.push(item);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
322
|
+
foreignKeys.push(...processForeignKeysFromSymbol(table, foreignKeysSymbol));
|
|
228
323
|
}
|
|
229
324
|
|
|
230
325
|
// Process foreign keys from extraSymbol
|
|
231
326
|
if (extraSymbol) {
|
|
232
|
-
|
|
233
|
-
const extraConfigBuilder = table[extraSymbol];
|
|
234
|
-
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
235
|
-
const configBuilderData = extraConfigBuilder(table);
|
|
236
|
-
if (configBuilderData) {
|
|
237
|
-
const configBuilders = Array.isArray(configBuilderData)
|
|
238
|
-
? configBuilderData
|
|
239
|
-
: Object.values(configBuilderData).map(
|
|
240
|
-
(item) => (item as ConfigBuilderData).value ?? item,
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
for (const builder of configBuilders) {
|
|
244
|
-
if (!builder?.constructor) continue;
|
|
245
|
-
|
|
246
|
-
const builderName = builder.constructor.name.toLowerCase();
|
|
247
|
-
if (builderName.includes("foreignkeybuilder")) {
|
|
248
|
-
foreignKeys.push(builder);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
327
|
+
foreignKeys.push(...processForeignKeysFromExtra(table, extraSymbol));
|
|
253
328
|
}
|
|
254
329
|
|
|
255
330
|
return foreignKeys;
|
|
256
331
|
}
|
|
257
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Extracts symbols from table schema.
|
|
335
|
+
* @param table - The table schema
|
|
336
|
+
* @returns Object containing relevant symbols
|
|
337
|
+
*/
|
|
338
|
+
function extractTableSymbols(table: AnyMySqlTable) {
|
|
339
|
+
const symbols = Object.getOwnPropertySymbols(table);
|
|
340
|
+
return {
|
|
341
|
+
nameSymbol: symbols.find((s) => s.toString().includes("Name")),
|
|
342
|
+
columnsSymbol: symbols.find((s) => s.toString().includes("Columns")),
|
|
343
|
+
foreignKeysSymbol: symbols.find((s) => s.toString().includes("ForeignKeys)")),
|
|
344
|
+
extraSymbol: symbols.find((s) => s.toString().includes("ExtraConfigBuilder")),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Maps builder to appropriate array based on its type.
|
|
350
|
+
* @param builder - The builder object
|
|
351
|
+
* @param builders - The builders object containing all arrays
|
|
352
|
+
* @returns True if builder was added to a specific array, false otherwise
|
|
353
|
+
*/
|
|
354
|
+
function addBuilderToTypedArray(
|
|
355
|
+
builder: any,
|
|
356
|
+
builders: {
|
|
357
|
+
indexes: AnyIndexBuilder[];
|
|
358
|
+
checks: CheckBuilder[];
|
|
359
|
+
primaryKeys: PrimaryKeyBuilder[];
|
|
360
|
+
uniqueConstraints: UniqueConstraintBuilder[];
|
|
361
|
+
},
|
|
362
|
+
): boolean {
|
|
363
|
+
if (!builder?.constructor) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const builderName = builder.constructor.name.toLowerCase();
|
|
368
|
+
const builderMap = {
|
|
369
|
+
indexbuilder: builders.indexes,
|
|
370
|
+
checkbuilder: builders.checks,
|
|
371
|
+
primarykeybuilder: builders.primaryKeys,
|
|
372
|
+
uniqueconstraintbuilder: builders.uniqueConstraints,
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
for (const [type, array] of Object.entries(builderMap)) {
|
|
376
|
+
if (builderName.includes(type)) {
|
|
377
|
+
array.push(builder);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Processes extra configuration builders and adds them to the builders object.
|
|
387
|
+
* @param table - The table schema
|
|
388
|
+
* @param extraSymbol - The extra symbol from table
|
|
389
|
+
* @param builders - The builders object to populate
|
|
390
|
+
*/
|
|
391
|
+
function processExtraConfigBuilders(
|
|
392
|
+
table: AnyMySqlTable,
|
|
393
|
+
extraSymbol: symbol | undefined,
|
|
394
|
+
builders: {
|
|
395
|
+
indexes: AnyIndexBuilder[];
|
|
396
|
+
checks: CheckBuilder[];
|
|
397
|
+
foreignKeys: ForeignKeyBuilder[];
|
|
398
|
+
primaryKeys: PrimaryKeyBuilder[];
|
|
399
|
+
uniqueConstraints: UniqueConstraintBuilder[];
|
|
400
|
+
extras: any[];
|
|
401
|
+
},
|
|
402
|
+
): void {
|
|
403
|
+
if (!extraSymbol) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// @ts-ignore
|
|
408
|
+
const extraConfigBuilder = table[extraSymbol];
|
|
409
|
+
if (!extraConfigBuilder || typeof extraConfigBuilder !== "function") {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const configBuilderData = extraConfigBuilder(table);
|
|
414
|
+
if (!configBuilderData) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const configBuilders = extractConfigBuilders(configBuilderData);
|
|
419
|
+
|
|
420
|
+
for (const builder of configBuilders) {
|
|
421
|
+
addBuilderToTypedArray(builder, builders);
|
|
422
|
+
builders.extras.push(builder);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
258
426
|
/**
|
|
259
427
|
* Extracts table metadata from the schema.
|
|
260
428
|
* @param {AnyMySqlTable} table - The table schema
|
|
261
429
|
* @returns {MetadataInfo} Object containing table metadata
|
|
262
430
|
*/
|
|
263
431
|
export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
|
|
264
|
-
const
|
|
265
|
-
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
266
|
-
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
267
|
-
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
|
|
268
|
-
const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
|
|
432
|
+
const { nameSymbol, columnsSymbol, foreignKeysSymbol, extraSymbol } = extractTableSymbols(table);
|
|
269
433
|
|
|
270
434
|
// Initialize builders arrays
|
|
271
435
|
const builders = {
|
|
@@ -281,47 +445,7 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
|
|
|
281
445
|
builders.foreignKeys = processForeignKeys(table, foreignKeysSymbol, extraSymbol);
|
|
282
446
|
|
|
283
447
|
// Process extra configuration if available
|
|
284
|
-
|
|
285
|
-
// @ts-ignore
|
|
286
|
-
const extraConfigBuilder = table[extraSymbol];
|
|
287
|
-
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
288
|
-
const configBuilderData = extraConfigBuilder(table);
|
|
289
|
-
if (configBuilderData) {
|
|
290
|
-
// Convert configBuilderData to array if it's an object
|
|
291
|
-
const configBuilders = Array.isArray(configBuilderData)
|
|
292
|
-
? configBuilderData
|
|
293
|
-
: Object.values(configBuilderData).map(
|
|
294
|
-
(item) => (item as ConfigBuilderData).value ?? item,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
// Process each builder
|
|
298
|
-
for (const builder of configBuilders) {
|
|
299
|
-
if (!builder?.constructor) continue;
|
|
300
|
-
|
|
301
|
-
const builderName = builder.constructor.name.toLowerCase();
|
|
302
|
-
|
|
303
|
-
// Map builder types to their corresponding arrays
|
|
304
|
-
const builderMap = {
|
|
305
|
-
indexbuilder: builders.indexes,
|
|
306
|
-
checkbuilder: builders.checks,
|
|
307
|
-
primarykeybuilder: builders.primaryKeys,
|
|
308
|
-
uniqueconstraintbuilder: builders.uniqueConstraints,
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// Add builder to appropriate array if it matches any type
|
|
312
|
-
for (const [type, array] of Object.entries(builderMap)) {
|
|
313
|
-
if (builderName.includes(type)) {
|
|
314
|
-
array.push(builder);
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Always add to extras array
|
|
320
|
-
builders.extras.push(builder);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
448
|
+
processExtraConfigBuilders(table, extraSymbol, builders);
|
|
325
449
|
|
|
326
450
|
return {
|
|
327
451
|
tableName: nameSymbol ? (table as any)[nameSymbol] : "",
|
|
@@ -372,7 +496,7 @@ function mapSelectTableToAlias(
|
|
|
372
496
|
const { columns, tableName } = getTableMetadata(table);
|
|
373
497
|
const selectionsTableFields: Record<string, unknown> = {};
|
|
374
498
|
for (const name of Object.keys(columns)) {
|
|
375
|
-
const column = columns[name]
|
|
499
|
+
const column = columns[name];
|
|
376
500
|
const uniqName = `a_${uniqPrefix}_${tableName}_${column.name}`.toLowerCase();
|
|
377
501
|
const fieldAlias = sql.raw(uniqName);
|
|
378
502
|
selectionsTableFields[name] = sql`${column} as \`${fieldAlias}\``;
|
|
@@ -419,38 +543,71 @@ export function mapSelectFieldsWithAlias<TSelection extends SelectedFields>(
|
|
|
419
543
|
}
|
|
420
544
|
const aliasMap: AliasColumnMap = {};
|
|
421
545
|
const selections: any = {};
|
|
422
|
-
for (
|
|
423
|
-
const [name, fields1] = Object.entries(fields)[i];
|
|
546
|
+
for (const [name, fields1] of Object.entries(fields)) {
|
|
424
547
|
mapSelectAllFieldsToAlias(selections, name, name, fields1, aliasMap);
|
|
425
548
|
}
|
|
426
549
|
return { selections, aliasMap };
|
|
427
550
|
}
|
|
428
551
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
552
|
+
/**
|
|
553
|
+
* Checks if value is a SQL object with queryChunks
|
|
554
|
+
*/
|
|
555
|
+
function isSQLValue(value: unknown): value is SQL {
|
|
556
|
+
return (
|
|
557
|
+
value !== null && typeof value === "object" && isSQLWrapper(value) && "queryChunks" in value
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Extracts the alias name chunk from query chunks if it exists and is a SQL object
|
|
563
|
+
*/
|
|
564
|
+
function getAliasNameChunk(queryChunks: any[]): SQL | undefined {
|
|
565
|
+
if (queryChunks.length <= 3) {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const aliasNameChunk = queryChunks.at(-2);
|
|
570
|
+
if (isSQLWrapper(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
|
|
571
|
+
return aliasNameChunk as SQL;
|
|
450
572
|
}
|
|
573
|
+
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Extracts string value from a SQL chunk if it contains a single string value
|
|
579
|
+
*/
|
|
580
|
+
function extractStringValueFromChunk(chunk: SQL): string | undefined {
|
|
581
|
+
if (chunk.queryChunks?.length !== 1 || !chunk.queryChunks[0]) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const stringChunk = chunk.queryChunks[0];
|
|
586
|
+
if (!("value" in stringChunk)) {
|
|
587
|
+
return undefined;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const values = (stringChunk as StringChunk).value;
|
|
591
|
+
if (values?.length === 1) {
|
|
592
|
+
return values[0];
|
|
593
|
+
}
|
|
594
|
+
|
|
451
595
|
return undefined;
|
|
452
596
|
}
|
|
453
597
|
|
|
598
|
+
function getAliasFromDrizzleAlias(value: unknown): string | undefined {
|
|
599
|
+
if (!isSQLValue(value)) {
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const aliasNameChunk = getAliasNameChunk(value.queryChunks);
|
|
604
|
+
if (!aliasNameChunk) {
|
|
605
|
+
return undefined;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return extractStringValueFromChunk(aliasNameChunk);
|
|
609
|
+
}
|
|
610
|
+
|
|
454
611
|
function transformValue(
|
|
455
612
|
value: unknown,
|
|
456
613
|
alias: string,
|
|
@@ -504,7 +661,7 @@ export function applyFromDriverTransform<T, TSelection>(
|
|
|
504
661
|
row as Record<string, unknown>,
|
|
505
662
|
selections as Record<string, unknown>,
|
|
506
663
|
aliasMap,
|
|
507
|
-
)
|
|
664
|
+
);
|
|
508
665
|
|
|
509
666
|
return processNullBranches(transformed) as unknown as T;
|
|
510
667
|
});
|
|
@@ -555,6 +712,39 @@ export function nextVal(sequenceName: string): number {
|
|
|
555
712
|
return sql.raw(`NEXTVAL(${sequenceName})`) as unknown as number;
|
|
556
713
|
}
|
|
557
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Helper function to build base query for CLUSTER_STATEMENTS_SUMMARY table
|
|
717
|
+
*/
|
|
718
|
+
function buildClusterStatementsSummaryQuery(forgeSQLORM: ForgeSqlOperation, timeDiffMs: number) {
|
|
719
|
+
const statementsTable = clusterStatementsSummary;
|
|
720
|
+
return forgeSQLORM
|
|
721
|
+
.getDrizzleQueryBuilder()
|
|
722
|
+
.select({
|
|
723
|
+
digestText: withTidbHint(statementsTable.digestText),
|
|
724
|
+
avgLatency: statementsTable.avgLatency,
|
|
725
|
+
avgMem: statementsTable.avgMem,
|
|
726
|
+
execCount: statementsTable.execCount,
|
|
727
|
+
plan: statementsTable.plan,
|
|
728
|
+
stmtType: statementsTable.stmtType,
|
|
729
|
+
})
|
|
730
|
+
.from(statementsTable)
|
|
731
|
+
.where(
|
|
732
|
+
and(
|
|
733
|
+
isNotNull(statementsTable.digest),
|
|
734
|
+
not(ilike(statementsTable.digestText, "%information_schema%")),
|
|
735
|
+
notInArray(statementsTable.stmtType, ["Use", "Set", "Show", "Commit", "Rollback", "Begin"]),
|
|
736
|
+
gte(
|
|
737
|
+
statementsTable.lastSeen,
|
|
738
|
+
sql`DATE_SUB
|
|
739
|
+
(NOW(), INTERVAL
|
|
740
|
+
${timeDiffMs * 1000}
|
|
741
|
+
MICROSECOND
|
|
742
|
+
)`,
|
|
743
|
+
),
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
558
748
|
/**
|
|
559
749
|
* Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
|
|
560
750
|
*
|
|
@@ -568,7 +758,7 @@ export function nextVal(sequenceName: string): number {
|
|
|
568
758
|
*
|
|
569
759
|
* @param forgeSQLORM - The ForgeSQL operation instance for database access
|
|
570
760
|
* @param timeDiffMs - Time window in milliseconds to look back for queries (e.g., 1500 for last 1.5 seconds)
|
|
571
|
-
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to
|
|
761
|
+
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to 3000ms)
|
|
572
762
|
*
|
|
573
763
|
* @example
|
|
574
764
|
* ```typescript
|
|
@@ -587,42 +777,9 @@ export async function printQueriesWithPlan(
|
|
|
587
777
|
timeout?: number,
|
|
588
778
|
) {
|
|
589
779
|
try {
|
|
590
|
-
const statementsTable = clusterStatementsSummary;
|
|
591
780
|
const timeoutMs = timeout ?? 3000;
|
|
592
781
|
const results = await withTimeout(
|
|
593
|
-
forgeSQLORM
|
|
594
|
-
.getDrizzleQueryBuilder()
|
|
595
|
-
.select({
|
|
596
|
-
digestText: withTidbHint(statementsTable.digestText),
|
|
597
|
-
avgLatency: statementsTable.avgLatency,
|
|
598
|
-
avgMem: statementsTable.avgMem,
|
|
599
|
-
execCount: statementsTable.execCount,
|
|
600
|
-
plan: statementsTable.plan,
|
|
601
|
-
stmtType: statementsTable.stmtType,
|
|
602
|
-
})
|
|
603
|
-
.from(statementsTable)
|
|
604
|
-
.where(
|
|
605
|
-
and(
|
|
606
|
-
isNotNull(statementsTable.digest),
|
|
607
|
-
not(ilike(statementsTable.digestText, "%information_schema%")),
|
|
608
|
-
notInArray(statementsTable.stmtType, [
|
|
609
|
-
"Use",
|
|
610
|
-
"Set",
|
|
611
|
-
"Show",
|
|
612
|
-
"Commit",
|
|
613
|
-
"Rollback",
|
|
614
|
-
"Begin",
|
|
615
|
-
]),
|
|
616
|
-
gte(
|
|
617
|
-
statementsTable.lastSeen,
|
|
618
|
-
sql`DATE_SUB
|
|
619
|
-
(NOW(), INTERVAL
|
|
620
|
-
${timeDiffMs * 1000}
|
|
621
|
-
MICROSECOND
|
|
622
|
-
)`,
|
|
623
|
-
),
|
|
624
|
-
),
|
|
625
|
-
),
|
|
782
|
+
buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs),
|
|
626
783
|
`Timeout ${timeoutMs}ms in printQueriesWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
|
|
627
784
|
timeoutMs + 200,
|
|
628
785
|
);
|
|
@@ -647,6 +804,44 @@ export async function printQueriesWithPlan(
|
|
|
647
804
|
}
|
|
648
805
|
}
|
|
649
806
|
|
|
807
|
+
export async function handleErrorsWithPlan(
|
|
808
|
+
forgeSQLORM: ForgeSqlOperation,
|
|
809
|
+
timeDiffMs: number,
|
|
810
|
+
type: "OOM" | "TIMEOUT",
|
|
811
|
+
) {
|
|
812
|
+
try {
|
|
813
|
+
const statementsTable = clusterStatementsSummary;
|
|
814
|
+
const timeoutMs = 3000;
|
|
815
|
+
const baseQuery = buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs);
|
|
816
|
+
const orderColumn = type === "OOM" ? statementsTable.avgMem : statementsTable.avgLatency;
|
|
817
|
+
const query = baseQuery.orderBy(desc(orderColumn)).limit(formatLimitOffset(1));
|
|
818
|
+
|
|
819
|
+
const results = await withTimeout(
|
|
820
|
+
query,
|
|
821
|
+
`Timeout ${timeoutMs}ms in handleErrorsWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`,
|
|
822
|
+
timeoutMs + 200,
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
for (const result of results) {
|
|
826
|
+
// Average execution time (convert from nanoseconds to milliseconds)
|
|
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
|
+
);
|
|
835
|
+
}
|
|
836
|
+
} catch (error) {
|
|
837
|
+
// eslint-disable-next-line no-console
|
|
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
|
+
);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
650
845
|
const SESSION_ALIAS_NAME_ORM = "orm";
|
|
651
846
|
|
|
652
847
|
/**
|
|
@@ -772,5 +967,5 @@ export function withTidbHint<
|
|
|
772
967
|
>(column: AnyMySqlColumn<TPartial>): AnyMySqlColumn<TPartial> {
|
|
773
968
|
// We lie a bit to TypeScript here: at runtime this is a new SQL fragment,
|
|
774
969
|
// but returning TExpr keeps the column type info in downstream inference.
|
|
775
|
-
return sql`/*+ SET_VAR(tidb_session_alias=${sql.raw(
|
|
970
|
+
return sql`/*+ SET_VAR(tidb_session_alias=${sql.raw(SESSION_ALIAS_NAME_ORM)}) */ ${column}` as unknown as AnyMySqlColumn<TPartial>;
|
|
776
971
|
}
|
|
@@ -50,7 +50,7 @@ export const applySchemaMigrations = async (
|
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
migrationHistory = sortedMigrations
|
|
53
|
-
.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.
|
|
53
|
+
.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toISOString()}`)
|
|
54
54
|
.join("\n");
|
|
55
55
|
}
|
|
56
56
|
// eslint-disable-next-line no-console
|