• 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

75.59
/src/utils/forgeDriver.ts
1
import { sql, UpdateQueryResponse } from "@forge/sql";
1✔
2
import { saveMetaDataToContext } from "./metadataContextUtils";
1✔
3
import { getOperationType } from "./requestTypeContextUtils";
1✔
4

5
/**
6
 * Metadata structure for Forge SQL query results.
7
 * Contains execution timing, response size, and field information.
8
 */
9
export type ForgeSQLMetadata = {
10
  dbExecutionTime: number;
11
  responseSize: number;
12
  fields: {
13
    catalog: string;
14
    name: string;
15
    schema: string;
16
    characterSet: number;
17
    decimals: number;
18
    table: string;
19
    orgTable: string;
20
    orgName: string;
21
    flags: number;
22
    columnType: number;
23
    columnLength: number;
24
  }[];
25
};
26

27
/**
28
 * Result structure for Forge SQL queries.
29
 * Contains rows data and execution metadata.
30
 */
31
export interface ForgeSQLResult {
32
  rows: Record<string, unknown>[] | Record<string, unknown>;
33
  metadata: ForgeSQLMetadata;
34
}
35

36
/**
37
 * Driver result structure for Drizzle ORM compatibility.
38
 */
39
export interface ForgeDriverResult {
40
  rows: unknown[];
41
  insertId?: number;
42
  affectedRows?: number;
43
}
44

45
/**
46
 * Query execution method types.
47
 */
48
export type QueryMethod = "all" | "execute";
49

50
/**
51
 * Type guard to check if an object is an UpdateQueryResponse.
52
 *
53
 * @param obj - The object to check
54
 * @returns True if the object is an UpdateQueryResponse
55
 */
56
export function isUpdateQueryResponse(obj: unknown): obj is UpdateQueryResponse {
1✔
57
  return (
44✔
58
    obj !== null &&
44✔
59
    typeof obj === "object" &&
44✔
60
    typeof (obj as any).affectedRows === "number" &&
44✔
61
    typeof (obj as any).insertId === "number"
37✔
62
  );
63
}
44✔
64

65
/**
66
 * Executes a promise with a timeout.
67
 *
68
 * @param promise - The promise to execute
69
 * @param timeoutMs - Timeout in milliseconds (default: 10000ms)
70
 * @returns Promise that resolves with the result or rejects on timeout
71
 * @throws {Error} When the operation times out
72
 */
73
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number = 10000): Promise<T> {
78✔
74
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
78✔
75

76
  const timeoutPromise = new Promise<never>((_, reject) => {
78✔
77
    timeoutId = setTimeout(() => {
78✔
NEW
78
      reject(
×
NEW
79
        new Error(
×
NEW
80
          `Atlassian @forge/sql did not return a response within ${timeoutMs}ms (${timeoutMs / 1000} seconds), so the request is blocked. Possible causes: slow query, network issues, or exceeding Forge SQL limits.`,
×
NEW
81
        ),
×
NEW
82
      );
×
83
    }, timeoutMs);
78✔
84
  });
78✔
85

86
  try {
78✔
87
    return await Promise.race([promise, timeoutPromise]);
78✔
88
  } finally {
78✔
89
    if (timeoutId) {
78✔
90
      clearTimeout(timeoutId);
78✔
91
    }
78✔
92
  }
78✔
93
}
78✔
94

95
function inlineParams(sql: string, params: unknown[]): string {
2✔
96
  let i = 0;
2✔
97
  return sql.replace(/\?/g, () => {
2✔
NEW
98
    const val = params[i++];
×
NEW
99
    if (val === null) return "NULL";
×
NEW
100
    if (typeof val === "number") return val.toString();
×
NEW
101
    return `'${String(val).replace(/'/g, "''")}'`;
×
102
  });
2✔
103
}
2✔
104

105
/**
106
 * Processes DDL query results and saves metadata.
107
 *
108
 * @param result - The DDL query result
109
 * @returns Processed result for Drizzle ORM
110
 */
111
async function processDDLResult(method: QueryMethod, result: any): Promise<ForgeDriverResult> {
2✔
112
  if (result.metadata) {
2✔
113
    await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
2✔
114
  }
2✔
115

116
  if (!result.rows) {
2!
NEW
117
    return { rows: [] };
×
NEW
118
  }
×
119

120
  if (isUpdateQueryResponse(result.rows)) {
2✔
121
    const oneRow = result.rows as any;
2✔
122
    return { ...oneRow, rows: [oneRow] };
2✔
123
  }
2!
124

NEW
125
  if (Array.isArray(result.rows)) {
×
NEW
126
    if (method === "execute") {
×
NEW
127
      return { rows: result.rows };
×
NEW
128
    } else {
×
NEW
129
      const rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
×
NEW
130
      return { rows };
×
UNCOV
131
    }
×
UNCOV
132
  }
×
133

NEW
134
  return { rows: [] };
×
NEW
135
}
×
136

137
/**
138
 * Processes execute method results (UPDATE, INSERT, DELETE).
139
 *
140
 * @param query - The SQL query
141
 * @param params - Query parameters
142
 * @returns Processed result for Drizzle ORM
143
 */
144
async function processExecuteMethod(query: string, params: unknown[]): Promise<ForgeDriverResult> {
45✔
145
  const sqlStatement = sql.prepare<UpdateQueryResponse>(query);
45✔
146

147
  if (params) {
45✔
148
    sqlStatement.bindParams(...params);
45✔
149
  }
45✔
150

151
  const result = await withTimeout(sqlStatement.execute());
45✔
152
  await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
42✔
153
  if (!result.rows) {
45!
NEW
154
    return { rows: [] };
×
NEW
155
  }
✔
156

157
  if (isUpdateQueryResponse(result.rows)) {
42✔
158
    const oneRow = result.rows as any;
35✔
159
    return { ...oneRow, rows: [oneRow] };
35✔
160
  }
35✔
161

162
  return { rows: result.rows };
7✔
163
}
7✔
164

165
/**
166
 * Processes all method results (SELECT queries).
167
 *
168
 * @param query - The SQL query
169
 * @param params - Query parameters
170
 * @returns Processed result for Drizzle ORM
171
 */
172
async function processAllMethod(query: string, params: unknown[]): Promise<ForgeDriverResult> {
31✔
173
  const sqlStatement = await sql.prepare<unknown>(query);
31✔
174

175
  if (params) {
31✔
176
    await sqlStatement.bindParams(...params);
31✔
177
  }
31✔
178

179
  const result = (await withTimeout(sqlStatement.execute())) as ForgeSQLResult;
31✔
180
  await saveMetaDataToContext(result.metadata);
31✔
181

182
  if (!result.rows) {
31!
NEW
183
    return { rows: [] };
×
NEW
184
  }
×
185

186
  const rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
31✔
187

188
  return { rows };
31✔
189
}
31✔
190

191
/**
192
 * Main Forge SQL driver function for Drizzle ORM integration.
193
 * Handles DDL operations, execute operations (UPDATE/INSERT/DELETE), and select operations.
194
 *
195
 * @param query - The SQL query to execute
196
 * @param params - Query parameters
197
 * @param method - Execution method ("all" for SELECT, "execute" for UPDATE/INSERT/DELETE)
198
 * @returns Promise with query results compatible with Drizzle ORM
199
 *
200
 * @throws {Error} When DDL operations are called with parameters
201
 *
202
 * @example
203
 * ```typescript
204
 * // DDL operation
205
 * await forgeDriver("CREATE TABLE users (id INT)", [], "all");
206
 *
207
 * // SELECT operation
208
 * await forgeDriver("SELECT * FROM users WHERE id = ?", [1], "all");
209
 *
210
 * // UPDATE operation
211
 * await forgeDriver("UPDATE users SET name = ? WHERE id = ?", ["John", 1], "execute");
212
 * ```
213
 */
214
export const forgeDriver = async (
1✔
215
  query: string,
78✔
216
  params: unknown[],
78✔
217
  method: QueryMethod,
78✔
218
): Promise<ForgeDriverResult> => {
78✔
219
  const operationType = await getOperationType();
78✔
220

221
  // Handle DDL operations
222
  if (operationType === "DDL") {
78✔
223
    const result = await withTimeout(sql.executeDDL(inlineParams(query, params)));
2✔
224
    return await processDDLResult(method, result);
2✔
225
  }
2✔
226

227
  // Handle execute method (UPDATE, INSERT, DELETE)
228
  if (method === "execute") {
78✔
229
    return await processExecuteMethod(query, params ?? []);
45!
230
  }
45✔
231

232
  // Handle all method (SELECT)
233
  return await processAllMethod(query, params ?? []);
78!
234
};
78✔
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