@mantiq/database 0.1.4 → 0.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
+ import type { DatabaseConnection } from '../contracts/Connection.ts'
2
+ import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
1
3
  import type {
2
- MongoDatabaseConnection,
3
4
  MongoCollectionContract,
4
5
  MongoFilter,
5
6
  MongoUpdateDoc,
@@ -10,159 +11,160 @@ import type {
10
11
  MongoDeleteResult,
11
12
  MongoQueryBuilder,
12
13
  } from '../contracts/MongoConnection.ts'
14
+ import type { QueryState, WhereClause } from '../query/Builder.ts'
15
+ import { QueryBuilder } from '../query/Builder.ts'
16
+ import { Expression } from '../query/Expression.ts'
13
17
  import { MongoQueryBuilderImpl } from './MongoQueryBuilderImpl.ts'
14
18
  import { ConnectionError } from '../errors/ConnectionError.ts'
15
- import { QueryError } from '../errors/QueryError.ts'
19
+ import { DriverNotSupportedError } from '../errors/DriverNotSupportedError.ts'
16
20
 
17
21
  export interface MongoConfig {
18
22
  uri: string
19
23
  database: string
20
- options?: Record<string, any>
24
+ options?: Record<string, any> | undefined
21
25
  }
22
26
 
23
- class MongoCollectionImpl implements MongoCollectionContract {
24
- constructor(
25
- private readonly col: any,
26
- private readonly name: string,
27
- ) {}
27
+ // ── Operator translation map ──────────────────────────────────────────────────
28
28
 
29
- find(filter: MongoFilter = {}): MongoQueryBuilder {
30
- return new MongoQueryBuilderImpl(
31
- this.name,
32
- async (opts) => {
33
- let cursor = this.col.find(opts.filter ?? {})
34
- if (opts.projection) cursor = cursor.project(opts.projection)
35
- if (opts.sort) cursor = cursor.sort(opts.sort)
36
- if (opts.skip) cursor = cursor.skip(opts.skip)
37
- if (opts.limit) cursor = cursor.limit(opts.limit)
38
- return cursor.toArray()
39
- },
40
- async (f) => this.col.countDocuments(f),
41
- )
42
- }
29
+ const OPERATOR_MAP: Record<string, string> = {
30
+ '=': '$eq',
31
+ '!=': '$ne',
32
+ '<>': '$ne',
33
+ '>': '$gt',
34
+ '>=': '$gte',
35
+ '<': '$lt',
36
+ '<=': '$lte',
37
+ }
43
38
 
44
- async findOne(filter: MongoFilter = {}): Promise<Record<string, any> | null> {
45
- return this.col.findOne(filter)
46
- }
39
+ // ── MongoConnection implements the universal DatabaseConnection interface ──
47
40
 
48
- async findById(id: any): Promise<Record<string, any> | null> {
49
- const { ObjectId } = await import('mongodb')
50
- return this.col.findOne({ _id: typeof id === 'string' ? new ObjectId(id) : id })
51
- }
41
+ export class MongoConnection implements DatabaseConnection {
42
+ private client: any = null
43
+ private db: any = null
44
+ private config: MongoConfig
52
45
 
53
- async insertOne(doc: Record<string, any>): Promise<MongoInsertResult> {
54
- const result = await this.col.insertOne(doc)
55
- return { insertedId: result.insertedId, acknowledged: result.acknowledged }
46
+ constructor(config: MongoConfig) {
47
+ this.config = config
56
48
  }
57
49
 
58
- async insertMany(docs: Record<string, any>[]): Promise<MongoInsertManyResult> {
59
- const result = await this.col.insertMany(docs)
60
- return {
61
- insertedIds: Object.values(result.insertedIds),
62
- acknowledged: result.acknowledged,
63
- insertedCount: result.insertedCount,
50
+ private async getDb(): Promise<any> {
51
+ if (!this.db) {
52
+ try {
53
+ const mongoModule = 'mongodb'
54
+ const { MongoClient } = await import(/* webpackIgnore: true */ mongoModule)
55
+ this.client = new MongoClient(this.config.uri, this.config.options ?? {})
56
+ await this.client.connect()
57
+ this.db = this.client.db(this.config.database)
58
+ } catch (e: any) {
59
+ throw new ConnectionError(`MongoDB connection failed: ${e.message}`, 'mongodb', e)
60
+ }
64
61
  }
62
+ return this.db
65
63
  }
66
64
 
67
- async updateOne(filter: MongoFilter, update: MongoUpdateDoc): Promise<MongoUpdateResult> {
68
- const result = await this.col.updateOne(filter, update)
69
- return { matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, acknowledged: result.acknowledged }
70
- }
65
+ // ── Universal executeXxx methods ──────────────────────────────────────────
71
66
 
72
- async updateMany(filter: MongoFilter, update: MongoUpdateDoc): Promise<MongoUpdateResult> {
73
- const result = await this.col.updateMany(filter, update)
74
- return { matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, acknowledged: result.acknowledged }
75
- }
67
+ async executeSelect(state: QueryState): Promise<Record<string, any>[]> {
68
+ this.guardNoJoins(state)
69
+ this.guardNoHavings(state)
76
70
 
77
- async replaceOne(filter: MongoFilter, replacement: Record<string, any>): Promise<MongoUpdateResult> {
78
- const result = await this.col.replaceOne(filter, replacement)
79
- return { matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, acknowledged: result.acknowledged }
71
+ const db = await this.getDb()
72
+ const col = db.collection(state.table)
73
+ const filter = this.translateWheres(state.wheres)
74
+ const projection = this.translateColumns(state.columns)
75
+ const sort = this.translateOrders(state.orders)
76
+
77
+ let cursor = col.find(filter)
78
+ if (projection) cursor = cursor.project(projection)
79
+ if (sort) cursor = cursor.sort(sort)
80
+ if (state.offsetValue !== null) cursor = cursor.skip(state.offsetValue)
81
+ if (state.limitValue !== null) cursor = cursor.limit(state.limitValue)
82
+
83
+ return cursor.toArray()
80
84
  }
81
85
 
82
- async upsert(filter: MongoFilter, update: MongoUpdateDoc): Promise<MongoUpdateResult> {
83
- const result = await this.col.updateOne(filter, update, { upsert: true })
84
- return { matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, acknowledged: result.acknowledged }
86
+ async executeInsert(table: string, data: Record<string, any>): Promise<number> {
87
+ const db = await this.getDb()
88
+ const result = await db.collection(table).insertOne(data)
89
+ return result.acknowledged ? 1 : 0
85
90
  }
86
91
 
87
- async deleteOne(filter: MongoFilter): Promise<MongoDeleteResult> {
88
- const result = await this.col.deleteOne(filter)
89
- return { deletedCount: result.deletedCount, acknowledged: result.acknowledged }
92
+ async executeInsertGetId(table: string, data: Record<string, any>): Promise<number | string> {
93
+ const db = await this.getDb()
94
+ const result = await db.collection(table).insertOne(data)
95
+ const id = result.insertedId
96
+ return typeof id === 'object' ? id.toString() : id
90
97
  }
91
98
 
92
- async deleteMany(filter: MongoFilter): Promise<MongoDeleteResult> {
93
- const result = await this.col.deleteMany(filter)
94
- return { deletedCount: result.deletedCount, acknowledged: result.acknowledged }
99
+ async executeUpdate(table: string, state: QueryState, data: Record<string, any>): Promise<number> {
100
+ const db = await this.getDb()
101
+ const filter = this.translateWheres(state.wheres)
102
+ const result = await db.collection(table).updateMany(filter, { $set: data })
103
+ return result.modifiedCount
95
104
  }
96
105
 
97
- async aggregate(pipeline: MongoPipelineStage[]): Promise<Record<string, any>[]> {
98
- return this.col.aggregate(pipeline).toArray()
106
+ async executeDelete(table: string, state: QueryState): Promise<number> {
107
+ const db = await this.getDb()
108
+ const filter = this.translateWheres(state.wheres)
109
+ const result = await db.collection(table).deleteMany(filter)
110
+ return result.deletedCount
99
111
  }
100
112
 
101
- async count(filter: MongoFilter = {}): Promise<number> {
102
- return this.col.countDocuments(filter)
113
+ async executeTruncate(table: string): Promise<void> {
114
+ const db = await this.getDb()
115
+ await db.collection(table).deleteMany({})
103
116
  }
104
117
 
105
- async createIndex(spec: Record<string, any>, options?: Record<string, any>): Promise<string> {
106
- return this.col.createIndex(spec, options)
118
+ async executeAggregate(state: QueryState, fn: 'count' | 'sum' | 'avg' | 'min' | 'max', column: string): Promise<number> {
119
+ const db = await this.getDb()
120
+ const filter = this.translateWheres(state.wheres)
121
+
122
+ if (fn === 'count') {
123
+ return db.collection(state.table).countDocuments(filter)
124
+ }
125
+
126
+ const aggOp = `$${fn}`
127
+ const aggField = column === '*' ? 1 : `$${column}`
128
+ const pipeline: any[] = [
129
+ { $match: filter },
130
+ { $group: { _id: null, result: { [aggOp]: aggField } } },
131
+ ]
132
+ const [row] = await db.collection(state.table).aggregate(pipeline).toArray()
133
+ return Number(row?.result ?? 0)
107
134
  }
108
135
 
109
- async drop(): Promise<boolean> {
110
- return this.col.drop()
136
+ async executeExists(state: QueryState): Promise<boolean> {
137
+ const db = await this.getDb()
138
+ const filter = this.translateWheres(state.wheres)
139
+ const count = await db.collection(state.table).countDocuments(filter, { limit: 1 })
140
+ return count > 0
111
141
  }
112
- }
113
142
 
114
- export class MongoConnection implements MongoDatabaseConnection {
115
- private client: any = null
116
- private db: any = null
117
- private config: MongoConfig
143
+ // ── Raw SQL methods — throw on MongoDB ────────────────────────────────────
118
144
 
119
- constructor(config: MongoConfig) {
120
- this.config = config
145
+ async select(sql: string, bindings?: any[]): Promise<Record<string, any>[]> {
146
+ throw new DriverNotSupportedError('mongodb', 'raw SQL queries')
121
147
  }
122
148
 
123
- private async getDb(): Promise<any> {
124
- if (!this.db) {
125
- try {
126
- const { MongoClient } = await import('mongodb')
127
- this.client = new MongoClient(this.config.uri, this.config.options ?? {})
128
- await this.client.connect()
129
- this.db = this.client.db(this.config.database)
130
- } catch (e: any) {
131
- throw new ConnectionError(`MongoDB connection failed: ${e.message}`, 'mongodb', e)
132
- }
133
- }
134
- return this.db
149
+ async statement(sql: string, bindings?: any[]): Promise<number> {
150
+ throw new DriverNotSupportedError('mongodb', 'raw SQL queries')
135
151
  }
136
152
 
137
- collection(name: string): MongoCollectionContract {
138
- // Lazily get collection actual DB ops will connect
139
- const self = this
140
- const col = {
141
- async getCol() {
142
- const db = await self.getDb()
143
- return db.collection(name)
144
- },
145
- }
146
-
147
- // We need to return a proxy that defers the actual collection resolution
148
- return new LazyMongoCollection(name, async () => {
149
- const db = await self.getDb()
150
- return db.collection(name)
151
- })
153
+ async insertGetId(sql: string, bindings?: any[]): Promise<number | bigint | string> {
154
+ throw new DriverNotSupportedError('mongodb', 'raw SQL queries')
152
155
  }
153
156
 
154
- async command(command: Record<string, any>): Promise<any> {
155
- const db = await this.getDb()
156
- return db.command(command)
157
+ // ── Shared interface ──────────────────────────────────────────────────────
158
+
159
+ table(name: string): QueryBuilder {
160
+ return new QueryBuilder(this, name)
157
161
  }
158
162
 
159
- async listCollections(): Promise<string[]> {
160
- const db = await this.getDb()
161
- const collections = await db.listCollections().toArray()
162
- return collections.map((c: any) => c.name)
163
+ schema(): SchemaBuilder {
164
+ throw new DriverNotSupportedError('mongodb', 'schema builder (MongoDB is schemaless — use native() for indexes)')
163
165
  }
164
166
 
165
- async transaction<T>(callback: (conn: MongoDatabaseConnection) => Promise<T>): Promise<T> {
167
+ async transaction<T>(callback: (conn: DatabaseConnection) => Promise<T>): Promise<T> {
166
168
  const client = await this.getClient()
167
169
  const session = client.startSession()
168
170
  try {
@@ -179,22 +181,194 @@ export class MongoConnection implements MongoDatabaseConnection {
179
181
  }
180
182
  }
181
183
 
182
- private async getClient(): Promise<any> {
183
- await this.getDb()
184
- return this.client
185
- }
186
-
187
184
  getDriverName(): string {
188
185
  return 'mongodb'
189
186
  }
190
187
 
188
+ getTablePrefix(): string {
189
+ return ''
190
+ }
191
+
192
+ // ── Native escape hatch — direct MongoDB access ───────────────────────────
193
+
194
+ /** Returns the native MongoDB collection for advanced operations. */
195
+ collection(name: string): MongoCollectionContract {
196
+ return new LazyMongoCollection(name, async () => {
197
+ const db = await this.getDb()
198
+ return db.collection(name)
199
+ })
200
+ }
201
+
202
+ /** Returns the underlying MongoDB Db instance. */
203
+ async native(): Promise<any> {
204
+ return this.getDb()
205
+ }
206
+
207
+ async command(command: Record<string, any>): Promise<any> {
208
+ const db = await this.getDb()
209
+ return db.command(command)
210
+ }
211
+
212
+ async listCollections(): Promise<string[]> {
213
+ const db = await this.getDb()
214
+ const collections = await db.listCollections().toArray()
215
+ return collections.map((c: any) => c.name)
216
+ }
217
+
191
218
  async disconnect(): Promise<void> {
192
219
  await this.client?.close()
193
220
  this.client = null
194
221
  this.db = null
195
222
  }
223
+
224
+ private async getClient(): Promise<any> {
225
+ await this.getDb()
226
+ return this.client
227
+ }
228
+
229
+ // ── QueryState → MongoDB translation ──────────────────────────────────────
230
+
231
+ private translateWheres(wheres: WhereClause[]): Record<string, any> {
232
+ if (wheres.length === 0) return {}
233
+
234
+ const andClauses: Record<string, any>[] = []
235
+ const orGroups: Record<string, any>[][] = []
236
+ let currentAnd: Record<string, any>[] = []
237
+
238
+ for (const w of wheres) {
239
+ const clause = this.translateSingleWhere(w)
240
+
241
+ if (w.boolean === 'or' && currentAnd.length > 0) {
242
+ // Push accumulated AND clauses as one OR branch
243
+ orGroups.push(currentAnd)
244
+ currentAnd = [clause]
245
+ } else {
246
+ currentAnd.push(clause)
247
+ }
248
+ }
249
+
250
+ // Final group
251
+ if (orGroups.length > 0) {
252
+ orGroups.push(currentAnd)
253
+ return { $or: orGroups.map((group) => group.length === 1 ? group[0]! : { $and: group }) }
254
+ }
255
+
256
+ if (currentAnd.length === 1) return currentAnd[0]!
257
+ return { $and: currentAnd }
258
+ }
259
+
260
+ private translateSingleWhere(w: WhereClause): Record<string, any> {
261
+ switch (w.type) {
262
+ case 'basic': {
263
+ const col = w.column!
264
+ const op = w.operator ?? '='
265
+ const val = w.value
266
+
267
+ if (op === '=' || op === '$eq') {
268
+ return { [col]: val }
269
+ }
270
+
271
+ if (op === 'like' || op === 'LIKE') {
272
+ return { [col]: { $regex: this.likeToRegex(val), $options: 'i' } }
273
+ }
274
+
275
+ if (op === 'not like' || op === 'NOT LIKE') {
276
+ return { [col]: { $not: { $regex: this.likeToRegex(val), $options: 'i' } } }
277
+ }
278
+
279
+ const mongoOp = OPERATOR_MAP[op]
280
+ if (mongoOp) {
281
+ return { [col]: { [mongoOp]: val } }
282
+ }
283
+
284
+ throw new DriverNotSupportedError('mongodb', `operator "${op}"`)
285
+ }
286
+
287
+ case 'in':
288
+ return { [w.column!]: { $in: w.values! } }
289
+
290
+ case 'notIn':
291
+ return { [w.column!]: { $nin: w.values! } }
292
+
293
+ case 'null':
294
+ return { [w.column!]: null }
295
+
296
+ case 'notNull':
297
+ return { [w.column!]: { $ne: null } }
298
+
299
+ case 'between':
300
+ return { [w.column!]: { $gte: w.range![0], $lte: w.range![1] } }
301
+
302
+ case 'nested':
303
+ return this.translateWheres(w.nested ?? [])
304
+
305
+ case 'raw':
306
+ throw new DriverNotSupportedError('mongodb', 'whereRaw (use standard where methods instead)')
307
+
308
+ case 'column':
309
+ throw new DriverNotSupportedError('mongodb', 'whereColumn (use $expr in native queries instead)')
310
+
311
+ default:
312
+ throw new DriverNotSupportedError('mongodb', `where type "${w.type}"`)
313
+ }
314
+ }
315
+
316
+ private translateColumns(columns: (string | Expression)[]): Record<string, 1> | undefined {
317
+ if (columns.length === 1 && columns[0] === '*') return undefined
318
+ if (columns.some((c) => c instanceof Expression)) {
319
+ // Allow expressions only if they're simple strings (column names)
320
+ // For actual SQL expressions, throw
321
+ const hasRealExpressions = columns.some((c) => c instanceof Expression && (c.value.includes('(') || c.value.includes(' ')))
322
+ if (hasRealExpressions) {
323
+ throw new DriverNotSupportedError('mongodb', 'selectRaw with SQL expressions')
324
+ }
325
+ }
326
+
327
+ const projection: Record<string, 1> = {}
328
+ for (const col of columns) {
329
+ const name = col instanceof Expression ? col.value : col
330
+ projection[name] = 1
331
+ }
332
+ return projection
333
+ }
334
+
335
+ private translateOrders(orders: Array<{ column: string | Expression; direction: 'asc' | 'desc' }>): Record<string, 1 | -1> | undefined {
336
+ if (orders.length === 0) return undefined
337
+ const sort: Record<string, 1 | -1> = {}
338
+ for (const o of orders) {
339
+ if (o.column instanceof Expression) {
340
+ throw new DriverNotSupportedError('mongodb', 'orderBy with raw Expression')
341
+ }
342
+ sort[o.column] = o.direction === 'asc' ? 1 : -1
343
+ }
344
+ return sort
345
+ }
346
+
347
+ /** Converts SQL LIKE pattern to regex: % → .*, _ → . */
348
+ private likeToRegex(pattern: string): string {
349
+ return pattern
350
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
351
+ .replace(/%/g, '.*')
352
+ .replace(/_/g, '.')
353
+ }
354
+
355
+ // ── Guards ────────────────────────────────────────────────────────────────
356
+
357
+ private guardNoJoins(state: QueryState): void {
358
+ if (state.joins.length > 0) {
359
+ throw new DriverNotSupportedError('mongodb', 'joins (use relationships or native $lookup instead)')
360
+ }
361
+ }
362
+
363
+ private guardNoHavings(state: QueryState): void {
364
+ if (state.havings.length > 0) {
365
+ throw new DriverNotSupportedError('mongodb', 'having (use native aggregation pipeline instead)')
366
+ }
367
+ }
196
368
  }
197
369
 
370
+ // ── LazyMongoCollection — deferred collection resolution ────────────────────
371
+
198
372
  class LazyMongoCollection implements MongoCollectionContract {
199
373
  private _col: any = null
200
374
 
@@ -232,7 +406,8 @@ class LazyMongoCollection implements MongoCollectionContract {
232
406
  }
233
407
 
234
408
  async findById(id: any): Promise<Record<string, any> | null> {
235
- const { ObjectId } = await import('mongodb')
409
+ const mongoModule = 'mongodb'
410
+ const { ObjectId } = await import(/* webpackIgnore: true */ mongoModule)
236
411
  return (await this.col()).findOne({ _id: typeof id === 'string' ? new ObjectId(id) : id })
237
412
  }
238
413
 
@@ -17,10 +17,10 @@ export class MongoQueryBuilderImpl implements MongoQueryBuilder {
17
17
  private readonly collectionName: string,
18
18
  private readonly executor: (opts: {
19
19
  filter: MongoFilter
20
- projection?: MongoProjection
21
- sort?: MongoSortDoc
22
- limit?: number
23
- skip?: number
20
+ projection?: MongoProjection | undefined
21
+ sort?: MongoSortDoc | undefined
22
+ limit?: number | undefined
23
+ skip?: number | undefined
24
24
  }) => Promise<Record<string, any>[]>,
25
25
  private readonly counter: (filter: MongoFilter) => Promise<number>,
26
26
  ) {}
@@ -1,5 +1,6 @@
1
1
  import type { DatabaseConnection } from '../contracts/Connection.ts'
2
2
  import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
3
+ import { BaseSQLConnection } from './BaseSQLConnection.ts'
3
4
  import { QueryBuilder } from '../query/Builder.ts'
4
5
  import { MySQLGrammar } from './MySQLGrammar.ts'
5
6
  import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
@@ -7,27 +8,28 @@ import { ConnectionError } from '../errors/ConnectionError.ts'
7
8
  import { QueryError } from '../errors/QueryError.ts'
8
9
 
9
10
  export interface MySQLConfig {
10
- host?: string
11
- port?: number
11
+ host?: string | undefined
12
+ port?: number | undefined
12
13
  database: string
13
- user?: string
14
- password?: string
15
- pool?: { min?: number; max?: number }
14
+ user?: string | undefined
15
+ password?: string | undefined
16
+ pool?: { min?: number | undefined; max?: number | undefined } | undefined
16
17
  }
17
18
 
18
- export class MySQLConnection implements DatabaseConnection {
19
+ export class MySQLConnection extends BaseSQLConnection {
19
20
  readonly _grammar = new MySQLGrammar()
20
21
  private pool: any = null
21
22
  private config: MySQLConfig
22
23
 
23
24
  constructor(config: MySQLConfig) {
25
+ super()
24
26
  this.config = config
25
27
  }
26
28
 
27
29
  private async getPool(): Promise<any> {
28
30
  if (!this.pool) {
29
31
  try {
30
- const mysql = await import('mysql2/promise')
32
+ const mysql: any = await import('mysql2/promise')
31
33
  this.pool = await mysql.createPool({
32
34
  host: this.config.host ?? 'localhost',
33
35
  port: this.config.port ?? 3306,
@@ -79,18 +81,18 @@ export class MySQLConnection implements DatabaseConnection {
79
81
  const conn = await pool.getConnection()
80
82
  try {
81
83
  await conn.beginTransaction()
82
- const txConn: DatabaseConnection = {
83
- select: async (sql, b) => { const [rows] = await conn.query(sql, b); return rows as Record<string, any>[] },
84
- statement: async (sql, b) => { const [r] = await conn.query(sql, b); return (r as any).affectedRows ?? 0 },
85
- insertGetId: async (sql, b) => { const [r] = await conn.query(sql, b); return (r as any).insertId ?? 0 },
86
- transaction: (cb) => cb(txConn),
87
- table: (name) => new QueryBuilder(txConn, name),
84
+ const txConn: any = {
85
+ _grammar: this._grammar,
86
+ select: async (sql: string, b?: any[]) => { const [rows] = await conn.query(sql, b); return rows as Record<string, any>[] },
87
+ statement: async (sql: string, b?: any[]) => { const [r] = await conn.query(sql, b); return (r as any).affectedRows ?? 0 },
88
+ insertGetId: async (sql: string, b?: any[]) => { const [r] = await conn.query(sql, b); return (r as any).insertId ?? 0 },
89
+ transaction: (cb: any) => cb(txConn),
90
+ table: (name: string) => new QueryBuilder(txConn, name),
88
91
  schema: () => new SchemaBuilderImpl(txConn),
89
92
  getDriverName: () => 'mysql',
90
93
  getTablePrefix: () => '',
91
94
  }
92
- // @ts-ignore — attach grammar
93
- txConn._grammar = this._grammar
95
+ this.applyExecuteMethods(txConn)
94
96
  const result = await callback(txConn)
95
97
  await conn.commit()
96
98
  return result
@@ -102,10 +104,6 @@ export class MySQLConnection implements DatabaseConnection {
102
104
  }
103
105
  }
104
106
 
105
- table(name: string): QueryBuilder {
106
- return new QueryBuilder(this, name)
107
- }
108
-
109
107
  schema(): SchemaBuilder {
110
108
  return new SchemaBuilderImpl(this)
111
109
  }
@@ -113,8 +111,4 @@ export class MySQLConnection implements DatabaseConnection {
113
111
  getDriverName(): string {
114
112
  return 'mysql'
115
113
  }
116
-
117
- getTablePrefix(): string {
118
- return ''
119
- }
120
114
  }
@@ -1,5 +1,6 @@
1
1
  import type { DatabaseConnection } from '../contracts/Connection.ts'
2
2
  import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
3
+ import { BaseSQLConnection } from './BaseSQLConnection.ts'
3
4
  import { QueryBuilder } from '../query/Builder.ts'
4
5
  import { PostgresGrammar } from './PostgresGrammar.ts'
5
6
  import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
@@ -7,21 +8,22 @@ import { ConnectionError } from '../errors/ConnectionError.ts'
7
8
  import { QueryError } from '../errors/QueryError.ts'
8
9
 
9
10
  export interface PostgresConfig {
10
- host?: string
11
- port?: number
11
+ host?: string | undefined
12
+ port?: number | undefined
12
13
  database: string
13
- user?: string
14
- password?: string
15
- ssl?: boolean
16
- pool?: { min?: number; max?: number }
14
+ user?: string | undefined
15
+ password?: string | undefined
16
+ ssl?: boolean | undefined
17
+ pool?: { min?: number | undefined; max?: number | undefined } | undefined
17
18
  }
18
19
 
19
- export class PostgresConnection implements DatabaseConnection {
20
+ export class PostgresConnection extends BaseSQLConnection {
20
21
  readonly _grammar = new PostgresGrammar()
21
22
  private client: any = null
22
23
  private config: PostgresConfig
23
24
 
24
25
  constructor(config: PostgresConfig) {
26
+ super()
25
27
  this.config = config
26
28
  }
27
29
 
@@ -83,19 +85,18 @@ export class PostgresConnection implements DatabaseConnection {
83
85
  const client = await pool.connect()
84
86
  try {
85
87
  await client.query('BEGIN')
86
- // Create a transactional connection wrapper
87
- const txConn: DatabaseConnection = {
88
- select: async (sql, b) => { const r = await client.query(sql, b); return r.rows },
89
- statement: async (sql, b) => { const r = await client.query(sql, b); return r.rowCount ?? 0 },
90
- insertGetId: async (sql, b) => { const r = await client.query(sql, b); return r.rows[0]?.id ?? 0 },
91
- transaction: (cb) => cb(txConn),
92
- table: (name) => new QueryBuilder(txConn, name),
88
+ const txConn: any = {
89
+ _grammar: this._grammar,
90
+ select: async (sql: string, b?: any[]) => { const r = await client.query(sql, b); return r.rows },
91
+ statement: async (sql: string, b?: any[]) => { const r = await client.query(sql, b); return r.rowCount ?? 0 },
92
+ insertGetId: async (sql: string, b?: any[]) => { const r = await client.query(sql, b); return r.rows[0]?.id ?? 0 },
93
+ transaction: (cb: any) => cb(txConn),
94
+ table: (name: string) => new QueryBuilder(txConn, name),
93
95
  schema: () => new SchemaBuilderImpl(txConn),
94
96
  getDriverName: () => 'postgres',
95
97
  getTablePrefix: () => '',
96
98
  }
97
- // @ts-ignore — attach grammar for the builder
98
- txConn._grammar = this._grammar
99
+ this.applyExecuteMethods(txConn)
99
100
  const result = await callback(txConn)
100
101
  await client.query('COMMIT')
101
102
  return result
@@ -107,10 +108,6 @@ export class PostgresConnection implements DatabaseConnection {
107
108
  }
108
109
  }
109
110
 
110
- table(name: string): QueryBuilder {
111
- return new QueryBuilder(this, name)
112
- }
113
-
114
111
  schema(): SchemaBuilder {
115
112
  return new SchemaBuilderImpl(this)
116
113
  }
@@ -118,8 +115,4 @@ export class PostgresConnection implements DatabaseConnection {
118
115
  getDriverName(): string {
119
116
  return 'postgres'
120
117
  }
121
-
122
- getTablePrefix(): string {
123
- return ''
124
- }
125
118
  }