forge-sql-orm 2.0.7 → 2.0.9
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 +80 -40
- package/dist/ForgeSQLORM.js +181 -50
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +165 -34
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +40 -3
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +6 -10
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +5 -2
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +5 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts +0 -5
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/index.d.ts +2 -2
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/ForgeSQLCrudOperations.ts +2 -2
- package/src/core/ForgeSQLORM.ts +103 -4
- package/src/core/ForgeSQLQueryBuilder.ts +20 -10
- package/src/core/ForgeSQLSelectOperations.ts +10 -4
- package/src/utils/forgeDriver.ts +34 -29
- package/src/utils/sqlUtils.ts +55 -5
- package/src/webtriggers/applyMigrationsWebTrigger.ts +7 -6
- package/src/webtriggers/dropMigrationWebTrigger.ts +8 -20
- package/src/webtriggers/index.ts +19 -20
package/README.md
CHANGED
|
@@ -23,14 +23,53 @@ import { drizzle } from "drizzle-orm/mysql-core";
|
|
|
23
23
|
import { forgeDriver } from "forge-sql-orm";
|
|
24
24
|
const db = drizzle(forgeDriver);
|
|
25
25
|
```
|
|
26
|
-
Best for: Simple CRUD operations without optimistic locking
|
|
26
|
+
Best for: Simple CRUD operations without optimistic locking. Note that you need to manually set `mapSelectFieldsWithAlias` for select fields to prevent field name collisions in Atlassian Forge SQL.
|
|
27
27
|
|
|
28
28
|
### 2. Full Forge-SQL-ORM Usage
|
|
29
29
|
```typescript
|
|
30
30
|
import ForgeSQL from "forge-sql-orm";
|
|
31
31
|
const forgeSQL = new ForgeSQL();
|
|
32
32
|
```
|
|
33
|
-
Best for: Advanced features like optimistic locking and automatic
|
|
33
|
+
Best for: Advanced features like optimistic locking, automatic versioning, and automatic field name collision prevention in complex queries.
|
|
34
|
+
|
|
35
|
+
## Field Name Collision Prevention in Complex Queries
|
|
36
|
+
|
|
37
|
+
When working with complex queries involving multiple tables (joins, inner joins, etc.), Atlassian Forge SQL has a specific behavior where fields with the same name from different tables get collapsed into a single field with a null value. This is not a Drizzle ORM issue but rather a characteristic of Atlassian Forge SQL's behavior.
|
|
38
|
+
|
|
39
|
+
Forge-SQL-ORM provides two ways to handle this:
|
|
40
|
+
|
|
41
|
+
### Using Forge-SQL-ORM
|
|
42
|
+
```typescript
|
|
43
|
+
import ForgeSQL from "forge-sql-orm";
|
|
44
|
+
|
|
45
|
+
const forgeSQL = new ForgeSQL();
|
|
46
|
+
|
|
47
|
+
// Automatic field name collision prevention
|
|
48
|
+
await forgeSQL
|
|
49
|
+
.select({user: users, order: orders})
|
|
50
|
+
.from(orders)
|
|
51
|
+
.innerJoin(users, eq(orders.userId, users.id));
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Using Direct Drizzle
|
|
55
|
+
```typescript
|
|
56
|
+
import { drizzle } from "drizzle-orm/mysql-core";
|
|
57
|
+
import { forgeDriver, mapSelectFieldsWithAlias } from "forge-sql-orm";
|
|
58
|
+
|
|
59
|
+
const db = drizzle(forgeDriver);
|
|
60
|
+
|
|
61
|
+
// Manual field name collision prevention
|
|
62
|
+
await db
|
|
63
|
+
.select(mapSelectFieldsWithAlias({user: users, order: orders}))
|
|
64
|
+
.from(orders)
|
|
65
|
+
.innerJoin(users, eq(orders.userId, users.id));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Important Notes
|
|
69
|
+
- This is a specific behavior of Atlassian Forge SQL, not Drizzle ORM
|
|
70
|
+
- For complex queries involving multiple tables, it's recommended to always specify select fields and avoid using `select()` without field selection
|
|
71
|
+
- The solution automatically creates unique aliases for each field by prefixing them with the table name
|
|
72
|
+
- This ensures that fields with the same name from different tables remain distinct in the query results
|
|
34
73
|
|
|
35
74
|
## Installation
|
|
36
75
|
|
|
@@ -40,7 +79,7 @@ Forge-SQL-ORM is designed to work with @forge/sql and requires some additional s
|
|
|
40
79
|
|
|
41
80
|
```sh
|
|
42
81
|
npm install forge-sql-orm @forge/sql drizzle-orm momment -S
|
|
43
|
-
npm install mysql2
|
|
82
|
+
npm install mysql2 drizzle-kit inquirer@8.0.0 -D
|
|
44
83
|
```
|
|
45
84
|
|
|
46
85
|
This will:
|
|
@@ -54,7 +93,7 @@ This will:
|
|
|
54
93
|
If you prefer to use Drizzle ORM directly without the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
|
|
55
94
|
|
|
56
95
|
```typescript
|
|
57
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
96
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
58
97
|
import { forgeDriver } from "forge-sql-orm";
|
|
59
98
|
|
|
60
99
|
// Initialize drizzle with the custom driver
|
|
@@ -275,7 +314,7 @@ const forgeSQL = new ForgeSQL();
|
|
|
275
314
|
or
|
|
276
315
|
|
|
277
316
|
```typescript
|
|
278
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
317
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
279
318
|
import { forgeDriver } from "forge-sql-orm";
|
|
280
319
|
|
|
281
320
|
// Initialize drizzle with the custom driver
|
|
@@ -293,13 +332,13 @@ const users = await db.select().from(users);
|
|
|
293
332
|
// Using forgeSQL.getDrizzleQueryBuilder()
|
|
294
333
|
const user = await forgeSQL
|
|
295
334
|
.getDrizzleQueryBuilder()
|
|
296
|
-
.select(
|
|
335
|
+
.select().from(Users)
|
|
297
336
|
.where(eq(Users.id, 1));
|
|
298
337
|
|
|
299
338
|
// OR using direct drizzle with custom driver
|
|
300
339
|
const db = drizzle(forgeDriver);
|
|
301
340
|
const user = await db
|
|
302
|
-
.select(
|
|
341
|
+
.select().from(Users)
|
|
303
342
|
.where(eq(Users.id, 1));
|
|
304
343
|
// Returns: { id: 1, name: "John Doe" }
|
|
305
344
|
|
|
@@ -309,7 +348,7 @@ const user = await forgeSQL
|
|
|
309
348
|
.executeQueryOnlyOne(
|
|
310
349
|
forgeSQL
|
|
311
350
|
.getDrizzleQueryBuilder()
|
|
312
|
-
.select(
|
|
351
|
+
.select().from(Users)
|
|
313
352
|
.where(eq(Users.id, 1))
|
|
314
353
|
);
|
|
315
354
|
// Returns: { id: 1, name: "John Doe" }
|
|
@@ -322,67 +361,69 @@ const usersAlias = alias(Users, "u");
|
|
|
322
361
|
const result = await forgeSQL
|
|
323
362
|
.getDrizzleQueryBuilder()
|
|
324
363
|
.select({
|
|
325
|
-
userId:
|
|
326
|
-
userName:
|
|
364
|
+
userId: sql<string>`${usersAlias.id} as \`userId\``,
|
|
365
|
+
userName: sql<string>`${usersAlias.name} as \`userName\``
|
|
327
366
|
}).from(usersAlias);
|
|
328
367
|
|
|
329
368
|
// OR with direct drizzle
|
|
330
369
|
const db = drizzle(forgeDriver);
|
|
331
370
|
const result = await db
|
|
332
371
|
.select({
|
|
333
|
-
userId:
|
|
334
|
-
userName:
|
|
372
|
+
userId: sql<string>`${usersAlias.id} as \`userId\``,
|
|
373
|
+
userName: sql<string>`${usersAlias.name} as \`userName\``
|
|
335
374
|
}).from(usersAlias);
|
|
336
375
|
// Returns: { userId: 1, userName: "John Doe" }
|
|
376
|
+
```
|
|
337
377
|
|
|
338
|
-
|
|
378
|
+
### Complex Queries
|
|
379
|
+
```js
|
|
380
|
+
|
|
381
|
+
// Using joins with automatic field name collision prevention
|
|
339
382
|
// With forgeSQL
|
|
340
383
|
const orderWithUser = await forgeSQL
|
|
341
|
-
.
|
|
342
|
-
.
|
|
343
|
-
|
|
344
|
-
product: Orders.product,
|
|
345
|
-
userName: rawSql`${Users.name} as \`userName\``
|
|
346
|
-
}).from(Orders)
|
|
347
|
-
.innerJoin(Users, eq(Orders.userId, Users.id))
|
|
348
|
-
.where(eq(Orders.id, 1));
|
|
384
|
+
.select({user: users, order: orders})
|
|
385
|
+
.from(orders)
|
|
386
|
+
.innerJoin(users, eq(orders.userId, users.id));
|
|
349
387
|
|
|
350
388
|
// OR with direct drizzle
|
|
351
389
|
const db = drizzle(forgeDriver);
|
|
352
390
|
const orderWithUser = await db
|
|
353
|
-
.select({
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
391
|
+
.select(mapSelectFieldsWithAlias({user: users, order: orders}))
|
|
392
|
+
.from(orders)
|
|
393
|
+
.innerJoin(users, eq(orders.userId, users.id));
|
|
394
|
+
// Returns: {
|
|
395
|
+
// user_id: 1,
|
|
396
|
+
// user_name: "John Doe",
|
|
397
|
+
// order_id: 1,
|
|
398
|
+
// order_product: "Product 1"
|
|
399
|
+
// }
|
|
400
|
+
|
|
401
|
+
// Using distinct select with automatic field name collision prevention
|
|
402
|
+
const uniqueOrdersWithUsers = await forgeSQL
|
|
403
|
+
.selectDistinct({user: users, order: orders})
|
|
404
|
+
.from(orders)
|
|
405
|
+
.innerJoin(users, eq(orders.userId, users.id));
|
|
364
406
|
|
|
365
|
-
```js
|
|
366
407
|
// Finding duplicates
|
|
367
408
|
// With forgeSQL
|
|
368
409
|
const duplicates = await forgeSQL
|
|
369
410
|
.getDrizzleQueryBuilder()
|
|
370
411
|
.select({
|
|
371
412
|
name: Users.name,
|
|
372
|
-
count:
|
|
413
|
+
count: sql<number>`COUNT(*) as \`count\``
|
|
373
414
|
}).from(Users)
|
|
374
415
|
.groupBy(Users.name)
|
|
375
|
-
.having(
|
|
416
|
+
.having(sql`COUNT(*) > 1`);
|
|
376
417
|
|
|
377
418
|
// OR with direct drizzle
|
|
378
419
|
const db = drizzle(forgeDriver);
|
|
379
420
|
const duplicates = await db
|
|
380
421
|
.select({
|
|
381
422
|
name: Users.name,
|
|
382
|
-
count:
|
|
423
|
+
count: sql<number>`COUNT(*) as \`count\``
|
|
383
424
|
}).from(Users)
|
|
384
425
|
.groupBy(Users.name)
|
|
385
|
-
.having(
|
|
426
|
+
.having(sql`COUNT(*) > 1`);
|
|
386
427
|
// Returns: { name: "John Doe", count: 2 }
|
|
387
428
|
|
|
388
429
|
// Using executeQueryOnlyOne for unique results
|
|
@@ -392,8 +433,8 @@ const userStats = await forgeSQL
|
|
|
392
433
|
forgeSQL
|
|
393
434
|
.getDrizzleQueryBuilder()
|
|
394
435
|
.select({
|
|
395
|
-
totalUsers:
|
|
396
|
-
uniqueNames:
|
|
436
|
+
totalUsers: sql`COUNT(*) as \`totalUsers\``,
|
|
437
|
+
uniqueNames: sql`COUNT(DISTINCT name) as \`uniqueNames\``
|
|
397
438
|
}).from(Users)
|
|
398
439
|
);
|
|
399
440
|
// Returns: { totalUsers: 100, uniqueNames: 80 }
|
|
@@ -611,7 +652,6 @@ Configure in `manifest.yml`:
|
|
|
611
652
|
**Security Considerations**:
|
|
612
653
|
- The drop migrations trigger should be restricted to development environments
|
|
613
654
|
- Consider implementing additional authentication for these endpoints
|
|
614
|
-
- Use the `security` section in `manifest.yml` to control access
|
|
615
655
|
|
|
616
656
|
**Best Practices**:
|
|
617
657
|
- Always backup your data before using the drop migrations trigger
|
package/dist/ForgeSQLORM.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
const drizzleOrm = require("drizzle-orm");
|
|
4
4
|
const moment = require("moment");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const table = require("drizzle-orm/table");
|
|
6
|
+
const sql = require("drizzle-orm/sql/sql");
|
|
7
|
+
const sql$1 = require("@forge/sql");
|
|
8
|
+
const mysqlProxy = require("drizzle-orm/mysql-proxy");
|
|
7
9
|
const mysqlCore = require("drizzle-orm/mysql-core");
|
|
8
10
|
const moment$1 = require("moment/moment.js");
|
|
9
11
|
const parseDateTime = (value, format) => {
|
|
@@ -17,8 +19,8 @@ function extractAlias(query) {
|
|
|
17
19
|
const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
|
|
18
20
|
return match ? match[2] : query;
|
|
19
21
|
}
|
|
20
|
-
function getPrimaryKeys(
|
|
21
|
-
const { columns, primaryKeys } = getTableMetadata(
|
|
22
|
+
function getPrimaryKeys(table2) {
|
|
23
|
+
const { columns, primaryKeys } = getTableMetadata(table2);
|
|
22
24
|
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
|
|
23
25
|
if (columnPrimaryKeys.length > 0) {
|
|
24
26
|
return columnPrimaryKeys;
|
|
@@ -36,10 +38,10 @@ function getPrimaryKeys(table) {
|
|
|
36
38
|
}
|
|
37
39
|
return [];
|
|
38
40
|
}
|
|
39
|
-
function processForeignKeys(
|
|
41
|
+
function processForeignKeys(table2, foreignKeysSymbol, extraSymbol) {
|
|
40
42
|
const foreignKeys = [];
|
|
41
43
|
if (foreignKeysSymbol) {
|
|
42
|
-
const fkArray =
|
|
44
|
+
const fkArray = table2[foreignKeysSymbol];
|
|
43
45
|
if (fkArray) {
|
|
44
46
|
fkArray.forEach((fk) => {
|
|
45
47
|
if (fk.reference) {
|
|
@@ -50,9 +52,9 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
if (extraSymbol) {
|
|
53
|
-
const extraConfigBuilder =
|
|
55
|
+
const extraConfigBuilder = table2[extraSymbol];
|
|
54
56
|
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
55
|
-
const configBuilderData = extraConfigBuilder(
|
|
57
|
+
const configBuilderData = extraConfigBuilder(table2);
|
|
56
58
|
if (configBuilderData) {
|
|
57
59
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
58
60
|
(item) => item.value || item
|
|
@@ -69,8 +71,8 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
|
|
|
69
71
|
}
|
|
70
72
|
return foreignKeys;
|
|
71
73
|
}
|
|
72
|
-
function getTableMetadata(
|
|
73
|
-
const symbols = Object.getOwnPropertySymbols(
|
|
74
|
+
function getTableMetadata(table2) {
|
|
75
|
+
const symbols = Object.getOwnPropertySymbols(table2);
|
|
74
76
|
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
75
77
|
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
76
78
|
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
|
|
@@ -83,11 +85,11 @@ function getTableMetadata(table) {
|
|
|
83
85
|
uniqueConstraints: [],
|
|
84
86
|
extras: []
|
|
85
87
|
};
|
|
86
|
-
builders.foreignKeys = processForeignKeys(
|
|
88
|
+
builders.foreignKeys = processForeignKeys(table2, foreignKeysSymbol, extraSymbol);
|
|
87
89
|
if (extraSymbol) {
|
|
88
|
-
const extraConfigBuilder =
|
|
90
|
+
const extraConfigBuilder = table2[extraSymbol];
|
|
89
91
|
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
90
|
-
const configBuilderData = extraConfigBuilder(
|
|
92
|
+
const configBuilderData = extraConfigBuilder(table2);
|
|
91
93
|
if (configBuilderData) {
|
|
92
94
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
93
95
|
(item) => item.value || item
|
|
@@ -113,15 +115,15 @@ function getTableMetadata(table) {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
return {
|
|
116
|
-
tableName: nameSymbol ?
|
|
117
|
-
columns: columnsSymbol ?
|
|
118
|
+
tableName: nameSymbol ? table2[nameSymbol] : "",
|
|
119
|
+
columns: columnsSymbol ? table2[columnsSymbol] : {},
|
|
118
120
|
...builders
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
123
|
function generateDropTableStatements(tables) {
|
|
122
124
|
const dropStatements = [];
|
|
123
|
-
tables.forEach((
|
|
124
|
-
const tableMetadata = getTableMetadata(
|
|
125
|
+
tables.forEach((table2) => {
|
|
126
|
+
const tableMetadata = getTableMetadata(table2);
|
|
125
127
|
if (tableMetadata.tableName) {
|
|
126
128
|
dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
|
|
127
129
|
}
|
|
@@ -129,6 +131,47 @@ function generateDropTableStatements(tables) {
|
|
|
129
131
|
dropStatements.push(`DELETE FROM __migrations;`);
|
|
130
132
|
return dropStatements;
|
|
131
133
|
}
|
|
134
|
+
function mapSelectTableToAlias(table2) {
|
|
135
|
+
const { columns, tableName } = getTableMetadata(table2);
|
|
136
|
+
const selectionsTableFields = {};
|
|
137
|
+
Object.keys(columns).forEach((name) => {
|
|
138
|
+
const column = columns[name];
|
|
139
|
+
const fieldAlias = drizzleOrm.sql.raw(`${tableName}_${column.name}`);
|
|
140
|
+
selectionsTableFields[name] = drizzleOrm.sql`${column} as \`${fieldAlias}\``;
|
|
141
|
+
});
|
|
142
|
+
return selectionsTableFields;
|
|
143
|
+
}
|
|
144
|
+
function isDrizzleColumn(column) {
|
|
145
|
+
return column && typeof column === "object" && "table" in column;
|
|
146
|
+
}
|
|
147
|
+
function mapSelectAllFieldsToAlias(selections, name, fields) {
|
|
148
|
+
if (drizzleOrm.isTable(fields)) {
|
|
149
|
+
selections[name] = mapSelectTableToAlias(fields);
|
|
150
|
+
} else if (isDrizzleColumn(fields)) {
|
|
151
|
+
const column = fields;
|
|
152
|
+
let aliasName = drizzleOrm.sql.raw(`${table.getTableName(column.table)}_${column.name}`);
|
|
153
|
+
selections[name] = drizzleOrm.sql`${column} as \`${aliasName}\``;
|
|
154
|
+
} else if (sql.isSQLWrapper(fields)) {
|
|
155
|
+
selections[name] = fields;
|
|
156
|
+
} else {
|
|
157
|
+
const innerSelections = {};
|
|
158
|
+
Object.entries(fields).forEach(([iname, ifields]) => {
|
|
159
|
+
mapSelectAllFieldsToAlias(innerSelections, iname, ifields);
|
|
160
|
+
});
|
|
161
|
+
selections[name] = innerSelections;
|
|
162
|
+
}
|
|
163
|
+
return selections;
|
|
164
|
+
}
|
|
165
|
+
function mapSelectFieldsWithAlias(fields) {
|
|
166
|
+
if (!fields) {
|
|
167
|
+
throw new Error("fields is empty");
|
|
168
|
+
}
|
|
169
|
+
const selections = {};
|
|
170
|
+
Object.entries(fields).forEach(([name, fields2]) => {
|
|
171
|
+
mapSelectAllFieldsToAlias(selections, name, fields2);
|
|
172
|
+
});
|
|
173
|
+
return selections;
|
|
174
|
+
}
|
|
132
175
|
class ForgeSQLCrudOperations {
|
|
133
176
|
forgeOperations;
|
|
134
177
|
options;
|
|
@@ -450,13 +493,12 @@ class ForgeSQLSelectOperations {
|
|
|
450
493
|
*/
|
|
451
494
|
async executeRawSQL(query, params) {
|
|
452
495
|
if (this.options.logRawSqlQuery) {
|
|
453
|
-
console.debug(
|
|
496
|
+
console.debug(
|
|
497
|
+
`Executing with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
|
|
498
|
+
);
|
|
454
499
|
}
|
|
455
|
-
const sqlStatement = sql.sql.prepare(query);
|
|
500
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
456
501
|
if (params) {
|
|
457
|
-
if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
|
|
458
|
-
console.debug("Executing with SQL Params: " + JSON.stringify(params));
|
|
459
|
-
}
|
|
460
502
|
sqlStatement.bindParams(...params);
|
|
461
503
|
}
|
|
462
504
|
const result = await sqlStatement.execute();
|
|
@@ -469,37 +511,42 @@ class ForgeSQLSelectOperations {
|
|
|
469
511
|
* @returns {Promise<UpdateQueryResponse>} The update response containing affected rows
|
|
470
512
|
*/
|
|
471
513
|
async executeRawUpdateSQL(query, params) {
|
|
472
|
-
const sqlStatement = sql.sql.prepare(query);
|
|
514
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
473
515
|
if (params) {
|
|
474
516
|
sqlStatement.bindParams(...params);
|
|
475
517
|
}
|
|
518
|
+
if (this.options.logRawSqlQuery) {
|
|
519
|
+
console.debug(
|
|
520
|
+
`Executing Update with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
|
|
521
|
+
);
|
|
522
|
+
}
|
|
476
523
|
const updateQueryResponseResults = await sqlStatement.execute();
|
|
477
524
|
return updateQueryResponseResults.rows;
|
|
478
525
|
}
|
|
479
526
|
}
|
|
480
|
-
const forgeDriver = {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const sqlStatement =
|
|
527
|
+
const forgeDriver = async (query, params, method) => {
|
|
528
|
+
try {
|
|
529
|
+
if (method == "execute") {
|
|
530
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
531
|
+
if (params) {
|
|
532
|
+
sqlStatement.bindParams(...params);
|
|
533
|
+
}
|
|
534
|
+
const updateQueryResponseResults = await sqlStatement.execute();
|
|
535
|
+
let result = updateQueryResponseResults.rows;
|
|
536
|
+
return { ...result, rows: [result] };
|
|
537
|
+
} else {
|
|
538
|
+
const sqlStatement = await sql$1.sql.prepare(query);
|
|
484
539
|
if (params) {
|
|
485
540
|
await sqlStatement.bindParams(...params);
|
|
486
541
|
}
|
|
487
542
|
const result = await sqlStatement.execute();
|
|
488
543
|
let rows;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
result.rows.map((r) => Object.values(r))
|
|
492
|
-
];
|
|
493
|
-
} else {
|
|
494
|
-
rows = [
|
|
495
|
-
result.rows
|
|
496
|
-
];
|
|
497
|
-
}
|
|
498
|
-
return rows;
|
|
499
|
-
} catch (error) {
|
|
500
|
-
console.error("SQL Error:", JSON.stringify(error));
|
|
501
|
-
throw error;
|
|
544
|
+
rows = result.rows.map((r) => Object.values(r));
|
|
545
|
+
return { rows };
|
|
502
546
|
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error("SQL Error:", JSON.stringify(error));
|
|
549
|
+
throw error;
|
|
503
550
|
}
|
|
504
551
|
};
|
|
505
552
|
class ForgeSQLORMImpl {
|
|
@@ -520,7 +567,7 @@ class ForgeSQLORMImpl {
|
|
|
520
567
|
if (newOptions.logRawSqlQuery) {
|
|
521
568
|
console.debug("Initializing ForgeSQLORM...");
|
|
522
569
|
}
|
|
523
|
-
this.drizzle =
|
|
570
|
+
this.drizzle = mysqlProxy.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
|
|
524
571
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
525
572
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
526
573
|
} catch (error) {
|
|
@@ -565,12 +612,94 @@ class ForgeSQLORMImpl {
|
|
|
565
612
|
getDrizzleQueryBuilder() {
|
|
566
613
|
return this.drizzle;
|
|
567
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
617
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
618
|
+
*
|
|
619
|
+
* @template TSelection - The type of the selected fields
|
|
620
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
621
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
622
|
+
* @throws {Error} If fields parameter is empty
|
|
623
|
+
* @example
|
|
624
|
+
* ```typescript
|
|
625
|
+
* await forgeSQL
|
|
626
|
+
* .select({user: users, order: orders})
|
|
627
|
+
* .from(orders)
|
|
628
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
select(fields) {
|
|
632
|
+
if (!fields) {
|
|
633
|
+
throw new Error("fields is empty");
|
|
634
|
+
}
|
|
635
|
+
return this.drizzle.select(mapSelectFieldsWithAlias(fields));
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
639
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
640
|
+
*
|
|
641
|
+
* @template TSelection - The type of the selected fields
|
|
642
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
643
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
644
|
+
* @throws {Error} If fields parameter is empty
|
|
645
|
+
* @example
|
|
646
|
+
* ```typescript
|
|
647
|
+
* await forgeSQL
|
|
648
|
+
* .selectDistinct({user: users, order: orders})
|
|
649
|
+
* .from(orders)
|
|
650
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
selectDistinct(fields) {
|
|
654
|
+
if (!fields) {
|
|
655
|
+
throw new Error("fields is empty");
|
|
656
|
+
}
|
|
657
|
+
return this.drizzle.selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
658
|
+
}
|
|
568
659
|
}
|
|
569
660
|
class ForgeSQLORM {
|
|
570
661
|
ormInstance;
|
|
571
662
|
constructor(options) {
|
|
572
663
|
this.ormInstance = ForgeSQLORMImpl.getInstance(options);
|
|
573
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
667
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
668
|
+
*
|
|
669
|
+
* @template TSelection - The type of the selected fields
|
|
670
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
671
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
672
|
+
* @throws {Error} If fields parameter is empty
|
|
673
|
+
* @example
|
|
674
|
+
* ```typescript
|
|
675
|
+
* await forgeSQL
|
|
676
|
+
* .select({user: users, order: orders})
|
|
677
|
+
* .from(orders)
|
|
678
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
679
|
+
* ```
|
|
680
|
+
*/
|
|
681
|
+
select(fields) {
|
|
682
|
+
return this.ormInstance.getDrizzleQueryBuilder().select(mapSelectFieldsWithAlias(fields));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
686
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
687
|
+
*
|
|
688
|
+
* @template TSelection - The type of the selected fields
|
|
689
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
690
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
691
|
+
* @throws {Error} If fields parameter is empty
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* await forgeSQL
|
|
695
|
+
* .selectDistinct({user: users, order: orders})
|
|
696
|
+
* .from(orders)
|
|
697
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
selectDistinct(fields) {
|
|
701
|
+
return this.ormInstance.getDrizzleQueryBuilder().selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
702
|
+
}
|
|
574
703
|
/**
|
|
575
704
|
* Proxies the `crud` method from `ForgeSQLORMImpl`.
|
|
576
705
|
* @returns CRUD operations.
|
|
@@ -650,13 +779,12 @@ async function dropSchemaMigrations(tables) {
|
|
|
650
779
|
const dropStatements = generateDropTableStatements(tables);
|
|
651
780
|
for (const statement of dropStatements) {
|
|
652
781
|
console.warn(statement);
|
|
653
|
-
await sql.sql.executeDDL(statement);
|
|
782
|
+
await sql$1.sql.executeDDL(statement);
|
|
654
783
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
return getHttpResponse(200, "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone.");
|
|
784
|
+
return getHttpResponse(
|
|
785
|
+
200,
|
|
786
|
+
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
|
|
787
|
+
);
|
|
660
788
|
} catch (error) {
|
|
661
789
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
662
790
|
return getHttpResponse(500, errorMessage);
|
|
@@ -664,12 +792,12 @@ async function dropSchemaMigrations(tables) {
|
|
|
664
792
|
}
|
|
665
793
|
const applySchemaMigrations = async (migration) => {
|
|
666
794
|
console.log("Provisioning the database");
|
|
667
|
-
await sql.sql._provision();
|
|
795
|
+
await sql$1.sql._provision();
|
|
668
796
|
console.info("Running schema migrations");
|
|
669
|
-
const migrations = await migration(sql.migrationRunner);
|
|
797
|
+
const migrations = await migration(sql$1.migrationRunner);
|
|
670
798
|
const successfulMigrations = await migrations.run();
|
|
671
799
|
console.info("Migrations applied:", successfulMigrations);
|
|
672
|
-
const migrationHistory = (await sql.migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
|
|
800
|
+
const migrationHistory = (await sql$1.migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
|
|
673
801
|
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
674
802
|
return {
|
|
675
803
|
headers: { "Content-Type": ["application/json"] },
|
|
@@ -707,5 +835,8 @@ exports.generateDropTableStatements = generateDropTableStatements;
|
|
|
707
835
|
exports.getHttpResponse = getHttpResponse;
|
|
708
836
|
exports.getPrimaryKeys = getPrimaryKeys;
|
|
709
837
|
exports.getTableMetadata = getTableMetadata;
|
|
838
|
+
exports.mapSelectAllFieldsToAlias = mapSelectAllFieldsToAlias;
|
|
839
|
+
exports.mapSelectFieldsWithAlias = mapSelectFieldsWithAlias;
|
|
840
|
+
exports.mapSelectTableToAlias = mapSelectTableToAlias;
|
|
710
841
|
exports.parseDateTime = parseDateTime;
|
|
711
842
|
//# sourceMappingURL=ForgeSQLORM.js.map
|