forge-sql-orm 2.1.11 → 2.1.13
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 +800 -541
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.js +257 -0
- package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -0
- package/dist/core/ForgeSQLCacheOperations.js +172 -0
- package/dist/core/ForgeSQLCacheOperations.js.map +1 -0
- package/dist/core/ForgeSQLCrudOperations.js +349 -0
- package/dist/core/ForgeSQLCrudOperations.js.map +1 -0
- package/dist/core/ForgeSQLORM.d.ts +1 -1
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.js +1191 -0
- package/dist/core/ForgeSQLORM.js.map +1 -0
- package/dist/core/ForgeSQLQueryBuilder.js +77 -0
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -0
- package/dist/core/ForgeSQLSelectOperations.js +81 -0
- package/dist/core/ForgeSQLSelectOperations.js.map +1 -0
- package/dist/core/SystemTables.js +258 -0
- package/dist/core/SystemTables.js.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +527 -0
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -0
- package/dist/utils/cacheContextUtils.d.ts.map +1 -1
- package/dist/utils/cacheContextUtils.js +198 -0
- package/dist/utils/cacheContextUtils.js.map +1 -0
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.js +383 -0
- package/dist/utils/cacheUtils.js.map +1 -0
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriver.js +139 -0
- package/dist/utils/forgeDriver.js.map +1 -0
- package/dist/utils/forgeDriverProxy.js +68 -0
- package/dist/utils/forgeDriverProxy.js.map +1 -0
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +28 -0
- package/dist/utils/metadataContextUtils.js.map +1 -0
- package/dist/utils/requestTypeContextUtils.js +10 -0
- package/dist/utils/requestTypeContextUtils.js.map +1 -0
- package/dist/utils/sqlHints.js +52 -0
- package/dist/utils/sqlHints.js.map +1 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +590 -0
- package/dist/utils/sqlUtils.js.map +1 -0
- package/dist/webtriggers/applyMigrationsWebTrigger.js +77 -0
- package/dist/webtriggers/applyMigrationsWebTrigger.js.map +1 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.js +83 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.js.map +1 -0
- package/dist/webtriggers/dropMigrationWebTrigger.js +54 -0
- package/dist/webtriggers/dropMigrationWebTrigger.js.map +1 -0
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js +54 -0
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js.map +1 -0
- package/dist/webtriggers/fetchSchemaWebTrigger.js +82 -0
- package/dist/webtriggers/fetchSchemaWebTrigger.js.map +1 -0
- package/dist/webtriggers/index.js +40 -0
- package/dist/webtriggers/index.js.map +1 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -1
- package/dist/webtriggers/slowQuerySchedulerTrigger.js +80 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.js.map +1 -0
- package/package.json +28 -23
- package/src/core/ForgeSQLAnalyseOperations.ts +3 -2
- package/src/core/ForgeSQLORM.ts +33 -27
- package/src/lib/drizzle/extensions/additionalActions.ts +11 -0
- package/src/utils/cacheContextUtils.ts +9 -6
- package/src/utils/cacheUtils.ts +28 -5
- package/src/utils/forgeDriver.ts +10 -6
- package/src/utils/metadataContextUtils.ts +1 -4
- package/src/utils/sqlUtils.ts +136 -125
- package/src/webtriggers/slowQuerySchedulerTrigger.ts +40 -33
- package/dist/ForgeSQLORM.js +0 -3896
- package/dist/ForgeSQLORM.js.map +0 -1
- package/dist/ForgeSQLORM.mjs +0 -3879
- package/dist/ForgeSQLORM.mjs.map +0 -1
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -204,31 +204,31 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
204
204
|
},
|
|
205
205
|
},
|
|
206
206
|
async () => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
207
|
+
const result = await query();
|
|
208
|
+
const metadata = await getLastestMetadata();
|
|
209
|
+
try {
|
|
210
|
+
if (metadata) {
|
|
211
|
+
await onMetadata(
|
|
212
|
+
metadata.totalDbExecutionTime,
|
|
213
|
+
metadata.totalResponseSize,
|
|
214
|
+
metadata.printQueriesWithPlan,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
} catch (e: any) {
|
|
218
|
+
// eslint-disable-next-line no-console
|
|
219
|
+
console.error(
|
|
220
|
+
"[ForgeSQLORM][executeWithMetadata] Failed to run onMetadata callback",
|
|
221
|
+
{
|
|
222
|
+
errorMessage: e?.message,
|
|
223
|
+
errorStack: e?.stack,
|
|
224
|
+
totalDbExecutionTime: metadata?.totalDbExecutionTime,
|
|
225
|
+
totalResponseSize: metadata?.totalResponseSize,
|
|
226
|
+
beginTime: metadata?.beginTime,
|
|
227
|
+
},
|
|
228
|
+
e,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
232
|
},
|
|
233
233
|
);
|
|
234
234
|
}
|
|
@@ -780,7 +780,10 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
780
780
|
* const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
|
|
781
781
|
* ```
|
|
782
782
|
*/
|
|
783
|
-
executeCacheable<T>(
|
|
783
|
+
executeCacheable<T>(
|
|
784
|
+
query: SQLWrapper | string,
|
|
785
|
+
cacheTtl?: number,
|
|
786
|
+
): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
|
|
784
787
|
return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
|
|
785
788
|
}
|
|
786
789
|
|
|
@@ -1340,7 +1343,10 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
1340
1343
|
* const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
|
|
1341
1344
|
* ```
|
|
1342
1345
|
*/
|
|
1343
|
-
executeCacheable(
|
|
1346
|
+
executeCacheable<T>(
|
|
1347
|
+
query: SQLWrapper | string,
|
|
1348
|
+
cacheTtl?: number,
|
|
1349
|
+
): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
|
|
1344
1350
|
return this.ormInstance.executeCacheable(query, cacheTtl);
|
|
1345
1351
|
}
|
|
1346
1352
|
|
|
@@ -593,6 +593,17 @@ function createAliasedSelectBuilder<TSelection extends SelectedFields>(
|
|
|
593
593
|
}
|
|
594
594
|
};
|
|
595
595
|
}
|
|
596
|
+
if (prop === "catch") {
|
|
597
|
+
return (onrejected: any) => (receiver as any).then(undefined, onrejected);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (prop === "finally") {
|
|
601
|
+
return (onfinally: any) =>
|
|
602
|
+
(receiver as any).then(
|
|
603
|
+
(value: any) => Promise.resolve(value).finally(onfinally),
|
|
604
|
+
(reason: any) => Promise.reject(reason).finally(onfinally),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
596
607
|
|
|
597
608
|
const value = Reflect.get(target, prop, receiver);
|
|
598
609
|
|
|
@@ -159,10 +159,11 @@ export async function getQueryLocalCacheQuery<
|
|
|
159
159
|
} else {
|
|
160
160
|
sql = query as { toSQL: () => Query };
|
|
161
161
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
162
|
+
const toSQL = sql.toSQL();
|
|
163
|
+
const key = hashKey(toSQL);
|
|
164
|
+
if (context.cache[key] && context.cache[key].sql === toSQL.sql.toLowerCase()) {
|
|
164
165
|
if (options.logCache) {
|
|
165
|
-
const q =
|
|
166
|
+
const q = toSQL;
|
|
166
167
|
// eslint-disable-next-line no-console
|
|
167
168
|
console.debug(
|
|
168
169
|
`[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
|
|
@@ -201,12 +202,14 @@ export async function evictLocalCacheQuery<T extends AnyMySqlTable>(
|
|
|
201
202
|
const tableName = getTableName(table);
|
|
202
203
|
const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
|
|
203
204
|
const keyToEvicts: string[] = [];
|
|
204
|
-
Object.keys(context.cache)
|
|
205
|
+
for (const key of Object.keys(context.cache)) {
|
|
205
206
|
if (context.cache[key].sql.includes(searchString)) {
|
|
206
207
|
keyToEvicts.push(key);
|
|
207
208
|
}
|
|
208
|
-
}
|
|
209
|
-
|
|
209
|
+
}
|
|
210
|
+
for (const key of keyToEvicts) {
|
|
211
|
+
delete context.cache[key];
|
|
212
|
+
}
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
package/src/utils/cacheUtils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DateTime } from "luxon";
|
|
2
|
-
import * as crypto from "crypto";
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
3
|
import { Query } from "drizzle-orm";
|
|
4
4
|
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
5
5
|
import { getTableName } from "drizzle-orm/table";
|
|
@@ -47,6 +47,29 @@ function nowPlusSeconds(secondsToAdd: number): number {
|
|
|
47
47
|
return Math.floor(dt.toSeconds());
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Extracts all table/column names between backticks from SQL query and returns them as comma-separated string.
|
|
52
|
+
*
|
|
53
|
+
* @param sql - SQL query string
|
|
54
|
+
* @returns Comma-separated string of unique backticked values
|
|
55
|
+
*/
|
|
56
|
+
function extractBacktickedValues(sql: string): string {
|
|
57
|
+
const regex = /`([^`]+)`/g;
|
|
58
|
+
const matches = new Set<string>();
|
|
59
|
+
let match;
|
|
60
|
+
|
|
61
|
+
while ((match = regex.exec(sql.toLowerCase())) !== null) {
|
|
62
|
+
if (!match[1].startsWith("a_")) {
|
|
63
|
+
matches.add(`\`${match[1]}\``);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Sort to ensure consistent order for the same input
|
|
68
|
+
return Array.from(matches)
|
|
69
|
+
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base", numeric: true }))
|
|
70
|
+
.join(",");
|
|
71
|
+
}
|
|
72
|
+
|
|
50
73
|
/**
|
|
51
74
|
* Generates a hash key for a query based on its SQL and parameters.
|
|
52
75
|
*
|
|
@@ -74,9 +97,9 @@ async function deleteCacheEntriesInBatches(
|
|
|
74
97
|
for (let i = 0; i < results.length; i += CACHE_CONSTANTS.BATCH_SIZE) {
|
|
75
98
|
const batch = results.slice(i, i + CACHE_CONSTANTS.BATCH_SIZE);
|
|
76
99
|
let transactionBuilder = kvs.transact();
|
|
77
|
-
|
|
100
|
+
for (const result of batch) {
|
|
78
101
|
transactionBuilder = transactionBuilder.delete(result.key, { entityName: cacheEntityName });
|
|
79
|
-
}
|
|
102
|
+
}
|
|
80
103
|
await transactionBuilder.execute();
|
|
81
104
|
}
|
|
82
105
|
}
|
|
@@ -339,7 +362,7 @@ export async function getFromCache<T>(
|
|
|
339
362
|
if (
|
|
340
363
|
cacheResult &&
|
|
341
364
|
(cacheResult[expirationName] as number) >= getCurrentTime() &&
|
|
342
|
-
sqlQuery.sql
|
|
365
|
+
extractBacktickedValues(sqlQuery.sql) === cacheResult[entityQueryName]
|
|
343
366
|
) {
|
|
344
367
|
if (options.logCache) {
|
|
345
368
|
// eslint-disable-next-line no-console
|
|
@@ -400,7 +423,7 @@ export async function setCacheResult(
|
|
|
400
423
|
.set(
|
|
401
424
|
key,
|
|
402
425
|
{
|
|
403
|
-
[entityQueryName]: sqlQuery.sql
|
|
426
|
+
[entityQueryName]: extractBacktickedValues(sqlQuery.sql),
|
|
404
427
|
[expirationName]: nowPlusSeconds(cacheTtl),
|
|
405
428
|
[dataName]: JSON.stringify(results),
|
|
406
429
|
},
|
package/src/utils/forgeDriver.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { sql, UpdateQueryResponse } from "@forge/sql";
|
|
2
2
|
import { saveMetaDataToContext } from "./metadataContextUtils";
|
|
3
3
|
import { getOperationType } from "./requestTypeContextUtils";
|
|
4
|
-
import {withTimeout} from "./sqlUtils";
|
|
4
|
+
import { withTimeout } from "./sqlUtils";
|
|
5
5
|
|
|
6
|
-
const timeoutMs =10000;
|
|
6
|
+
const timeoutMs = 10000;
|
|
7
7
|
const timeoutMessage = `Atlassian @forge/sql did not return a response within ${timeoutMs}ms (${timeoutMs / 1000} seconds), so the request is blocked. Possible causes: slow query, network issues, or exceeding Forge SQL limits.`;
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -92,7 +92,7 @@ async function processDDLResult(method: QueryMethod, result: any): Promise<Forge
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
if (isUpdateQueryResponse(result.rows)) {
|
|
95
|
-
const oneRow = result.rows
|
|
95
|
+
const oneRow = result.rows;
|
|
96
96
|
return { ...oneRow, rows: [oneRow] };
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -145,8 +145,8 @@ async function processAllMethod(query: string, params: unknown[]): Promise<Forge
|
|
|
145
145
|
await sqlStatement.bindParams(...params);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const result =
|
|
149
|
-
await saveMetaDataToContext(result.metadata);
|
|
148
|
+
const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
|
|
149
|
+
await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
|
|
150
150
|
|
|
151
151
|
if (!result.rows) {
|
|
152
152
|
return { rows: [] };
|
|
@@ -188,7 +188,11 @@ export const forgeDriver = async (
|
|
|
188
188
|
const operationType = await getOperationType();
|
|
189
189
|
// Handle DDL operations
|
|
190
190
|
if (operationType === "DDL") {
|
|
191
|
-
const result = await withTimeout(
|
|
191
|
+
const result = await withTimeout(
|
|
192
|
+
sql.executeDDL(inlineParams(query, params)),
|
|
193
|
+
timeoutMessage,
|
|
194
|
+
timeoutMs,
|
|
195
|
+
);
|
|
192
196
|
return await processDDLResult(method, result);
|
|
193
197
|
}
|
|
194
198
|
|
|
@@ -19,10 +19,7 @@ export async function saveMetaDataToContext(metadata?: ForgeSQLMetadata): Promis
|
|
|
19
19
|
if (process.env.NODE_ENV !== "test") {
|
|
20
20
|
await new Promise((r) => setTimeout(r, 200));
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
context.forgeSQLORM,
|
|
24
|
-
Date.now() - context.beginTime.getTime(),
|
|
25
|
-
);
|
|
22
|
+
await printQueriesWithPlan(context.forgeSQLORM, Date.now() - context.beginTime.getTime());
|
|
26
23
|
};
|
|
27
24
|
if (metadata) {
|
|
28
25
|
context.totalResponseSize += metadata.responseSize;
|