fix: sql guard gaps, date tz, timeouts, payload

This commit is contained in:
smartass
2026-06-12 01:37:15 +05:00
parent 783e5bc5b9
commit 1fecb1cce4
14 changed files with 237 additions and 10 deletions
+12
View File
@@ -126,6 +126,18 @@ describe('createMysqlDriver', () => {
})
})
it('sets a connect timeout on every pool', async () => {
const driver = createMysqlDriver(target())
await driver.query({ sql: 'select 1', rowLimit: 10 })
expect(mysqlState.state.pools[0].options.connectTimeout).toBe(10_000)
})
it('returns DATE and DATETIME as strings to avoid tz day-shift', async () => {
const driver = createMysqlDriver(target())
await driver.query({ sql: 'select 1', rowLimit: 10 })
expect(mysqlState.state.pools[0].options.dateStrings).toEqual(['DATE', 'DATETIME'])
})
it('reuses pools per database', async () => {
const driver = createMysqlDriver(target())
await driver.query({ sql: 'select 1', rowLimit: 10 })
+29 -3
View File
@@ -41,9 +41,14 @@ const pgState = vi.hoisted(() => {
return { state, FakePool }
})
vi.mock('pg', () => ({
default: { Pool: pgState.FakePool }
}))
vi.mock('pg', async () => {
// Use the real TypeOverrides so the driver's date/timestamp parser overrides
// are exercised against pg's genuine default parsers (e.g. timestamptz).
const actual = (await vi.importActual('pg')) as { default: { TypeOverrides: unknown } }
return {
default: { Pool: pgState.FakePool, TypeOverrides: actual.default.TypeOverrides }
}
})
import type { ConnectionConfig } from '../../../src/config/types.js'
import { createPostgresDriver } from '../../../src/db/postgres.js'
@@ -87,6 +92,27 @@ describe('createPostgresDriver', () => {
expect(pgState.state.pools[1].options).toMatchObject({ database: 'other' })
})
it('sets a connection timeout on every pool', async () => {
const driver = createPostgresDriver(target())
await driver.query({ sql: 'select 1', rowLimit: 10 })
expect(pgState.state.pools[0].options.connectionTimeoutMillis).toBe(10_000)
})
it('returns wall-clock date/timestamp types as raw strings, keeps tz absolute', async () => {
const driver = createPostgresDriver(target())
await driver.query({ sql: 'select 1', rowLimit: 10 })
const types = pgState.state.pools[0].options.types as {
getTypeParser: (oid: number, format?: string) => (value: string) => unknown
}
// DATE (1082) and TIMESTAMP without tz (1114) stay raw strings.
expect(types.getTypeParser(1082)('2026-06-11')).toBe('2026-06-11')
expect(types.getTypeParser(1114)('2026-06-11 12:34:56')).toBe('2026-06-11 12:34:56')
// TIMESTAMPTZ (1184) keeps the default parser, which is not identity.
expect(types.getTypeParser(1184)('2026-06-11 12:34:56+00')).not.toBe(
'2026-06-11 12:34:56+00'
)
})
it('passes readonly as a startup option', async () => {
const driver = createPostgresDriver(target({ readonly: true }))
await driver.query({ sql: 'select 1', rowLimit: 10 })
+37
View File
@@ -66,6 +66,24 @@ describe('assertSafeStatement', () => {
).not.toThrow()
})
it('does not treat a digit-leading dollar tag as a dollar quote', () => {
// $1$ is a positional param $1 followed by a bare $, NOT a dollar
// quote. The ; after it is top-level, so this is two statements.
expect(() => assertSafeStatement('SELECT $1$ ; DROP TABLE x', 'postgres')).toThrow(
/one SQL statement/
)
})
it('allows digits later inside a dollar tag', () => {
expect(() =>
assertSafeStatement('select $tag1$ a ; b $tag1$', 'postgres')
).not.toThrow()
})
it('keeps plain positional params with values unaffected', () => {
expect(() => assertSafeStatement('SELECT $1', 'postgres')).not.toThrow()
})
it('ignores semicolons inside line comments', () => {
expect(() => assertSafeStatement('select 1 -- ; select 2', 'postgres')).not.toThrow()
expect(() => assertSafeStatement('select 1 # ; select 2', 'mysql')).not.toThrow()
@@ -94,6 +112,25 @@ describe('assertSafeStatement', () => {
expect(() => assertSafeStatement("select 'a\\'; select 2'", 'mysql')).not.toThrow()
})
it('handles mysql backslash escape inside double-quoted string', () => {
// mysql treats "..." as a string; \" does not close it, so the ;
// stays inside the literal and this is a single statement.
expect(() => assertSafeStatement('SELECT "x\\";y" AS c', 'mysql')).not.toThrow()
})
it('handles double-quote doubling in both engines', () => {
expect(() => assertSafeStatement('select "a""b;c"', 'mysql')).not.toThrow()
expect(() => assertSafeStatement('select "a""b;c"', 'postgres')).not.toThrow()
})
it('does not treat backslash as escape in postgres double-quoted identifiers', () => {
// For postgres "..." is an identifier with no backslash escapes, so
// "a\" closes at the second quote and '; SELECT 1' is a second statement.
expect(() => assertSafeStatement('SELECT "a\\";SELECT 1', 'postgres')).toThrow(
/one SQL statement/
)
})
it('treats postgres E-strings as backslash-escaped (single statement)', () => {
// E'it\'s; fine' is one literal: \' is an escaped quote, so the ;
// stays inside the string and this is a single statement.