-
Notifications
You must be signed in to change notification settings - Fork 91
Expand file tree
/
Copy pathsqlite.ts
More file actions
116 lines (103 loc) · 3.4 KB
/
Copy pathsqlite.ts
File metadata and controls
116 lines (103 loc) · 3.4 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
/**
* SQLite driver using Bun's built-in `bun:sqlite`.
* Synchronous API wrapped in async interface.
*/
import { Database } from "bun:sqlite"
import type { ConnectionConfig, Connector, ConnectorResult, ExecuteOptions, SchemaColumn } from "./types"
export async function connect(config: ConnectionConfig): Promise<Connector> {
const dbPath = (config.path as string) ?? ":memory:"
let db: Database | null = null
return {
async connect() {
const isReadonly = config.readonly === true
db = new Database(dbPath, {
readonly: isReadonly,
create: !isReadonly,
})
if (!isReadonly) {
db.exec("PRAGMA journal_mode = WAL")
}
},
async execute(sql: string, limit?: number, _binds?: any[], options?: ExecuteOptions): Promise<ConnectorResult> {
if (!db) throw new Error("SQLite connection not open")
const effectiveLimit = options?.noLimit ? 0 : (limit ?? 1000)
// Determine if this is a SELECT-like statement
const trimmed = sql.trim().toLowerCase()
const isPragma = trimmed.startsWith("pragma")
const isSelect =
trimmed.startsWith("select") ||
isPragma ||
trimmed.startsWith("with") ||
trimmed.startsWith("explain")
// PRAGMA statements don't support LIMIT clause
let query = sql
if (
isSelect &&
!isPragma &&
effectiveLimit &&
!/\bLIMIT\b/i.test(sql)
) {
query = `${sql.replace(/;\s*$/, "")} LIMIT ${effectiveLimit + 1}`
}
if (!isSelect) {
// Non-SELECT statements (INSERT, UPDATE, DELETE, CREATE, etc.)
const info = db.prepare(sql).run()
return {
columns: ["changes", "lastInsertRowid"],
rows: [[info.changes, info.lastInsertRowid]],
row_count: 1,
truncated: false,
}
}
const stmt = db.prepare(query)
const rows = stmt.all() as any[]
const columns = rows.length > 0 ? Object.keys(rows[0]) : []
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[]> {
// SQLite doesn't have schemas, return "main"
return ["main"]
},
async listTables(
_schema: string,
): Promise<Array<{ name: string; type: string }>> {
if (!db) throw new Error("SQLite connection not open")
const rows = db
.prepare(
"SELECT name, type FROM sqlite_master WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' ORDER BY name",
)
.all() as any[]
return rows.map((r: any) => ({
name: r.name as string,
type: r.type as string,
}))
},
async describeTable(
_schema: string,
table: string,
): Promise<SchemaColumn[]> {
if (!db) throw new Error("SQLite connection not open")
const rows = db.prepare('SELECT * FROM pragma_table_info(?) ORDER BY cid').all(table) as any[]
return rows.map((r: any) => ({
name: r.name as string,
data_type: r.type as string,
nullable: r.notnull === 0,
}))
},
async close() {
if (db) {
db.close()
db = null
}
},
}
}