feat: add postgres driver
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const pgState = vi.hoisted(() => {
|
||||
const state = {
|
||||
pools: [] as FakePool[],
|
||||
nextResult: undefined as unknown
|
||||
}
|
||||
|
||||
class FakePool {
|
||||
options: Record<string, unknown>
|
||||
queries: unknown[] = []
|
||||
ended = false
|
||||
|
||||
constructor(options: Record<string, unknown>) {
|
||||
this.options = options
|
||||
state.pools.push(this)
|
||||
}
|
||||
|
||||
async query(args: unknown) {
|
||||
this.queries.push(args)
|
||||
return state.nextResult ?? { rows: [], fields: [], rowCount: 0, command: 'SELECT' }
|
||||
}
|
||||
|
||||
async end() {
|
||||
this.ended = true
|
||||
}
|
||||
}
|
||||
|
||||
return { state, FakePool }
|
||||
})
|
||||
|
||||
vi.mock('pg', () => ({
|
||||
default: { Pool: pgState.FakePool }
|
||||
}))
|
||||
|
||||
import type { ConnectionConfig } from '../../../src/config/types.js'
|
||||
import { createPostgresDriver } from '../../../src/db/postgres.js'
|
||||
|
||||
const config = (extra: Partial<ConnectionConfig> = {}): ConnectionConfig => ({
|
||||
name: 'pg',
|
||||
type: 'postgres',
|
||||
host: 'real-host',
|
||||
user: 'postgres',
|
||||
password: 'pw',
|
||||
database: 'main',
|
||||
readonly: false,
|
||||
...extra
|
||||
})
|
||||
|
||||
const target = (extra: Partial<ConnectionConfig> = {}) => ({
|
||||
config: config(extra),
|
||||
host: '127.0.0.1',
|
||||
port: 15432
|
||||
})
|
||||
|
||||
describe('createPostgresDriver', () => {
|
||||
beforeEach(() => {
|
||||
pgState.state.pools.length = 0
|
||||
pgState.state.nextResult = undefined
|
||||
})
|
||||
|
||||
it('creates one pool per database and reuses it', async () => {
|
||||
const driver = createPostgresDriver(target())
|
||||
await driver.query({ sql: 'select 1', rowLimit: 10 })
|
||||
await driver.query({ sql: 'select 2', rowLimit: 10 })
|
||||
await driver.query({ sql: 'select 3', database: 'other', rowLimit: 10 })
|
||||
expect(pgState.state.pools).toHaveLength(2)
|
||||
expect(pgState.state.pools[0].options).toMatchObject({
|
||||
host: '127.0.0.1',
|
||||
port: 15432,
|
||||
database: 'main',
|
||||
max: 4
|
||||
})
|
||||
expect(pgState.state.pools[1].options).toMatchObject({ database: 'other' })
|
||||
})
|
||||
|
||||
it('passes readonly as a startup option', async () => {
|
||||
const driver = createPostgresDriver(target({ readonly: true }))
|
||||
await driver.query({ sql: 'select 1', rowLimit: 10 })
|
||||
expect(pgState.state.pools[0].options.options).toBe('-c default_transaction_read_only=on')
|
||||
})
|
||||
|
||||
it('omits startup options when not readonly', async () => {
|
||||
const driver = createPostgresDriver(target())
|
||||
await driver.query({ sql: 'select 1', rowLimit: 10 })
|
||||
expect(pgState.state.pools[0].options.options).toBeUndefined()
|
||||
})
|
||||
|
||||
it('maps array-mode results with column names and normalized cells', async () => {
|
||||
pgState.state.nextResult = {
|
||||
rows: [[1n, new Date('2026-01-01T00:00:00Z')]],
|
||||
fields: [{ name: 'id' }, { name: 'created' }],
|
||||
rowCount: 1,
|
||||
command: 'SELECT'
|
||||
}
|
||||
const driver = createPostgresDriver(target())
|
||||
const result = await driver.query({ sql: 'select * from t', rowLimit: 10 })
|
||||
expect(result).toEqual({
|
||||
columns: ['id', 'created'],
|
||||
rows: [['1', '2026-01-01T00:00:00.000Z']],
|
||||
rowCount: 1,
|
||||
truncated: false
|
||||
})
|
||||
})
|
||||
|
||||
it('truncates rows beyond the limit', async () => {
|
||||
pgState.state.nextResult = {
|
||||
rows: [[1], [2], [3]],
|
||||
fields: [{ name: 'n' }],
|
||||
rowCount: 3,
|
||||
command: 'SELECT'
|
||||
}
|
||||
const driver = createPostgresDriver(target())
|
||||
const result = await driver.query({ sql: 'select n', rowLimit: 2 })
|
||||
expect(result.rows).toEqual([[1], [2]])
|
||||
expect(result.truncated).toBe(true)
|
||||
expect(result.rowCount).toBe(3)
|
||||
})
|
||||
|
||||
it('sends positional params through', async () => {
|
||||
const driver = createPostgresDriver(target())
|
||||
await driver.query({ sql: 'select $1', params: [42], rowLimit: 10 })
|
||||
expect(pgState.state.pools[0].queries[0]).toMatchObject({
|
||||
text: 'select $1',
|
||||
values: [42],
|
||||
rowMode: 'array'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects multi-statement results', async () => {
|
||||
pgState.state.nextResult = [
|
||||
{ rows: [], fields: [], rowCount: 0 },
|
||||
{ rows: [], fields: [], rowCount: 0 }
|
||||
]
|
||||
const driver = createPostgresDriver(target())
|
||||
await expect(driver.query({ sql: 'select 1; select 2', rowLimit: 10 })).rejects.toThrow(
|
||||
/one SQL statement/
|
||||
)
|
||||
})
|
||||
|
||||
it('lists databases against the maintenance db and maps sizes', async () => {
|
||||
pgState.state.nextResult = {
|
||||
rows: [
|
||||
['app', 1024],
|
||||
['postgres', 2048]
|
||||
],
|
||||
fields: [{ name: 'name' }, { name: 'size_bytes' }],
|
||||
rowCount: 2,
|
||||
command: 'SELECT'
|
||||
}
|
||||
const driver = createPostgresDriver(target({ database: undefined }))
|
||||
const databases = await driver.listDatabases()
|
||||
expect(databases).toEqual([
|
||||
{ name: 'app', sizeBytes: 1024 },
|
||||
{ name: 'postgres', sizeBytes: 2048 }
|
||||
])
|
||||
expect(pgState.state.pools[0].options.database).toBe('postgres')
|
||||
})
|
||||
|
||||
it('maps reltuples=-1 to null in listTables', async () => {
|
||||
pgState.state.nextResult = {
|
||||
rows: [
|
||||
['public', 'fresh', '-1'],
|
||||
['public', 'analyzed', '42']
|
||||
],
|
||||
fields: [{ name: 'schema' }, { name: 'name' }, { name: 'row_estimate' }],
|
||||
rowCount: 2,
|
||||
command: 'SELECT'
|
||||
}
|
||||
const driver = createPostgresDriver(target())
|
||||
const tables = await driver.listTables({})
|
||||
expect(tables).toEqual([
|
||||
{ schema: 'public', name: 'fresh', rowEstimate: null },
|
||||
{ schema: 'public', name: 'analyzed', rowEstimate: 42 }
|
||||
])
|
||||
})
|
||||
|
||||
it('dispose ends every pool', async () => {
|
||||
const driver = createPostgresDriver(target())
|
||||
await driver.query({ sql: 'select 1', rowLimit: 1 })
|
||||
await driver.query({ sql: 'select 1', database: 'other', rowLimit: 1 })
|
||||
await driver.dispose()
|
||||
expect(pgState.state.pools.every((pool) => pool.ended)).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user