• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vzakharchenko / forge-sql-orm / 18198127865

02 Oct 2025 03:43PM UTC coverage: 83.082% (-3.6%) from 86.669%
18198127865

Pull #738

github

web-flow
Merge dd3548235 into 62498cd35
Pull Request #738: added support ddl operations

520 of 633 branches covered (82.15%)

Branch coverage included in aggregate %.

206 of 433 new or added lines in 8 files covered. (47.58%)

3 existing lines in 2 files now uncovered.

2510 of 3014 relevant lines covered (83.28%)

13.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.66
/src/core/ForgeSQLORM.ts
1
import { ForgeSQLCrudOperations } from "./ForgeSQLCrudOperations";
1✔
2
import {
3
  VerioningModificationForgeSQL,
4
  ForgeSqlOperation,
5
  ForgeSqlOrmOptions,
6
  SchemaAnalyzeForgeSql,
7
  SchemaSqlForgeSql,
8
} from "./ForgeSQLQueryBuilder";
9
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
1✔
10
import {
1✔
11
  drizzle,
12
  MySqlRemoteDatabase,
13
  MySqlRemotePreparedQueryHKT,
14
  MySqlRemoteQueryResultHKT,
15
} from "drizzle-orm/mysql-proxy";
16
import { createForgeDriverProxy } from "../utils/forgeDriverProxy";
1✔
17
import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
18
import { MySqlSelectBuilder } from "drizzle-orm/mysql-core";
19
import {
1✔
20
  DeleteAndEvictCacheType,
21
  InsertAndEvictCacheType,
22
  patchDbWithSelectAliased,
23
  SelectAliasedCacheableType,
24
  SelectAliasedDistinctCacheableType,
25
  SelectAliasedDistinctType,
26
  SelectAliasedType,
27
  UpdateAndEvictCacheType,
28
} from "../lib/drizzle/extensions/additionalActions";
29
import { ForgeSQLAnalyseOperation } from "./ForgeSQLAnalyseOperations";
1✔
30
import { ForgeSQLCacheOperations } from "./ForgeSQLCacheOperations";
1✔
31
import type { MySqlTable } from "drizzle-orm/mysql-core/table";
32
import {
33
  MySqlDeleteBase,
34
  MySqlInsertBuilder,
35
  MySqlUpdateBuilder,
36
} from "drizzle-orm/mysql-core/query-builders";
37
import { cacheApplicationContext, localCacheApplicationContext } from "../utils/cacheContextUtils";
1✔
38
import { clearTablesCache } from "../utils/cacheUtils";
1✔
39
import { SQLWrapper } from "drizzle-orm/sql/sql";
40
import { WithSubquery } from "drizzle-orm/subquery";
41
import { ForgeSQLMetadata } from "../utils/forgeDriver";
42
import { getLastestMetadata, metadataQueryContext } from "../utils/metadataContextUtils";
1✔
43
import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
1✔
44
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
45

46
/**
47
 * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
48
 * This class provides a bridge between Forge SQL and Drizzle ORM, allowing
49
 * to use Drizzle's query builder while executing queries through Forge SQL.
50
 */
51
class ForgeSQLORMImpl implements ForgeSqlOperation {
1✔
52
  private static instance: ForgeSQLORMImpl | null = null;
6✔
53
  private readonly drizzle: MySqlRemoteDatabase<any> & {
6✔
54
    selectAliased: SelectAliasedType;
55
    selectAliasedDistinct: SelectAliasedDistinctType;
56
    selectAliasedCacheable: SelectAliasedCacheableType;
57
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
58
    insertWithCacheContext: InsertAndEvictCacheType;
59
    insertAndEvictCache: InsertAndEvictCacheType;
60
    updateAndEvictCache: UpdateAndEvictCacheType;
61
    updateWithCacheContext: UpdateAndEvictCacheType;
62
    deleteAndEvictCache: DeleteAndEvictCacheType;
63
    deleteWithCacheContext: DeleteAndEvictCacheType;
64
  };
65
  private readonly crudOperations: VerioningModificationForgeSQL;
6✔
66
  private readonly fetchOperations: SchemaSqlForgeSql;
6✔
67
  private readonly analyzeOperations: SchemaAnalyzeForgeSql;
6✔
68
  private readonly cacheOperations: ForgeSQLCacheOperations;
6✔
69
  private readonly options: ForgeSqlOrmOptions;
6✔
70

71
  /**
72
   * Private constructor to enforce singleton behavior.
73
   * @param options - Options for configuring ForgeSQL ORM behavior.
74
   */
75
  private constructor(options?: ForgeSqlOrmOptions) {
6✔
76
    try {
3✔
77
      const newOptions: ForgeSqlOrmOptions = options ?? {
3!
78
        logRawSqlQuery: false,
×
79
        logCache: false,
×
80
        disableOptimisticLocking: false,
×
81
        cacheWrapTable: true,
×
82
        cacheTTL: 120,
×
83
        cacheEntityQueryName: "sql",
×
84
        cacheEntityExpirationName: "expiration",
×
85
        cacheEntityDataName: "data",
×
86
      };
×
87
      this.options = newOptions;
3✔
88
      if (newOptions.logRawSqlQuery) {
3✔
89
        // eslint-disable-next-line no-console
90
        console.debug("Initializing ForgeSQLORM...");
2✔
91
      }
2✔
92
      // Initialize Drizzle instance with our custom driver
93
      const proxiedDriver = createForgeDriverProxy(newOptions.hints, newOptions.logRawSqlQuery);
3✔
94
      this.drizzle = patchDbWithSelectAliased(
3✔
95
        drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
3✔
96
        newOptions,
3✔
97
      );
3✔
98
      this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
3✔
99
      this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
3✔
100
      this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
3✔
101
      this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
3✔
102
    } catch (error) {
3!
103
      // eslint-disable-next-line no-console
104
      console.error("ForgeSQLORM initialization failed:", error);
×
105
      throw error;
×
106
    }
×
107
  }
3✔
108

109
  /**
110
   * Executes a query and provides access to execution metadata.
111
   * This method allows you to capture detailed information about query execution
112
   * including database execution time, response size, and Forge SQL metadata.
113
   *
114
   * @template T - The return type of the query
115
   * @param query - A function that returns a Promise with the query result
116
   * @param onMetadata - Callback function that receives execution metadata
117
   * @returns Promise with the query result
118
   * @example
119
   * ```typescript
120
   * const result = await forgeSQL.executeWithMetadata(
121
   *   async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
122
   *   (dbTime, responseSize, metadata) => {
123
   *     console.log(`DB execution time: ${dbTime}ms`);
124
   *     console.log(`Response size: ${responseSize} bytes`);
125
   *     console.log('Forge metadata:', metadata);
126
   *   }
127
   * );
128
   * ```
129
   */
130
  async executeWithMetadata<T>(
6✔
131
    query: () => Promise<T>,
1✔
132
    onMetadata: (
1✔
133
      totalDbExecutionTime: number,
134
      totalResponseSize: number,
135
      forgeMetadata: ForgeSQLMetadata,
136
    ) => Promise<void> | void,
137
  ): Promise<T> {
1✔
138
    return metadataQueryContext.run(
1✔
139
      {
1✔
140
        totalDbExecutionTime: 0,
1✔
141
        totalResponseSize: 0,
1✔
142
      },
1✔
143
      async () => {
1✔
144
        try {
1✔
145
          return await query();
1✔
146
        } finally {
1✔
147
          const metadata = await getLastestMetadata();
1✔
148
          if (metadata && metadata.lastMetadata) {
1✔
149
            await onMetadata(
1✔
150
              metadata.totalDbExecutionTime,
1✔
151
              metadata.totalResponseSize,
1✔
152
              metadata.lastMetadata,
1✔
153
            );
1✔
154
          }
1✔
155
        }
1✔
156
      },
1✔
157
    );
1✔
158
  }
1✔
159

160
  /**
161
   * Executes operations within a cache context that collects cache eviction events.
162
   * All clearCache calls within the context are collected and executed in batch at the end.
163
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
164
   *
165
   * This is useful for:
166
   * - Batch operations that affect multiple tables
167
   * - Transaction-like operations where you want to clear cache only at the end
168
   * - Performance optimization by reducing cache clear operations
169
   *
170
   * @param cacheContext - Function containing operations that may trigger cache evictions
171
   * @returns Promise that resolves when all operations and cache clearing are complete
172
   *
173
   * @example
174
   * ```typescript
175
   * await forgeSQL.executeWithCacheContext(async () => {
176
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
177
   *   await forgeSQL.modifyWithVersioning().insert(orders, orderData);
178
   *   // Cache for both users and orders tables will be cleared at the end
179
   * });
180
   * ```
181
   */
182
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
6✔
183
    return this.executeWithCacheContextAndReturnValue<void>(cacheContext);
6✔
184
  }
6✔
185

186
  /**
187
   * Executes operations within a cache context and returns a value.
188
   * All clearCache calls within the context are collected and executed in batch at the end.
189
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
190
   *
191
   * @param cacheContext - Function containing operations that may trigger cache evictions
192
   * @returns Promise that resolves to the return value of the cacheContext function
193
   *
194
   * @example
195
   * ```typescript
196
   * const result = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
197
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
198
   *   return await forgeSQL.fetch().executeQueryOnlyOne(selectUserQuery);
199
   * });
200
   * ```
201
   */
202
  async executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
6✔
203
    return await this.executeWithLocalCacheContextAndReturnValue(
6✔
204
      async () =>
6✔
205
        await cacheApplicationContext.run(
6✔
206
          cacheApplicationContext.getStore() ?? { tables: new Set<string>() },
6✔
207
          async () => {
6✔
208
            try {
6✔
209
              return await cacheContext();
6✔
210
            } finally {
6✔
211
              await clearTablesCache(
6✔
212
                Array.from(cacheApplicationContext.getStore()?.tables ?? []),
6!
213
                this.options,
6✔
214
              );
6✔
215
            }
6✔
216
          },
6✔
217
        ),
6✔
218
    );
6✔
219
  }
6✔
220
  /**
221
   * Executes operations within a local cache context and returns a value.
222
   * This provides in-memory caching for select queries within a single request scope.
223
   *
224
   * @param cacheContext - Function containing operations that will benefit from local caching
225
   * @returns Promise that resolves to the return value of the cacheContext function
226
   */
227
  async executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
6✔
228
    return await localCacheApplicationContext.run(
18✔
229
      localCacheApplicationContext.getStore() ?? { cache: {} },
18✔
230
      async () => {
18✔
231
        return await cacheContext();
18✔
232
      },
18✔
233
    );
18✔
234
  }
18✔
235

236
  /**
237
   * Executes operations within a local cache context.
238
   * This provides in-memory caching for select queries within a single request scope.
239
   *
240
   * @param cacheContext - Function containing operations that will benefit from local caching
241
   * @returns Promise that resolves when all operations are complete
242
   */
243
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
6✔
244
    return this.executeWithLocalCacheContextAndReturnValue<void>(cacheContext);
9✔
245
  }
9✔
246
  /**
247
   * Creates an insert query builder.
248
   *
249
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
250
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
251
   *
252
   * @param table - The table to insert into
253
   * @returns Insert query builder (no versioning, no cache management)
254
   */
255
  insert<TTable extends MySqlTable>(
6✔
256
    table: TTable,
14✔
257
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
14✔
258
    return this.drizzle.insertWithCacheContext(table);
14✔
259
  }
14✔
260
  /**
261
   * Creates an insert query builder that automatically evicts cache after execution.
262
   *
263
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
264
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
265
   *
266
   * @param table - The table to insert into
267
   * @returns Insert query builder with automatic cache eviction (no versioning)
268
   */
269
  insertAndEvictCache<TTable extends MySqlTable>(
6✔
270
    table: TTable,
2✔
271
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
272
    return this.drizzle.insertAndEvictCache(table);
2✔
273
  }
2✔
274

275
  /**
276
   * Creates an update query builder that automatically evicts cache after execution.
277
   *
278
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
279
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
280
   *
281
   * @param table - The table to update
282
   * @returns Update query builder with automatic cache eviction (no versioning)
283
   */
284
  updateAndEvictCache<TTable extends MySqlTable>(
6✔
285
    table: TTable,
2✔
286
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
287
    return this.drizzle.updateAndEvictCache(table);
2✔
288
  }
2✔
289

290
  /**
291
   * Creates an update query builder.
292
   *
293
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
294
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
295
   *
296
   * @param table - The table to update
297
   * @returns Update query builder (no versioning, no cache management)
298
   */
299
  update<TTable extends MySqlTable>(
6✔
300
    table: TTable,
16✔
301
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
16✔
302
    return this.drizzle.updateWithCacheContext(table);
16✔
303
  }
16✔
304

305
  /**
306
   * Creates a delete query builder.
307
   *
308
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
309
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
310
   *
311
   * @param table - The table to delete from
312
   * @returns Delete query builder (no versioning, no cache management)
313
   */
314
  delete<TTable extends MySqlTable>(
6✔
315
    table: TTable,
6✔
316
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
6✔
317
    return this.drizzle.deleteWithCacheContext(table);
6✔
318
  }
6✔
319
  /**
320
   * Creates a delete query builder that automatically evicts cache after execution.
321
   *
322
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
323
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
324
   *
325
   * @param table - The table to delete from
326
   * @returns Delete query builder with automatic cache eviction (no versioning)
327
   */
328
  deleteAndEvictCache<TTable extends MySqlTable>(
6✔
329
    table: TTable,
2✔
330
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
331
    return this.drizzle.deleteAndEvictCache(table);
2✔
332
  }
2✔
333

334
  /**
335
   * Create the modify operations instance.
336
   * @returns modify operations.
337
   */
338
  modifyWithVersioning(): VerioningModificationForgeSQL {
6✔
339
    return this.crudOperations;
26✔
340
  }
26✔
341

342
  /**
343
   * Returns the singleton instance of ForgeSQLORMImpl.
344
   * @param options - Options for configuring ForgeSQL ORM behavior.
345
   * @returns The singleton instance of ForgeSQLORMImpl.
346
   */
347
  static getInstance(options?: ForgeSqlOrmOptions): ForgeSqlOperation {
6✔
348
    ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
81✔
349
    return ForgeSQLORMImpl.instance;
81✔
350
  }
81✔
351

352
  /**
353
   * Retrieves the fetch operations instance.
354
   * @returns Fetch operations.
355
   */
356
  fetch(): SchemaSqlForgeSql {
6✔
357
    return this.fetchOperations;
6✔
358
  }
6✔
359

360
  /**
361
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
362
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
363
   */
364
  analyze(): SchemaAnalyzeForgeSql {
6✔
365
    return this.analyzeOperations;
1✔
366
  }
1✔
367

368
  /**
369
   * Provides schema-level SQL operations with optimistic locking/versioning and automatic cache eviction.
370
   *
371
   * This method returns operations that use `modifyWithVersioning()` internally, providing:
372
   * - Optimistic locking support
373
   * - Automatic version field management
374
   * - Cache eviction after successful operations
375
   *
376
   * @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
377
   */
378
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
6✔
379
    return this.cacheOperations;
3✔
380
  }
3✔
381

382
  /**
383
   * Returns a Drizzle query builder instance.
384
   *
385
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
386
   * The returned instance should NOT be used for direct database connections or query execution.
387
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
388
   *
389
   * @returns A Drizzle query builder instance for query construction only.
390
   */
391
  getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> & {
6✔
392
    selectAliased: SelectAliasedType;
393
    selectAliasedDistinct: SelectAliasedDistinctType;
394
    selectAliasedCacheable: SelectAliasedCacheableType;
395
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
396
    insertWithCacheContext: InsertAndEvictCacheType;
397
    insertAndEvictCache: InsertAndEvictCacheType;
398
    updateAndEvictCache: UpdateAndEvictCacheType;
399
    updateWithCacheContext: UpdateAndEvictCacheType;
400
    deleteAndEvictCache: DeleteAndEvictCacheType;
401
    deleteWithCacheContext: DeleteAndEvictCacheType;
402
  } {
14✔
403
    return this.drizzle;
14✔
404
  }
14✔
405

406
  /**
407
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
408
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
409
   *
410
   * @template TSelection - The type of the selected fields
411
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
412
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
413
   * @throws {Error} If fields parameter is empty
414
   * @example
415
   * ```typescript
416
   * await forgeSQL
417
   *   .select({user: users, order: orders})
418
   *   .from(orders)
419
   *   .innerJoin(users, eq(orders.userId, users.id));
420
   * ```
421
   */
422
  select<TSelection extends SelectedFields>(
6✔
423
    fields: TSelection,
21✔
424
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
21✔
425
    if (!fields) {
21!
426
      throw new Error("fields is empty");
×
427
    }
×
428
    return this.drizzle.selectAliased(fields);
21✔
429
  }
21✔
430

431
  /**
432
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
433
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
434
   *
435
   * @template TSelection - The type of the selected fields
436
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
437
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
438
   * @throws {Error} If fields parameter is empty
439
   * @example
440
   * ```typescript
441
   * await forgeSQL
442
   *   .selectDistinct({user: users, order: orders})
443
   *   .from(orders)
444
   *   .innerJoin(users, eq(orders.userId, users.id));
445
   * ```
446
   */
447
  selectDistinct<TSelection extends SelectedFields>(
6✔
448
    fields: TSelection,
1✔
449
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
450
    if (!fields) {
1!
451
      throw new Error("fields is empty");
×
452
    }
×
453
    return this.drizzle.selectAliasedDistinct(fields);
1✔
454
  }
1✔
455

456
  /**
457
   * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
458
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
459
   *
460
   * @template TSelection - The type of the selected fields
461
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
462
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
463
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
464
   * @throws {Error} If fields parameter is empty
465
   * @example
466
   * ```typescript
467
   * await forgeSQL
468
   *   .selectCacheable({user: users, order: orders},60)
469
   *   .from(orders)
470
   *   .innerJoin(users, eq(orders.userId, users.id));
471
   * ```
472
   */
473
  selectCacheable<TSelection extends SelectedFields>(
6✔
474
    fields: TSelection,
1✔
475
    cacheTTL?: number,
1✔
476
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
477
    if (!fields) {
1!
478
      throw new Error("fields is empty");
×
479
    }
×
480
    return this.drizzle.selectAliasedCacheable(fields, cacheTTL);
1✔
481
  }
1✔
482

483
  /**
484
   * Creates a cacheable distinct select query with unique field aliases to prevent field name collisions in joins.
485
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
486
   *
487
   * @template TSelection - The type of the selected fields
488
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
489
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
490
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
491
   * @throws {Error} If fields parameter is empty
492
   * @example
493
   * ```typescript
494
   * await forgeSQL
495
   *   .selectDistinctCacheable({user: users, order: orders}, 60)
496
   *   .from(orders)
497
   *   .innerJoin(users, eq(orders.userId, users.id));
498
   * ```
499
   */
500
  selectDistinctCacheable<TSelection extends SelectedFields>(
6✔
501
    fields: TSelection,
1✔
502
    cacheTTL?: number,
1✔
503
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
504
    if (!fields) {
1!
505
      throw new Error("fields is empty");
×
506
    }
×
507
    return this.drizzle.selectAliasedDistinctCacheable(fields, cacheTTL);
1✔
508
  }
1✔
509

510
  /**
511
   * Creates a select query builder for all columns from a table with field aliasing support.
512
   * This is a convenience method that automatically selects all columns from the specified table.
513
   *
514
   * @template T - The type of the table
515
   * @param table - The table to select from
516
   * @returns Select query builder with all table columns and field aliasing support
517
   * @example
518
   * ```typescript
519
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
520
   * ```
521
   */
522
  selectFrom<T extends MySqlTable>(table: T) {
6✔
523
    return this.drizzle.selectFrom(table);
×
524
  }
×
525

526
  /**
527
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
528
   * This is a convenience method that automatically selects all distinct columns from the specified table.
529
   *
530
   * @template T - The type of the table
531
   * @param table - The table to select from
532
   * @returns Select distinct query builder with all table columns and field aliasing support
533
   * @example
534
   * ```typescript
535
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
536
   * ```
537
   */
538
  selectDistinctFrom<T extends MySqlTable>(table: T) {
6✔
539
    return this.drizzle.selectDistinctFrom(table);
×
540
  }
×
541

542
  /**
543
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
544
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
545
   *
546
   * @template T - The type of the table
547
   * @param table - The table to select from
548
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
549
   * @returns Select query builder with all table columns, field aliasing, and caching support
550
   * @example
551
   * ```typescript
552
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
553
   * ```
554
   */
555
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
6✔
556
    return this.drizzle.selectFromCacheable(table, cacheTTL);
×
557
  }
×
558

559
  /**
560
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
561
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
562
   *
563
   * @template T - The type of the table
564
   * @param table - The table to select from
565
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
566
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
567
   * @example
568
   * ```typescript
569
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
570
   * ```
571
   */
572
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
6✔
573
    return this.drizzle.selectDistinctFromCacheable(table, cacheTTL);
×
574
  }
×
575

576
  /**
577
   * Executes a raw SQL query with local cache support.
578
   * This method provides local caching for raw SQL queries within the current invocation context.
579
   * Results are cached locally and will be returned from cache on subsequent identical queries.
580
   *
581
   * @param query - The SQL query to execute (SQLWrapper or string)
582
   * @returns Promise with query results
583
   * @example
584
   * ```typescript
585
   * // Using SQLWrapper
586
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
587
   *
588
   * // Using string
589
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
590
   * ```
591
   */
592
  execute<T>(query: SQLWrapper | string) {
6✔
593
    return this.drizzle.executeQuery<T>(query);
1✔
594
  }
1✔
595

596
  /**
597
   * Executes a Data Definition Language (DDL) SQL query.
598
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
599
   *
600
   * This method is specifically designed for DDL operations and provides:
601
   * - Proper operation type context for DDL queries
602
   * - No caching (DDL operations should not be cached)
603
   * - Direct execution without query optimization
604
   *
605
   * @template T - The expected return type of the query result
606
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
607
   * @returns Promise with query results
608
   * @throws {Error} If the DDL operation fails
609
   *
610
   * @example
611
   * ```typescript
612
   * // Create a new table
613
   * await forgeSQL.executeDDL(`
614
   *   CREATE TABLE users (
615
   *     id INT PRIMARY KEY AUTO_INCREMENT,
616
   *     name VARCHAR(255) NOT NULL,
617
   *     email VARCHAR(255) UNIQUE
618
   *   )
619
   * `);
620
   *
621
   * // Alter table structure
622
   * await forgeSQL.executeDDL(sql`
623
   *   ALTER TABLE users
624
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
625
   * `);
626
   *
627
   * // Drop a table
628
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
629
   * ```
630
   */
631
  async executeDDL<T>(query: SQLWrapper | string) {
6✔
632
    return this.executeDDLActions(async () => this.drizzle.executeQuery<T>(query));
2✔
633
  }
2✔
634

635
  /**
636
   * Executes a series of actions within a DDL operation context.
637
   * This method provides a way to execute regular SQL queries that should be treated
638
   * as DDL operations, ensuring proper operation type context for performance monitoring.
639
   *
640
   * This method is useful for:
641
   * - Executing regular SQL queries in DDL context for monitoring purposes
642
   * - Wrapping non-DDL operations that should be treated as DDL for analysis
643
   * - Ensuring proper operation type context for complex workflows
644
   * - Maintaining DDL operation context across multiple function calls
645
   *
646
   * @template T - The return type of the actions function
647
   * @param actions - Function containing SQL operations to execute in DDL context
648
   * @returns Promise that resolves to the return value of the actions function
649
   *
650
   * @example
651
   * ```typescript
652
   * // Execute regular SQL queries in DDL context for monitoring
653
   * await forgeSQL.executeDDLActions(async () => {
654
   *   const slowQueries = await forgeSQL.execute(`
655
   *     SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
656
   *     WHERE AVG_LATENCY > 1000000
657
   *   `);
658
   *   return slowQueries;
659
   * });
660
   *
661
   * // Execute complex analysis queries in DDL context
662
   * const result = await forgeSQL.executeDDLActions(async () => {
663
   *   const tableInfo = await forgeSQL.execute("SHOW TABLES");
664
   *   const performanceData = await forgeSQL.execute(`
665
   *     SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
666
   *     WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
667
   *   `);
668
   *   return { tableInfo, performanceData };
669
   * });
670
   *
671
   * // Execute monitoring queries with error handling
672
   * try {
673
   *   await forgeSQL.executeDDLActions(async () => {
674
   *     const metrics = await forgeSQL.execute(`
675
   *       SELECT COUNT(*) as query_count
676
   *       FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
677
   *     `);
678
   *     console.log(`Total queries: ${metrics[0].query_count}`);
679
   *   });
680
   * } catch (error) {
681
   *   console.error("Monitoring query failed:", error);
682
   * }
683
   * ```
684
   */
685
  async executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
6✔
686
    return operationTypeQueryContext.run({ operationType: "DDL" }, async () => actions());
2✔
687
  }
2✔
688

689
  /**
690
   * Executes a raw SQL query with both local and global cache support.
691
   * This method provides comprehensive caching for raw SQL queries:
692
   * - Local cache: Within the current invocation context
693
   * - Global cache: Cross-invocation caching using @forge/kvs
694
   *
695
   * @param query - The SQL query to execute (SQLWrapper or string)
696
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
697
   * @returns Promise with query results
698
   * @example
699
   * ```typescript
700
   * // Using SQLWrapper with custom TTL
701
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
702
   *
703
   * // Using string with default TTL
704
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
705
   * ```
706
   */
707
  executeCacheable<T>(query: SQLWrapper | string, cacheTtl?: number) {
6✔
708
    return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
2✔
709
  }
2✔
710

711
  /**
712
   * Creates a Common Table Expression (CTE) builder for complex queries.
713
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
714
   *
715
   * @returns WithBuilder for creating CTEs
716
   * @example
717
   * ```typescript
718
   * const withQuery = forgeSQL.$with('userStats').as(
719
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
720
   *     .from(users)
721
   *     .groupBy(users.id)
722
   * );
723
   * ```
724
   */
725
  get $with() {
6✔
726
    return this.drizzle.$with;
×
727
  }
×
728

729
  /**
730
   * Creates a query builder that uses Common Table Expressions (CTEs).
731
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
732
   *
733
   * @param queries - Array of CTE queries created with $with()
734
   * @returns Query builder with CTE support
735
   * @example
736
   * ```typescript
737
   * const withQuery = forgeSQL.$with('userStats').as(
738
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
739
   *     .from(users)
740
   *     .groupBy(users.id)
741
   * );
742
   *
743
   * const result = await forgeSQL.with(withQuery)
744
   *   .select({ userId: withQuery.userId, count: withQuery.count })
745
   *   .from(withQuery);
746
   * ```
747
   */
748
  with(...queries: WithSubquery[]) {
6✔
749
    return this.drizzle.with(...queries);
×
750
  }
×
751
}
6✔
752

753
/**
754
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
755
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
756
 */
757
class ForgeSQLORM implements ForgeSqlOperation {
1✔
758
  private readonly ormInstance: ForgeSqlOperation;
81✔
759

760
  constructor(options?: ForgeSqlOrmOptions) {
81✔
761
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
81✔
762
  }
81✔
763

764
  /**
765
   * Executes a query and provides access to execution metadata.
766
   * This method allows you to capture detailed information about query execution
767
   * including database execution time, response size, and Forge SQL metadata.
768
   *
769
   * @template T - The return type of the query
770
   * @param query - A function that returns a Promise with the query result
771
   * @param onMetadata - Callback function that receives execution metadata
772
   * @returns Promise with the query result
773
   * @example
774
   * ```typescript
775
   * const result = await forgeSQL.executeWithMetadata(
776
   *   async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
777
   *   (dbTime, responseSize, metadata) => {
778
   *     console.log(`DB execution time: ${dbTime}ms`);
779
   *     console.log(`Response size: ${responseSize} bytes`);
780
   *     console.log('Forge metadata:', metadata);
781
   *   }
782
   * );
783
   * ```
784
   */
785
  async executeWithMetadata<T>(
81✔
786
    query: () => Promise<T>,
1✔
787
    onMetadata: (
1✔
788
      totalDbExecutionTime: number,
789
      totalResponseSize: number,
790
      forgeMetadata: ForgeSQLMetadata,
791
    ) => Promise<void> | void,
792
  ): Promise<T> {
1✔
793
    return this.ormInstance.executeWithMetadata(query, onMetadata);
1✔
794
  }
1✔
795

796
  selectCacheable<TSelection extends SelectedFields>(
81✔
797
    fields: TSelection,
1✔
798
    cacheTTL?: number,
1✔
799
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
800
    return this.ormInstance.selectCacheable(fields, cacheTTL);
1✔
801
  }
1✔
802

803
  selectDistinctCacheable<TSelection extends SelectedFields>(
81✔
804
    fields: TSelection,
1✔
805
    cacheTTL?: number,
1✔
806
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
807
    return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1✔
808
  }
1✔
809

810
  /**
811
   * Creates a select query builder for all columns from a table with field aliasing support.
812
   * This is a convenience method that automatically selects all columns from the specified table.
813
   *
814
   * @template T - The type of the table
815
   * @param table - The table to select from
816
   * @returns Select query builder with all table columns and field aliasing support
817
   * @example
818
   * ```typescript
819
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
820
   * ```
821
   */
822
  selectFrom<T extends MySqlTable>(table: T) {
81✔
823
    return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table);
1✔
824
  }
1✔
825

826
  /**
827
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
828
   * This is a convenience method that automatically selects all distinct columns from the specified table.
829
   *
830
   * @template T - The type of the table
831
   * @param table - The table to select from
832
   * @returns Select distinct query builder with all table columns and field aliasing support
833
   * @example
834
   * ```typescript
835
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
836
   * ```
837
   */
838
  selectDistinctFrom<T extends MySqlTable>(table: T) {
81✔
839
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table);
1✔
840
  }
1✔
841

842
  /**
843
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
844
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
845
   *
846
   * @template T - The type of the table
847
   * @param table - The table to select from
848
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
849
   * @returns Select query builder with all table columns, field aliasing, and caching support
850
   * @example
851
   * ```typescript
852
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
853
   * ```
854
   */
855
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
81✔
856
    return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table, cacheTTL);
1✔
857
  }
1✔
858

859
  /**
860
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
861
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
862
   *
863
   * @template T - The type of the table
864
   * @param table - The table to select from
865
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
866
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
867
   * @example
868
   * ```typescript
869
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
870
   * ```
871
   */
872
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
81✔
873
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table, cacheTTL);
1✔
874
  }
1✔
875

876
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
81✔
877
    return this.ormInstance.executeWithCacheContext(cacheContext);
6✔
878
  }
6✔
879
  executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
81✔
880
    return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
×
881
  }
×
882
  /**
883
   * Executes operations within a local cache context.
884
   * This provides in-memory caching for select queries within a single request scope.
885
   *
886
   * @param cacheContext - Function containing operations that will benefit from local caching
887
   * @returns Promise that resolves when all operations are complete
888
   */
889
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
81✔
890
    return this.ormInstance.executeWithLocalContext(cacheContext);
9✔
891
  }
9✔
892

893
  /**
894
   * Executes operations within a local cache context and returns a value.
895
   * This provides in-memory caching for select queries within a single request scope.
896
   *
897
   * @param cacheContext - Function containing operations that will benefit from local caching
898
   * @returns Promise that resolves to the return value of the cacheContext function
899
   */
900
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
81✔
901
    return this.ormInstance.executeWithLocalCacheContextAndReturnValue(cacheContext);
3✔
902
  }
3✔
903

904
  /**
905
   * Creates an insert query builder.
906
   *
907
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
908
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
909
   *
910
   * @param table - The table to insert into
911
   * @returns Insert query builder (no versioning, no cache management)
912
   */
913
  insert<TTable extends MySqlTable>(
81✔
914
    table: TTable,
5✔
915
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
5✔
916
    return this.ormInstance.insert(table);
5✔
917
  }
5✔
918

919
  /**
920
   * Creates an insert query builder that automatically evicts cache after execution.
921
   *
922
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
923
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
924
   *
925
   * @param table - The table to insert into
926
   * @returns Insert query builder with automatic cache eviction (no versioning)
927
   */
928
  insertAndEvictCache<TTable extends MySqlTable>(
81✔
929
    table: TTable,
2✔
930
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
931
    return this.ormInstance.insertAndEvictCache(table);
2✔
932
  }
2✔
933

934
  /**
935
   * Creates an update query builder.
936
   *
937
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
938
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
939
   *
940
   * @param table - The table to update
941
   * @returns Update query builder (no versioning, no cache management)
942
   */
943
  update<TTable extends MySqlTable>(
81✔
944
    table: TTable,
3✔
945
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
946
    return this.ormInstance.update(table);
3✔
947
  }
3✔
948

949
  /**
950
   * Creates an update query builder that automatically evicts cache after execution.
951
   *
952
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
953
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
954
   *
955
   * @param table - The table to update
956
   * @returns Update query builder with automatic cache eviction (no versioning)
957
   */
958
  updateAndEvictCache<TTable extends MySqlTable>(
81✔
959
    table: TTable,
2✔
960
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
961
    return this.ormInstance.updateAndEvictCache(table);
2✔
962
  }
2✔
963

964
  /**
965
   * Creates a delete query builder.
966
   *
967
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
968
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
969
   *
970
   * @param table - The table to delete from
971
   * @returns Delete query builder (no versioning, no cache management)
972
   */
973
  delete<TTable extends MySqlTable>(
81✔
974
    table: TTable,
3✔
975
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
976
    return this.ormInstance.delete(table);
3✔
977
  }
3✔
978

979
  /**
980
   * Creates a delete query builder that automatically evicts cache after execution.
981
   *
982
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
983
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
984
   *
985
   * @param table - The table to delete from
986
   * @returns Delete query builder with automatic cache eviction (no versioning)
987
   */
988
  deleteAndEvictCache<TTable extends MySqlTable>(
81✔
989
    table: TTable,
2✔
990
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
991
    return this.ormInstance.deleteAndEvictCache(table);
2✔
992
  }
2✔
993

994
  /**
995
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
996
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
997
   *
998
   * @template TSelection - The type of the selected fields
999
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
1000
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
1001
   * @throws {Error} If fields parameter is empty
1002
   * @example
1003
   * ```typescript
1004
   * await forgeSQL
1005
   *   .select({user: users, order: orders})
1006
   *   .from(orders)
1007
   *   .innerJoin(users, eq(orders.userId, users.id));
1008
   * ```
1009
   */
1010
  select<TSelection extends SelectedFields>(
81✔
1011
    fields: TSelection,
20✔
1012
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
20✔
1013
    return this.ormInstance.select(fields);
20✔
1014
  }
20✔
1015

1016
  /**
1017
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
1018
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
1019
   *
1020
   * @template TSelection - The type of the selected fields
1021
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
1022
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
1023
   * @throws {Error} If fields parameter is empty
1024
   * @example
1025
   * ```typescript
1026
   * await forgeSQL
1027
   *   .selectDistinct({user: users, order: orders})
1028
   *   .from(orders)
1029
   *   .innerJoin(users, eq(orders.userId, users.id));
1030
   * ```
1031
   */
1032
  selectDistinct<TSelection extends SelectedFields>(
81✔
1033
    fields: TSelection,
1✔
1034
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
1035
    return this.ormInstance.selectDistinct(fields);
1✔
1036
  }
1✔
1037

1038
  /**
1039
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
1040
   * @returns Modify operations.
1041
   */
1042
  modifyWithVersioning(): VerioningModificationForgeSQL {
81✔
1043
    return this.ormInstance.modifyWithVersioning();
25✔
1044
  }
25✔
1045

1046
  /**
1047
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
1048
   * @returns Fetch operations.
1049
   */
1050
  fetch(): SchemaSqlForgeSql {
81✔
1051
    return this.ormInstance.fetch();
5✔
1052
  }
5✔
1053

1054
  /**
1055
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
1056
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
1057
   */
1058
  analyze(): SchemaAnalyzeForgeSql {
81✔
1059
    return this.ormInstance.analyze();
1✔
1060
  }
1✔
1061

1062
  /**
1063
   * Provides schema-level SQL cacheable operations with type safety.
1064
   * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
1065
   */
1066
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
81✔
1067
    return this.ormInstance.modifyWithVersioningAndEvictCache();
3✔
1068
  }
3✔
1069

1070
  /**
1071
   * Returns a Drizzle query builder instance.
1072
   *
1073
   * @returns A Drizzle query builder instance for query construction only.
1074
   */
1075
  getDrizzleQueryBuilder() {
81✔
1076
    return this.ormInstance.getDrizzleQueryBuilder();
8✔
1077
  }
8✔
1078

1079
  /**
1080
   * Executes a raw SQL query with local cache support.
1081
   * This method provides local caching for raw SQL queries within the current invocation context.
1082
   * Results are cached locally and will be returned from cache on subsequent identical queries.
1083
   *
1084
   * @param query - The SQL query to execute (SQLWrapper or string)
1085
   * @returns Promise with query results
1086
   * @example
1087
   * ```typescript
1088
   * // Using SQLWrapper
1089
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
1090
   *
1091
   * // Using string
1092
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
1093
   * ```
1094
   */
1095
  execute<T>(
81✔
1096
    query: SQLWrapper | string,
1✔
1097
  ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1✔
1098
    return this.ormInstance.execute(query);
1✔
1099
  }
1✔
1100

1101
  /**
1102
   * Executes a Data Definition Language (DDL) SQL query.
1103
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
1104
   *
1105
   * This method is specifically designed for DDL operations and provides:
1106
   * - Proper operation type context for DDL queries
1107
   * - No caching (DDL operations should not be cached)
1108
   * - Direct execution without query optimization
1109
   *
1110
   * @template T - The expected return type of the query result
1111
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
1112
   * @returns Promise with query results
1113
   * @throws {Error} If the DDL operation fails
1114
   *
1115
   * @example
1116
   * ```typescript
1117
   * // Create a new table
1118
   * await forgeSQL.executeDDL(`
1119
   *   CREATE TABLE users (
1120
   *     id INT PRIMARY KEY AUTO_INCREMENT,
1121
   *     name VARCHAR(255) NOT NULL,
1122
   *     email VARCHAR(255) UNIQUE
1123
   *   )
1124
   * `);
1125
   *
1126
   * // Alter table structure
1127
   * await forgeSQL.executeDDL(sql`
1128
   *   ALTER TABLE users
1129
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1130
   * `);
1131
   *
1132
   * // Drop a table
1133
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
1134
   * ```
1135
   */
1136
  executeDDL(query: SQLWrapper | string) {
81✔
1137
    return this.ormInstance.executeDDL(query);
2✔
1138
  }
2✔
1139

1140
  /**
1141
   * Executes a series of actions within a DDL operation context.
1142
   * This method provides a way to execute regular SQL queries that should be treated
1143
   * as DDL operations, ensuring proper operation type context for performance monitoring.
1144
   *
1145
   * This method is useful for:
1146
   * - Executing regular SQL queries in DDL context for monitoring purposes
1147
   * - Wrapping non-DDL operations that should be treated as DDL for analysis
1148
   * - Ensuring proper operation type context for complex workflows
1149
   * - Maintaining DDL operation context across multiple function calls
1150
   *
1151
   * @template T - The return type of the actions function
1152
   * @param actions - Function containing SQL operations to execute in DDL context
1153
   * @returns Promise that resolves to the return value of the actions function
1154
   *
1155
   * @example
1156
   * ```typescript
1157
   * // Execute regular SQL queries in DDL context for monitoring
1158
   * await forgeSQL.executeDDLActions(async () => {
1159
   *   const slowQueries = await forgeSQL.execute(`
1160
   *     SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1161
   *     WHERE AVG_LATENCY > 1000000
1162
   *   `);
1163
   *   return slowQueries;
1164
   * });
1165
   *
1166
   * // Execute complex analysis queries in DDL context
1167
   * const result = await forgeSQL.executeDDLActions(async () => {
1168
   *   const tableInfo = await forgeSQL.execute("SHOW TABLES");
1169
   *   const performanceData = await forgeSQL.execute(`
1170
   *     SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
1171
   *     WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
1172
   *   `);
1173
   *   return { tableInfo, performanceData };
1174
   * });
1175
   *
1176
   * // Execute monitoring queries with error handling
1177
   * try {
1178
   *   await forgeSQL.executeDDLActions(async () => {
1179
   *     const metrics = await forgeSQL.execute(`
1180
   *       SELECT COUNT(*) as query_count
1181
   *       FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1182
   *     `);
1183
   *     console.log(`Total queries: ${metrics[0].query_count}`);
1184
   *   });
1185
   * } catch (error) {
1186
   *   console.error("Monitoring query failed:", error);
1187
   * }
1188
   * ```
1189
   */
1190
  executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
81✔
NEW
1191
    return this.ormInstance.executeDDLActions(actions);
×
UNCOV
1192
  }
×
1193

1194
  /**
1195
   * Executes a raw SQL query with both local and global cache support.
1196
   * This method provides comprehensive caching for raw SQL queries:
1197
   * - Local cache: Within the current invocation context
1198
   * - Global cache: Cross-invocation caching using @forge/kvs
1199
   *
1200
   * @param query - The SQL query to execute (SQLWrapper or string)
1201
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
1202
   * @returns Promise with query results
1203
   * @example
1204
   * ```typescript
1205
   * // Using SQLWrapper with custom TTL
1206
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
1207
   *
1208
   * // Using string with default TTL
1209
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1210
   * ```
1211
   */
1212
  executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
81✔
1213
    return this.ormInstance.executeCacheable(query, cacheTtl);
2✔
1214
  }
2✔
1215

1216
  /**
1217
   * Creates a Common Table Expression (CTE) builder for complex queries.
1218
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1219
   *
1220
   * @returns WithBuilder for creating CTEs
1221
   * @example
1222
   * ```typescript
1223
   * const withQuery = forgeSQL.$with('userStats').as(
1224
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1225
   *     .from(users)
1226
   *     .groupBy(users.id)
1227
   * );
1228
   * ```
1229
   */
1230
  get $with() {
81✔
1231
    return this.ormInstance.getDrizzleQueryBuilder().$with;
1✔
1232
  }
1✔
1233

1234
  /**
1235
   * Creates a query builder that uses Common Table Expressions (CTEs).
1236
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1237
   *
1238
   * @param queries - Array of CTE queries created with $with()
1239
   * @returns Query builder with CTE support
1240
   * @example
1241
   * ```typescript
1242
   * const withQuery = forgeSQL.$with('userStats').as(
1243
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1244
   *     .from(users)
1245
   *     .groupBy(users.id)
1246
   * );
1247
   *
1248
   * const result = await forgeSQL.with(withQuery)
1249
   *   .select({ userId: withQuery.userId, count: withQuery.count })
1250
   *   .from(withQuery);
1251
   * ```
1252
   */
1253
  with(...queries: WithSubquery[]) {
81✔
1254
    return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
1✔
1255
  }
1✔
1256
}
81✔
1257

1258
export default ForgeSQLORM;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc