Skip to content

Commit 99ffd96

Browse files
suryaiyer95claude
andcommitted
fix: upgrade mssql to v12 with ConnectionPool isolation and row flattening
- Upgrade `mssql` from v11 to v12 (`tedious` 18 → 19) - Use explicit `ConnectionPool` instead of global `mssql.connect()` to isolate multiple simultaneous connections - Flatten unnamed column arrays — `mssql` merges unnamed columns (e.g. `SELECT COUNT(*), SUM(...)`) into a single array under the empty-string key; restore positional column values - Proper column name resolution: compare `namedKeys.length` against flattened row length, fall back to synthetic `col_0`, `col_1`, etc. - Update test mock to export `ConnectionPool` class and `createMockPool` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8255caf commit 99ffd96

File tree

4 files changed

+81
-31
lines changed

4 files changed

+81
-31
lines changed

bun.lock

Lines changed: 18 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/drivers/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@google-cloud/bigquery": "^8.0.0",
1818
"@databricks/sql": "^1.0.0",
1919
"mysql2": "^3.0.0",
20-
"mssql": "^11.0.0",
20+
"mssql": "^12.0.0",
2121
"oracledb": "^6.0.0",
2222
"duckdb": "^1.0.0",
2323
"mongodb": "^6.0.0",

packages/drivers/src/sqlserver.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import type { ConnectionConfig, Connector, ConnectorResult, ExecuteOptions, Sche
66

77
export async function connect(config: ConnectionConfig): Promise<Connector> {
88
let mssql: any
9+
let MssqlConnectionPool: any
910
try {
1011
// @ts-expect-error — mssql has no type declarations; installed as optional peerDependency
11-
mssql = await import("mssql")
12-
mssql = mssql.default || mssql
12+
const mod = await import("mssql")
13+
mssql = mod.default || mod
14+
// ConnectionPool is a named export, not on .default
15+
MssqlConnectionPool = mod.ConnectionPool ?? mssql.ConnectionPool
1316
} catch {
1417
throw new Error(
1518
"SQL Server driver not installed. Run: npm install mssql",
@@ -103,7 +106,14 @@ export async function connect(config: ConnectionConfig): Promise<Connector> {
103106
mssqlConfig.password = config.password
104107
}
105108

106-
pool = await mssql.connect(mssqlConfig)
109+
// Use an explicit ConnectionPool (not the global mssql.connect()) so
110+
// multiple simultaneous connections to different servers are isolated.
111+
if (MssqlConnectionPool) {
112+
pool = new MssqlConnectionPool(mssqlConfig)
113+
await pool.connect()
114+
} else {
115+
pool = await mssql.connect(mssqlConfig)
116+
}
107117
},
108118

109119
async execute(sql: string, limit?: number, _binds?: any[], options?: ExecuteOptions): Promise<ConnectorResult> {
@@ -126,22 +136,38 @@ export async function connect(config: ConnectionConfig): Promise<Connector> {
126136
}
127137

128138
const result = await pool.request().query(query)
129-
const rows = result.recordset ?? []
139+
const recordset = result.recordset ?? []
140+
const truncated = effectiveLimit > 0 && recordset.length > effectiveLimit
141+
const limitedRecordset = truncated ? recordset.slice(0, effectiveLimit) : recordset
142+
143+
// mssql merges unnamed columns (e.g. SELECT COUNT(*), SUM(...)) into a
144+
// single array under the empty-string key: row[""] = [val1, val2, ...].
145+
// Flatten these arrays to restore positional column values.
146+
const flattenRow = (row: any): any[] => {
147+
const vals: any[] = []
148+
for (const v of Object.values(row)) {
149+
if (Array.isArray(v)) vals.push(...v)
150+
else vals.push(v)
151+
}
152+
return vals
153+
}
154+
155+
const rows = limitedRecordset.map(flattenRow)
156+
const sampleFlat = rows.length > 0 ? rows[0] : []
157+
const namedKeys = recordset.length > 0 ? Object.keys(recordset[0]) : []
130158
const columns =
131-
rows.length > 0
132-
? Object.keys(rows[0])
133-
: (result.recordset?.columns
134-
? Object.keys(result.recordset.columns)
135-
: [])
136-
const truncated = effectiveLimit > 0 && rows.length > effectiveLimit
137-
const limitedRows = truncated ? rows.slice(0, effectiveLimit) : rows
159+
namedKeys.length === sampleFlat.length
160+
? namedKeys
161+
: sampleFlat.length > 0
162+
? sampleFlat.map((_: any, i: number) => `col_${i}`)
163+
: (result.recordset?.columns
164+
? Object.keys(result.recordset.columns)
165+
: [])
138166

139167
return {
140168
columns,
141-
rows: limitedRows.map((row: any) =>
142-
columns.map((col) => row[col]),
143-
),
144-
row_count: limitedRows.length,
169+
rows,
170+
row_count: rows.length,
145171
truncated,
146172
}
147173
},

packages/drivers/test/sqlserver-unit.test.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,29 @@ function createMockRequest() {
3939
return req
4040
}
4141

42+
function createMockPool(config: any) {
43+
mockConnectCalls.push(config)
44+
return {
45+
connect: async () => {},
46+
request: () => createMockRequest(),
47+
close: async () => {
48+
mockCloseCalls++
49+
},
50+
}
51+
}
52+
4253
mock.module("mssql", () => ({
4354
default: {
44-
connect: async (config: any) => {
45-
mockConnectCalls.push(config)
46-
return {
47-
request: () => createMockRequest(),
48-
close: async () => {
49-
mockCloseCalls++
50-
},
51-
}
52-
},
55+
connect: async (config: any) => createMockPool(config),
56+
},
57+
ConnectionPool: class {
58+
_pool: any
59+
constructor(config: any) {
60+
this._pool = createMockPool(config)
61+
}
62+
async connect() { return this._pool.connect() }
63+
request() { return this._pool.request() }
64+
async close() { return this._pool.close() }
5365
},
5466
}))
5567

0 commit comments

Comments
 (0)