import type { QueryResultRow } from 'pg'; import type { PostgreSQLClient } from './client'; import type { JoinCondition, OrderByCondition, QueryResult, WhereCondition } from './types'; /** * PostgreSQL Query Builder * * Provides a fluent interface for building SQL queries */ export class PostgreSQLQueryBuilder { private queryType: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | null = null; private selectColumns: string[] = []; private fromTable: string = ''; private joins: JoinCondition[] = []; private whereConditions: WhereCondition[] = []; private groupByColumns: string[] = []; private havingConditions: WhereCondition[] = []; private orderByConditions: OrderByCondition[] = []; private limitCount: number | null = null; private offsetCount: number | null = null; private insertValues: Record = {}; private updateValues: Record = {}; private readonly client: PostgreSQLClient; constructor(client: PostgreSQLClient) { this.client = client; } /** * SELECT statement */ select(columns: string | string[] = '*'): this { this.queryType = 'SELECT'; this.selectColumns = Array.isArray(columns) ? columns : [columns]; return this; } /** * FROM clause */ from(table: string): this { this.fromTable = table; return this; } /** * JOIN clause */ join(table: string, on: string, type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL' = 'INNER'): this { this.joins.push({ type, table, on }); return this; } /** * WHERE clause */ where(column: string, operator: string, value?: any): this { this.whereConditions.push({ column, operator: operator as any, value }); return this; } /** * GROUP BY clause */ groupBy(columns: string | string[]): this { this.groupByColumns = Array.isArray(columns) ? columns : [columns]; return this; } /** * ORDER BY clause */ orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this { this.orderByConditions.push({ column, direction }); return this; } /** * LIMIT clause */ limit(count: number): this { this.limitCount = count; return this; } /** * OFFSET clause */ offset(count: number): this { this.offsetCount = count; return this; } /** * INSERT statement */ insert(table: string): this { this.queryType = 'INSERT'; this.fromTable = table; return this; } /** * VALUES for INSERT */ values(data: Record): this { this.insertValues = data; return this; } /** * UPDATE statement */ update(table: string): this { this.queryType = 'UPDATE'; this.fromTable = table; return this; } /** * SET for UPDATE */ set(data: Record): this { this.updateValues = data; return this; } /** * DELETE statement */ delete(table: string): this { this.queryType = 'DELETE'; this.fromTable = table; return this; } /** * Build and execute the query */ async execute(): Promise> { const { sql, params } = this.build(); return await this.client.query(sql, params); } /** * Build the SQL query */ build(): { sql: string; params: any[] } { const params: any[] = []; let sql = ''; switch (this.queryType) { case 'SELECT': sql = this.buildSelectQuery(params); break; case 'INSERT': sql = this.buildInsertQuery(params); break; case 'UPDATE': sql = this.buildUpdateQuery(params); break; case 'DELETE': sql = this.buildDeleteQuery(params); break; default: throw new Error('Query type not specified'); } return { sql, params }; } private buildSelectQuery(params: any[]): string { let sql = `SELECT ${this.selectColumns.join(', ')}`; if (this.fromTable) { sql += ` FROM ${this.fromTable}`; } // Add JOINs for (const join of this.joins) { sql += ` ${join.type} JOIN ${join.table} ON ${join.on}`; } // Add WHERE if (this.whereConditions.length > 0) { sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params); } // Add GROUP BY if (this.groupByColumns.length > 0) { sql += ` GROUP BY ${this.groupByColumns.join(', ')}`; } // Add HAVING if (this.havingConditions.length > 0) { sql += ' HAVING ' + this.buildWhereClause(this.havingConditions, params); } // Add ORDER BY if (this.orderByConditions.length > 0) { const orderBy = this.orderByConditions .map(order => `${order.column} ${order.direction}`) .join(', '); sql += ` ORDER BY ${orderBy}`; } // Add LIMIT if (this.limitCount !== null) { sql += ` LIMIT $${params.length + 1}`; params.push(this.limitCount); } // Add OFFSET if (this.offsetCount !== null) { sql += ` OFFSET $${params.length + 1}`; params.push(this.offsetCount); } return sql; } private buildInsertQuery(params: any[]): string { const columns = Object.keys(this.insertValues); const placeholders = columns.map((_, i) => `$${params.length + i + 1}`); params.push(...Object.values(this.insertValues)); return `INSERT INTO ${this.fromTable} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`; } private buildUpdateQuery(params: any[]): string { const sets = Object.keys(this.updateValues).map((key, i) => { return `${key} = $${params.length + i + 1}`; }); params.push(...Object.values(this.updateValues)); let sql = `UPDATE ${this.fromTable} SET ${sets.join(', ')}`; if (this.whereConditions.length > 0) { sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params); } return sql; } private buildDeleteQuery(params: any[]): string { let sql = `DELETE FROM ${this.fromTable}`; if (this.whereConditions.length > 0) { sql += ' WHERE ' + this.buildWhereClause(this.whereConditions, params); } return sql; } private buildWhereClause(conditions: WhereCondition[], params: any[]): string { return conditions .map(condition => { if (condition.operator === 'IS NULL' || condition.operator === 'IS NOT NULL') { return `${condition.column} ${condition.operator}`; } else { params.push(condition.value); return `${condition.column} ${condition.operator} $${params.length}`; } }) .join(' AND '); } }