Add raw SQL expression support for virtual fields#14
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…al fields Co-authored-by: gblikas <13577108+gblikas@users.noreply.github.com>
…tests Co-authored-by: gblikas <13577108+gblikas@users.noreply.github.com>
Co-authored-by: gblikas <13577108+gblikas@users.noreply.github.com>
Co-authored-by: gblikas <13577108+gblikas@users.noreply.github.com>
Co-authored-by: gblikas <13577108+gblikas@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add support for advanced virtual fields in queries
Add raw SQL expression support for virtual fields
Feb 24, 2026
…ys` parameter, allowing invalid values like Infinity, NaN, and negative numbers to generate invalid SQL. This commit fixes the issue reported at src/virtual-fields/helpers.ts:64 ## Bug Explanation The `dateWithinDays` function in `src/virtual-fields/helpers.ts` accepts a `days` parameter without validating it. While the original issue description claimed this was a SQL injection vulnerability, the actual problem is more subtle: 1. **The SQL injection claim is technically incorrect**: JavaScript numbers converted to strings can only contain digits, decimals, and scientific notation characters - none of which can break out of the SQL string context to inject code. 2. **The real issue is input validation**: JavaScript allows special numeric values like `Infinity`, `-Infinity`, and `NaN`. When these are passed to the function, they generate invalid SQL: - `INTERVAL 'Infinity days'` - Invalid PostgreSQL syntax - `INTERVAL 'NaN days'` - Invalid PostgreSQL syntax - Negative numbers also logically don't make sense for a date range filter 3. **Impact**: This could cause query execution failures and suggests the function is not robust against unexpected inputs. ## Fix Explanation I added a `validateDaysParameter()` function that: - Checks that `days` is finite using `Number.isFinite()` - Checks that `days` is positive (greater than 0) - Throws descriptive error messages for invalid values This follows the existing validation pattern used in the codebase for the `field` parameter via `validateFieldName()`. The fix: - Prevents invalid SQL generation - Makes the function more robust - Maintains backward compatibility with all valid existing usage - Adds comprehensive test coverage for edge cases All existing tests pass, and new tests validate that invalid numeric values are properly rejected. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: gblikas <gblikas@gmail.com>
…reSQL SQL by inserting raw JSON strings without proper quoting, resulting in `field @> [...]::jsonb` instead of `field @> '[...]'::jsonb`
This commit fixes the issue reported at src/virtual-fields/helpers.ts:42
## Bug Analysis
**Why this is a bug:**
The jsonbContains function was generating SQL like:
```sql
assigned_to @> ["user123"]::jsonb
```
PostgreSQL interprets `[...]` as a SQL array literal syntax, not JSON. When PostgreSQL tries to cast a SQL array literal to JSONB, it fails because:
- `["user123"]` is not valid JSON (JSON uses string syntax, not array literals)
- The `@>` operator expects a JSONB value on the right side
- JSONB values must be constructed from string literals containing valid JSON
The correct SQL should be:
```sql
assigned_to @> '["user123"]'::jsonb
```
The string literal quotes are essential - without them, PostgreSQL cannot parse the JSON syntax.
**When it manifests:**
- Any time `jsonbContains()` is used to generate SQL, the query will fail with a PostgreSQL syntax error
- This affects all uses of virtual field features that rely on JSONB contains checks
- The error would occur at query execution time, not at build/parse time
**Impact:**
- Complete failure of JSONB-based filtering in virtual fields
- Users cannot use `jsonbContains()` helper in their queries
- This is a critical runtime bug that prevents core functionality
## Fix Analysis
**What changed:**
Modified the SQL generation in `jsonbContains()` to properly quote the JSON string:
```typescript
// Before:
sql`${sql.identifier(field)} @> ${JSON.stringify(...)}::jsonb`
// After:
sql`${sql.identifier(field)} @> ${sql.raw("'" + JSON.stringify(...).replace(/'/g, "''") + "'::jsonb")}`
```
**Why this solves it:**
1. `JSON.stringify()` produces a valid JSON string (e.g., `["user123"]`)
2. Wrapping in single quotes creates a PostgreSQL string literal: `'["user123"]'`
3. Escaping single quotes by doubling them prevents SQL injection: `.replace(/'/g, "''")`
4. Appending `::jsonb` casts the string to the correct type
5. Using `sql.raw()` ensures the entire quoted string is inserted as-is into the template
**Why it's safe:**
- Field names are still protected by `sql.identifier()`
- Values are already escaped by `JSON.stringify()` (which handles JSON encoding)
- Additional single quote escaping prevents any possible injection from values containing single quotes
- All tests (526) pass, confirming backward compatibility and correctness
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: gblikas <gblikas@gmail.com>
gblikas
approved these changes
Feb 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Virtual fields previously only supported standard comparison expressions, preventing database-specific operations like JSONB array membership checks and computed fields based on date calculations.
Changes
Type System
IRawSqlExpressioninterface toparser/types.ts- encapsulates SQL generation viatoSql(context) => SQLIRawSqlContextinterface tovirtual-fields/types.ts- provides adapter, tableName, schema to SQL generatorsQueryExpressionunion to include raw expressionsResolution & Translation
virtual-fields/resolver.ts- pass through raw expressions unchangedtranslators/drizzle/index.ts- invoketoSql()method and return Drizzle SQL objectsecurity/validator.ts- handle raw expressions in all validation methods (skip field/value checks, count as one clause)Helper Utilities (new file:
virtual-fields/helpers.ts)jsonbContains(field, value)- PostgreSQL JSONB@>operator for array membershipdateWithinDays(field, days)- date range check usingNOW() - INTERVALUsage
Testing
Added
virtual-fields/raw-sql.test.ts(38 tests) anduser-example-integration.test.ts(4 tests) covering unit, integration, and end-to-end scenarios including field validation.Original prompt
Problem
Virtual fields currently only support resolving to standard comparison expressions on schema fields. This limitation prevents users from implementing advanced query patterns such as:
my:assignedwhereassignedTois a JSONB array and we need to check if the current user's ID is in that arraypriority:highbased oncreatedAttimestamps, where "high" priority means created within the last dayUser Example
A user has a Drizzle schema like:
They want to support queries like
my:assignedwhich should check ifctx.currentUserIdis contained in theassignedToJSONB array.Proposed Solution
Extend the virtual fields system to support raw SQL expressions that resolvers can return for database-specific operations.
1. Add new types in
src/virtual-fields/types.ts2. Update resolver in
src/virtual-fields/resolver.tsPass through raw expressions without modification:
3. Update Drizzle translator in
src/translators/drizzle/index.tsHandle raw expressions by calling their
toSqlmethod:4. Add helper utilities (optional but recommended)
Provide common helpers in
src/virtual-fields/helpers.ts:Example Usage After Implementation