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

vzakharchenko / forge-sql-orm / 18196215488

02 Oct 2025 02:32PM UTC coverage: 83.082% (-3.6%) from 86.669%
18196215488

Pull #738

github

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

520 of 633 branches covered (82.15%)

Branch coverage included in aggregate %.

202 of 393 new or added lines in 8 files covered. (51.4%)

34 existing lines in 3 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 Error(`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
79
    }, timeoutMs);
×
NEW
80
  });
×
NEW
81

×
NEW
82
  try {
×
83
    return await Promise.race([promise, timeoutPromise]);
78✔
84
  } finally {
78✔
85
    if (timeoutId) {
86
      clearTimeout(timeoutId);
78✔
87
    }
78✔
88
  }
78✔
89
}
78✔
90

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

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

2✔
112
  if (!result.rows) {
2✔
113
    return { rows: [] };
2✔
114
  }
2✔
115

116
  if (isUpdateQueryResponse(result.rows)) {
2!
NEW
117
    const oneRow = result.rows as any;
×
NEW
118
    return { ...oneRow, rows: [oneRow] };
×
119
  }
120

2✔
121
  if (Array.isArray(result.rows)) {
2✔
122
    if (method === "execute") {
2✔
123
      return { rows: result.rows };
2!
124
    } else {
NEW
125
      const rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
×
NEW
126
      return { rows };
×
UNCOV
127
    }
×
UNCOV
128
  }
×
NEW
129

×
NEW
130
  return { rows: [] };
×
NEW
131
}
×
NEW
132

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

143
  if (params) {
144
    sqlStatement.bindParams(...params);
45✔
145
  }
45✔
146

147
  const result = await withTimeout(sqlStatement.execute());
45✔
148
  await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
45✔
149
  if (!result.rows) {
45✔
150
    return { rows: [] };
151
  }
45✔
152

42✔
153
  if (isUpdateQueryResponse(result.rows)) {
45!
NEW
154
    const oneRow = result.rows as any;
×
NEW
155
    return { ...oneRow, rows: [oneRow] };
✔
156
  }
157

42✔
158
  return { rows: result.rows };
35✔
159
}
35✔
160

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

171
  if (params) {
172
    await sqlStatement.bindParams(...params);
31✔
173
  }
31✔
174

175
  const result = (await withTimeout(sqlStatement.execute())) as ForgeSQLResult;
31✔
176
  await saveMetaDataToContext(result.metadata);
31✔
177

31✔
178
  if (!result.rows) {
179
    return { rows: [] };
31✔
180
  }
31✔
181

182
  const rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
31!
NEW
183

×
NEW
184
  return { rows };
×
185
}
186

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

78✔
217
  // Handle DDL operations
78✔
218
  if (operationType === "DDL") {
78✔
219
    const result = await withTimeout(sql.executeDDL(inlineParams(query, params)));
78✔
220
    return await processDDLResult(method, result);
221
  }
222

78✔
223
  // Handle execute method (UPDATE, INSERT, DELETE)
2✔
224
  if (method === "execute") {
2✔
225
    return await processExecuteMethod(query, params ?? []);
2✔
226
  }
227

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