/** * PostgreSQL identifier validation utilities * * Provides validation for PostgreSQL identifiers (table names, column names, etc.) * following PostgreSQL naming rules and security best practices. */ /** * PostgreSQL identifier validation pattern * Identifiers must start with a letter or underscore, followed by letters, numbers, or underscores * * Note: PostgreSQL also allows $ in identifiers, but we disallow it for security */ export const POSTGRES_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/ /** * Maximum length for PostgreSQL identifiers (63 characters) */ export const MAX_IDENTIFIER_LENGTH = 63 /** * Checks if a name is a valid PostgreSQL identifier * * @param name - The identifier to validate * @returns true if the identifier is valid, false otherwise */ export function isValidIdentifier(name: string): boolean { if (!name || name.length === 0) { return false } if (name.length > MAX_IDENTIFIER_LENGTH) { return false } return POSTGRES_IDENTIFIER_PATTERN.test(name) } /** * Validates a PostgreSQL identifier (table name or column name) * * @param name - The identifier to validate * @param type - The type of identifier ('table' or 'column') for error messages * @throws Error if the identifier is invalid */ export function validateIdentifier(name: string, type: 'table' | 'column' = 'table'): void { if (!name || name.length === 0) { throw new Error(`Invalid ${type} name: cannot be empty`) } if (name.length > MAX_IDENTIFIER_LENGTH) { throw new Error( `Invalid ${type} name "${name}": exceeds maximum length of ${MAX_IDENTIFIER_LENGTH} characters` ) } if (!POSTGRES_IDENTIFIER_PATTERN.test(name)) { throw new Error( `Invalid ${type} name "${name}": must match PostgreSQL identifier rules (start with letter or underscore, contain only letters, numbers, and underscores)` ) } } /** * Validates a schema-qualified PostgreSQL name (e.g., "public.users") * * @param name - The schema-qualified name to validate (schema.identifier) * @throws Error if the name is invalid */ export function validateSchemaQualifiedName(name: string): void { if (!name || name.length === 0) { throw new Error('Invalid schema-qualified name: cannot be empty') } // Handle schema-qualified names (schema.table) if (name.includes('.')) { const parts = name.split('.') if (parts.length !== 2) { throw new Error( `Invalid schema-qualified name "${name}": must have exactly one dot separating schema and identifier` ) } const [schema, identifier] = parts as [string, string] validateIdentifier(schema, 'table') // schema name follows same rules validateIdentifier(identifier, 'table') return } // If no dot, validate as a simple identifier validateIdentifier(name, 'table') }