Query DSL and SQL Builder
Query DSL and SQL Builder
Relevant source files
The following files were used as context for generating this wiki page:
The @primebrick/dal-pg package provides a functional Domain Specific Language (DSL) for constructing type-safe database queries. This DSL abstracts the complexities of SQL syntax while maintaining a strong link to the underlying Entity metadata. The buildSelectQuery engine transforms these DSL expressions into parameterized SqlQuery objects, ensuring protection against SQL injection through strict identifier validation and parameterization.
Natural Language to Code Entity Space: DSL Components
This diagram maps the conceptual query components used in natural language to their corresponding classes and functions in the src/query/dsl.ts file.
Query DSL Mapping
Code
Sources: src/query/dsl.ts:23-124
Functional Query DSL
The DSL is designed to be composable and type-safe by leveraging TypeScript generics to ensure that fields referenced in queries actually exist on the target entities.
Field References
The foundation of the DSL is the field() function. it creates a FieldRef which binds a specific property of an EntityClass to its metadata src/query/dsl.ts:28-33.
Filtering (Filter)
The Filter object provides static methods for creating FilterExpr objects:
fieldValue(left, op, right): Compares a field to a literal value src/query/dsl.ts:64-71.fieldField(left, op, right): Compares two fields (e.g., for self-joins or cross-table comparisons) src/query/dsl.ts:72-79.raw(left, op, right): Allows raw SQL fragments (use with caution) src/query/dsl.ts:80-82.group(filters, operand): Groups multiple filters withANDorORlogic src/query/dsl.ts:83-85.
Sorting, Joining, and Projection
- Sort:
Sort.by(field, direction)defines theORDER BYclause src/query/dsl.ts:89-93. - Join:
Join.on(right, left, type, options)defines table relationships. It supportsINNER,LEFT, andRIGHTjoins, along with explicit type casting src/query/dsl.ts:102-111. - Project:
Project.field(field, alias)orProject.expr(sql, alias)defines which columns are returned. If no projection is provided, the builder defaults to all persisted columns defined in the entity metadata src/query/dsl.ts:117-124.
SQL Builder Engine
The buildSelectQuery function (and its internal helpers in src/query/query-builder.ts) acts as the compiler that translates DSL objects into a SqlQuery containing a string text and an array of values src/query/query-builder.ts:7-7.
Data Flow: DSL to SQL
The following diagram illustrates how the ParamWriter and various render functions cooperate to produce a safe, parameterized SQL statement.
SQL Generation Pipeline
Code
Sources: src/query/query-builder.ts:41-47, src/query/query-builder.ts:49-132, src/query/query-builder.ts:182-200
Key Rendering Logic
| Function | Responsibility | Implementation Detail |
|---|---|---|
renderProjection | Generates the SELECT clause. | Maps TS property names to SQL column names using AS aliases src/query/query-builder.ts:49-89. |
renderJoins | Generates JOIN ... ON clauses. | Handles automatic type casting (e.g., ::uuid) and includes regex guards for UUID joins src/query/query-builder.ts:91-132. |
renderWhere | Generates the WHERE clause. | Injects soft-delete filters (deleted_at IS NULL) based on WithDeletedRecords mode src/query/query-builder.ts:182-195. |
renderFilterExpr | Recursively processes filters. | Handles complex operators like ILIKE, BETWEEN, IN, and IS NULL src/query/query-builder.ts:134-180. |
ParamWriter | Manages query parameters. | Collects values into an array and returns $1, $2, etc. placeholders src/query/query-builder.ts:41-47. |
Security and Injection Safety
The builder employs a "Strict Identifier" strategy to prevent SQL injection via table or column names, which cannot be parameterized in standard PostgreSQL.
Identifier Validation
Every identifier (table names, column names, aliases) is passed through assertValidIdentPart(s, what). This function enforces a strict regex: /^[A-Za-z_][A-Za-z0-9_]*$/ src/query/query-builder.ts:9-15. If an identifier contains spaces, semicolons, or comments, the system throws an error immediately.
Quoting
The quoteIdent(ident) function ensures that valid identifiers are wrapped in double quotes (e.g., "user_table") to handle PostgreSQL case-sensitivity and reserved keyword conflicts safely src/query/query-builder.ts:17-20.
UUID Safety in Joins
When joining on UUID fields that might be stored as strings in one of the tables, the builder automatically injects a regex check: ~ '^[0-9a-fA-F-]{36}$'. This prevents PostgreSQL from throwing a "badly formed hexadecimal UUID" error when attempting to compare a non-UUID string to a UUID column src/query/query-builder.ts:123-127.
Sources: src/query/query-builder.ts:9-34, src/query/query-builder.ts:123-127, src/query/dsl.ts:1-125