* Builds a case-insensitive pattern match against a JSONB cell using ILIKE. * `position` controls wildcard placement: `contains` → `%value%`, `startsWith` * → `value%`, `endsWith` → `%value`. When `negate` is set the match is inverted * and null cells are included — "does not contain X" should ke
(
tableName: string,
field: string,
value: string,
position: 'contains' | 'startsWith' | 'endsWith',
options?: { negate?: boolean }
)
| 504 | * back to a sequential scan bounded by the `table_id` btree prefix. |
| 505 | */ |
| 506 | function buildLikeClause( |
| 507 | tableName: string, |
| 508 | field: string, |
| 509 | value: string, |
| 510 | position: 'contains' | 'startsWith' | 'endsWith', |
| 511 | options?: { negate?: boolean } |
| 512 | ): SQL { |
| 513 | const escapedField = field.replace(/'/g, "''") |
| 514 | // Coerce defensively: filters arriving via the raw v1 API / tools may carry a |
| 515 | // non-string value (e.g. `{ $contains: 123 }`), and ILIKE compares text anyway. |
| 516 | const text = String(value) |
| 517 | // An empty pattern collapses to `%`/`%%`, which matches every non-null row — |
| 518 | // a silent footgun for raw-API callers (the UI gates empty values out). Reject |
| 519 | // it, consistent with the range/`$empty` operand validation. |
| 520 | if (text.length === 0) { |
| 521 | const opName = position === 'contains' && options?.negate ? 'ncontains' : position |
| 522 | throw new TableQueryValidationError( |
| 523 | `$${opName} on column "${field}" requires a non-empty value` |
| 524 | ) |
| 525 | } |
| 526 | const escaped = escapeLikePattern(text) |
| 527 | const pattern = |
| 528 | position === 'startsWith' |
| 529 | ? `${escaped}%` |
| 530 | : position === 'endsWith' |
| 531 | ? `%${escaped}` |
| 532 | : `%${escaped}%` |
| 533 | const cell = sql.raw(`${tableName}.data->>'${escapedField}'`) |
| 534 | return options?.negate |
| 535 | ? sql`(${cell} IS NULL OR ${cell} NOT ILIKE ${pattern})` |
| 536 | : sql`${cell} ILIKE ${pattern}` |
| 537 | } |
| 538 | |
| 539 | /** |
| 540 | * Coerces a `$empty` operand to a boolean. Accepts a real boolean (the UI path) |
no test coverage detected