fix(db): PostgreSQL P0 fixes - SQL_KEYWORDS expansion, timeout config, and tests

- Expand SQL_KEYWORDS from ~120 to 226+ words covering:
  - Window functions (ROW_NUMBER, RANK, LAG, LEAD, etc.)
  - CTEs (WITH, RECURSIVE, MATERIALIZED, etc.)
  - Advanced grouping (ROLLUP, CUBE, GROUPING SETS)
  - JSON operations, types, table sampling
  - Transaction control and other PostgreSQL-specific keywords
- Add connection pool timeout configuration:
  - connectionTimeoutMillis: 10s
  - statement_timeout: 30s (PostgreSQL level)
  - idleTimeoutMillis: 30s (connection cleanup)
  - query_timeout: 60s (driver-level fallback)
- Add 12 comprehensive edge case tests covering:
  - Window functions, CTEs, advanced grouping
  - CASE expressions, set operations, JSON operators
- All 38 tests pass

Production-ready: prevents hung queries and supports complex SQL.
This commit is contained in:
Misaka
2026-04-05 13:09:01 +08:00
parent e2669af870
commit 7601b5f176
2 changed files with 432 additions and 29 deletions

View File

@@ -15,9 +15,20 @@ export type { PostgreSqlConfig } from '../../types/database.types'
* SQL keywords that should NOT be double-quoted during identifier preprocessing.
* PostgreSQL lowercases unquoted identifiers, but SSMA-migrated databases
* have uppercase column names that require double-quoting to preserve case.
*
* This list covers PostgreSQL reserved words across multiple categories:
* - DML (Data Manipulation Language)
* - DDL (Data Definition Language)
* - Window functions
* - CTEs (Common Table Expressions)
* - Advanced GROUP BY clauses
* - JSON operations
* - Type system
* - Table sampling
* - Transaction control
*/
const SQL_KEYWORDS = new Set([
// DML
// ==================== DML (Data Manipulation Language) ====================
'SELECT',
'FROM',
'WHERE',
@@ -33,7 +44,8 @@ const SQL_KEYWORDS = new Set([
'UPDATE',
'SET',
'DELETE',
// Ordering & limiting
// ==================== Ordering & Limiting ====================
'ORDER',
'BY',
'ASC',
@@ -44,7 +56,8 @@ const SQL_KEYWORDS = new Set([
'NEXT',
'ROWS',
'ONLY',
// Joins
// ==================== Joins ====================
'JOIN',
'LEFT',
'RIGHT',
@@ -53,16 +66,67 @@ const SQL_KEYWORDS = new Set([
'CROSS',
'FULL',
'ON',
// Set operations
'NATURAL',
'LATERAL',
// ==================== Set Operations ====================
'UNION',
'ALL',
'INTERSECT',
'EXCEPT',
// Grouping
// ==================== Grouping & Aggregation ====================
'GROUP',
'HAVING',
'DISTINCT',
// DDL
'GROUPING',
'SETS',
'ROLLUP',
'CUBE',
'FILTER',
'WITHIN',
// ==================== Window Functions ====================
'OVER',
'PARTITION',
'WINDOW',
'RANGE',
'UNBOUNDED',
'PRECEDING',
'FOLLOWING',
'CURRENT',
'ROW',
'GROUPS',
'EXCLUDE',
'TIES',
'RANK',
'DENSE_RANK',
'ROW_NUMBER',
'NTILE',
'LAG',
'LEAD',
'FIRST_VALUE',
'LAST_VALUE',
'NTH_VALUE',
// ==================== CTE (Common Table Expressions) ====================
'WITH',
'RECURSIVE',
'MATERIALIZED',
'SEARCH',
'CYCLE',
'PATH',
'ROOT',
'SIBLINGS',
// ==================== CASE Expressions ====================
'CASE',
'WHEN',
'THEN',
'ELSE',
'END',
// ==================== DDL (Data Definition Language) ====================
'CREATE',
'ALTER',
'DROP',
@@ -73,7 +137,15 @@ const SQL_KEYWORDS = new Set([
'MODIFY',
'RENAME',
'TO',
// PostgreSQL specific
'GENERATED',
'ALWAYS',
'IDENTITY',
'INCLUDE',
'TEMP',
'TEMPORARY',
'UNLOGGED',
// ==================== PostgreSQL Specific - UPSERT/MERGE ====================
'CONFLICT',
'DO',
'NOTHING',
@@ -82,32 +154,76 @@ const SQL_KEYWORDS = new Set([
'MERGE',
'USING',
'MATCHED',
'WHEN',
'THEN',
'ELSE',
'END',
'TARGET',
'SOURCE',
// Functions
// ==================== Aggregate Functions ====================
'COUNT',
'SUM',
'AVG',
'MIN',
'MAX',
'EXISTS',
'CURRENT_TIMESTAMP',
'NOW',
'GETDATE',
'COALESCE',
'NULLIF',
'CAST',
'AS',
// Transaction
// ==================== JSON Operations ====================
'JSON',
'JSONB',
'JSON_ARRAY',
'JSON_OBJECT',
'JSON_AGG',
'JSONB_AGG',
'JSONB_OBJECT_AGG',
// ==================== Types & Casting ====================
'DECIMAL',
'NUMERIC',
'BOOLEAN',
'CHARACTER',
'VARYING',
'PRECISION',
'REAL',
'DOUBLE',
'FLOAT',
'TEXT',
'INTEGER',
'SERIAL',
'BIGINT',
'SMALLINT',
'DATE',
'TIME',
'TIMESTAMP',
'TIMESTAMPTZ',
'TIMEZONE',
'INTERVAL',
'BIGSERIAL',
'SMALLSERIAL',
// ==================== Table Sampling ====================
'TABLESAMPLE',
'BERNOULLI',
'SYSTEM',
'REPEATABLE',
'SEED',
// ==================== Transaction Control ====================
'BEGIN',
'COMMIT',
'ROLLBACK',
'SAVEPOINT',
// Types & values
'WORK',
'ISOLATION',
'LEVEL',
'READ',
'WRITE',
'COMMITTED',
'REPEATABLE',
'SERIALIZABLE',
// ==================== Types & Values ====================
'TRUE',
'FALSE',
'DEFAULT',
@@ -118,23 +234,65 @@ const SQL_KEYWORDS = new Set([
'CONSTRAINT',
'UNIQUE',
'CHECK',
'CASE',
'NULLS',
'FIRST',
'LAST',
// ==================== Pattern Matching ====================
'BETWEEN',
'LIKE',
'ILIKE',
'SIMILAR',
'ESCAPE',
'ANY',
'SOME',
// Common
'IF',
'WITH',
'RECURSIVE',
'OVER',
'PARTITION',
'WINDOW',
'ROW',
'FIRST',
// ==================== Functions & Procedures ====================
'AFTER',
'BEFORE'
'BEFORE',
'EACH',
'STATEMENT',
'TRIGGER',
'FUNCTION',
'PROCEDURE',
'LANGUAGE',
'SQL',
'PLPGSQL',
'RETURNS',
'CALLED',
'STRICT',
'SECURITY',
'INVOKER',
'DEFINER',
'VOLATILE',
'STABLE',
'IMMUTABLE',
'PARALLEL',
'SAFE',
'RESTRICTED',
'UNSAFE',
// ==================== Utility Commands ====================
'CONCURRENTLY',
'REINDEX',
'VACUUM',
'ANALYZE',
'EXPLAIN',
'LOCAL',
'GLOBAL',
'ORDINALITY',
'FREEZE',
'VERBOSE',
'BUFFERS',
'FORMAT',
'XML',
'YAML',
// ==================== Additional Reserved Words ====================
'IF',
'CURRENT_TIMESTAMP',
'NOW',
'GETDATE'
])
/**
@@ -286,7 +444,31 @@ export class PostgreSqlService implements IDatabaseService {
user: this.config.user,
password: this.config.password,
database: this.config.database,
max: this.config.maxPoolSize ?? 10
max: this.config.maxPoolSize ?? 10,
/**
* Connection timeout in milliseconds.
* Time to wait when connecting to PostgreSQL before failing.
* Prevents hanging during network issues or server overload.
*/
connectionTimeoutMillis: 10000,
/**
* PostgreSQL statement timeout in milliseconds.
* Limits execution time for individual SQL statements.
* Prevents long-running queries from blocking the connection pool.
*/
statement_timeout: 30000,
/**
* Idle connection timeout in milliseconds.
* Closes connections that have been idle for this duration.
* Frees up pool resources and prevents stale connections.
*/
idleTimeoutMillis: 30000,
/**
* Query timeout in milliseconds (pg driver level).
* Fallback protection to abort queries that exceed this duration.
* Should be longer than statement_timeout to allow PG to handle first.
*/
query_timeout: 60000
})
// Test connection