-
Notifications
You must be signed in to change notification settings - Fork 89
Expand file tree
/
Copy pathsqlserver.ts
More file actions
152 lines (139 loc) · 4.42 KB
/
Copy pathsqlserver.ts
File metadata and controls
152 lines (139 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* SQL Server driver using the `mssql` (tedious) package.
*/
import type { ConnectionConfig, Connector, ConnectorResult, ExecuteOptions, SchemaColumn } from "./types"
export async function connect(config: ConnectionConfig): Promise<Connector> {
let mssql: any
try {
// @ts-expect-error — mssql has no type declarations; installed as optional peerDependency
mssql = await import("mssql")
mssql = mssql.default || mssql
} catch {
throw new Error(
"SQL Server driver not installed. Run: npm install mssql",
)
}
let pool: any
return {
async connect() {
const mssqlConfig: Record<string, unknown> = {
server: config.host ?? "127.0.0.1",
port: config.port ?? 1433,
database: config.database,
user: config.user,
password: config.password,
options: {
encrypt: config.encrypt ?? false,
trustServerCertificate: config.trust_server_certificate ?? true,
connectTimeout: 10000,
requestTimeout: 30000,
},
pool: {
max: 5,
min: 0,
idleTimeoutMillis: 30000,
},
}
pool = await mssql.connect(mssqlConfig)
},
async execute(sql: string, limit?: number, _binds?: any[], options?: ExecuteOptions): Promise<ConnectorResult> {
const effectiveLimit = options?.noLimit ? 0 : (limit ?? 1000)
let query = sql
const isSelectLike = /^\s*SELECT\b/i.test(sql)
// SQL Server uses TOP, not LIMIT
if (
isSelectLike &&
effectiveLimit &&
!/\bTOP\b/i.test(sql) &&
!/\bLIMIT\b/i.test(sql)
) {
// Insert TOP after SELECT
query = sql.replace(
/^(\s*SELECT\s)/i,
`$1TOP ${effectiveLimit + 1} `,
)
}
const result = await pool.request().query(query)
const rows = result.recordset ?? []
const columns =
rows.length > 0
? Object.keys(rows[0]).filter((k) => !k.startsWith("_"))
: (result.recordset?.columns
? Object.keys(result.recordset.columns)
: [])
const truncated = effectiveLimit > 0 && rows.length > effectiveLimit
const limitedRows = truncated ? rows.slice(0, effectiveLimit) : rows
return {
columns,
rows: limitedRows.map((row: any) =>
columns.map((col) => row[col]),
),
row_count: limitedRows.length,
truncated,
}
},
async listSchemas(): Promise<string[]> {
const result = await pool
.request()
.query(
"SELECT name FROM sys.schemas WHERE name NOT IN ('guest','INFORMATION_SCHEMA','sys') ORDER BY name",
)
return result.recordset.map((r: any) => r.name as string)
},
async listTables(
schema: string,
): Promise<Array<{ name: string; type: string }>> {
const result = await pool
.request()
.input("schema", schema)
.query(
`SELECT t.name, t.type
FROM sys.tables t
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name = @schema
UNION ALL
SELECT v.name, 'V' as type
FROM sys.views v
INNER JOIN sys.schemas s ON v.schema_id = s.schema_id
WHERE s.name = @schema
ORDER BY name`,
)
return result.recordset.map((r: any) => ({
name: r.name as string,
type: r.type?.trim() === "V" ? "view" : "table",
}))
},
async describeTable(
schema: string,
table: string,
): Promise<SchemaColumn[]> {
const result = await pool
.request()
.input("schema", schema)
.input("table", table)
.query(
`SELECT c.name AS column_name,
tp.name AS data_type,
c.is_nullable
FROM sys.columns c
INNER JOIN sys.types tp ON c.user_type_id = tp.user_type_id
INNER JOIN sys.objects o ON c.object_id = o.object_id
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE s.name = @schema AND o.name = @table
AND o.type IN ('U', 'V')
ORDER BY c.column_id`,
)
return result.recordset.map((r: any) => ({
name: r.column_name as string,
data_type: r.data_type as string,
nullable: r.is_nullable === 1,
}))
},
async close() {
if (pool) {
await pool.close()
pool = null
}
},
}
}