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 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 versioning
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 @types/mysql2 drizzle-kit -D
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-core";
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-core";
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("*").from(Users)
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("*").from(Users)
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("*").from(Users)
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: rawSql`${usersAlias.id} as \`userId\``,
326
- userName: rawSql`${usersAlias.name} as \`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: rawSql`${usersAlias.id} as \`userId\``,
334
- userName: rawSql`${usersAlias.name} as \`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
- // Using joins
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
- .getDrizzleQueryBuilder()
342
- .select({
343
- orderId: rawSql`${Orders.id} as \`orderId\``,
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
- orderId: rawSql`${Orders.id} as \`orderId\``,
355
- product: Orders.product,
356
- userName: rawSql`${Users.name} as \`userName\``
357
- }).from(Orders)
358
- .innerJoin(Users, eq(Orders.userId, Users.id))
359
- .where(eq(Orders.id, 1));
360
- // Returns: { orderId: 1, product: "Product 1", userName: "John Doe" }
361
- ```
362
-
363
- ### Complex Queries with Aggregations
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: rawSql`COUNT(*) as \`count\``
413
+ count: sql<number>`COUNT(*) as \`count\``
373
414
  }).from(Users)
374
415
  .groupBy(Users.name)
375
- .having(rawSql`COUNT(*) > 1`);
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: rawSql`COUNT(*) as \`count\``
423
+ count: sql<number>`COUNT(*) as \`count\``
383
424
  }).from(Users)
384
425
  .groupBy(Users.name)
385
- .having(rawSql`COUNT(*) > 1`);
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: rawSql`COUNT(*) as \`totalUsers\``,
396
- uniqueNames: rawSql`COUNT(DISTINCT name) as \`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
@@ -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 sql = require("@forge/sql");
6
- const mysql2 = require("drizzle-orm/mysql2");
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(table) {
21
- const { columns, primaryKeys } = getTableMetadata(table);
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(table, foreignKeysSymbol, extraSymbol) {
41
+ function processForeignKeys(table2, foreignKeysSymbol, extraSymbol) {
40
42
  const foreignKeys = [];
41
43
  if (foreignKeysSymbol) {
42
- const fkArray = table[foreignKeysSymbol];
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 = table[extraSymbol];
55
+ const extraConfigBuilder = table2[extraSymbol];
54
56
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
55
- const configBuilderData = extraConfigBuilder(table);
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(table) {
73
- const symbols = Object.getOwnPropertySymbols(table);
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(table, foreignKeysSymbol, extraSymbol);
88
+ builders.foreignKeys = processForeignKeys(table2, foreignKeysSymbol, extraSymbol);
87
89
  if (extraSymbol) {
88
- const extraConfigBuilder = table[extraSymbol];
90
+ const extraConfigBuilder = table2[extraSymbol];
89
91
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
90
- const configBuilderData = extraConfigBuilder(table);
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 ? table[nameSymbol] : "",
117
- columns: columnsSymbol ? table[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((table) => {
124
- const tableMetadata = getTableMetadata(table);
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("Executing raw SQL: " + query);
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
- query: async (query, params) => {
482
- try {
483
- const sqlStatement = await sql.sql.prepare(query.sql);
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
- if (Array.isArray(result.rows)) {
490
- rows = [
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 = mysql2.drizzle(forgeDriver);
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
- const droppedTables = tables.map((table) => {
656
- const metadata = getTableMetadata(table);
657
- return metadata.tableName;
658
- }).filter(Boolean);
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