* Builds a single ORDER BY clause for a field. * Timestamp fields use direct column access, others use JSONB text extraction. * Numeric and date columns are cast to appropriate types for correct sorting. * * @param tableName - The table name * @param field - The field name to sort by * @param
( tableName: string, field: string, direction: 'asc' | 'desc', columnType: ColumnType | undefined )
| 575 | * @param columnType - Optional column type for type-aware sorting |
| 576 | */ |
| 577 | function buildSortFieldClause( |
| 578 | tableName: string, |
| 579 | field: string, |
| 580 | direction: 'asc' | 'desc', |
| 581 | columnType: ColumnType | undefined |
| 582 | ): SQL { |
| 583 | const escapedField = field.replace(/'/g, "''") |
| 584 | const directionSql = direction.toUpperCase() |
| 585 | |
| 586 | if (field === 'createdAt' || field === 'updatedAt') { |
| 587 | return sql.raw(`${tableName}.${escapedField} ${directionSql}`) |
| 588 | } |
| 589 | |
| 590 | const jsonbExtract = `${tableName}.data->>'${escapedField}'` |
| 591 | const cast = jsonbCastForType(columnType) |
| 592 | |
| 593 | if (cast === null) { |
| 594 | // Sort as text (string, boolean, json, or unknown types) |
| 595 | return sql.raw(`${jsonbExtract} ${directionSql}`) |
| 596 | } |
| 597 | |
| 598 | // NULLS LAST so rows with null/invalid values sort to the bottom regardless of direction |
| 599 | return sql.raw(`(${jsonbExtract})::${cast} ${directionSql} NULLS LAST`) |
| 600 | } |
no test coverage detected