( rawQuery: QueryIR, pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, selectClause: Select, collection: Collection, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, limit?: number, offset?: number, groupKeyFn?: (key: unknown, value: unknown) => unknown, )
| 45 | * Always uses fractional indexing and adds the index as __ordering_index to the result |
| 46 | */ |
| 47 | export function processOrderBy( |
| 48 | rawQuery: QueryIR, |
| 49 | pipeline: NamespacedAndKeyedStream, |
| 50 | orderByClause: Array<OrderByClause>, |
| 51 | selectClause: Select, |
| 52 | collection: Collection, |
| 53 | optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, |
| 54 | setWindowFn: (windowFn: (options: WindowOptions) => void) => void, |
| 55 | limit?: number, |
| 56 | offset?: number, |
| 57 | groupKeyFn?: (key: unknown, value: unknown) => unknown, |
| 58 | ): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> { |
| 59 | // Pre-compile all order by expressions |
| 60 | const compiledOrderBy = orderByClause.map((clause) => { |
| 61 | const clauseWithoutAggregates = replaceAggregatesByRefs( |
| 62 | clause.expression, |
| 63 | selectClause, |
| 64 | `$selected`, |
| 65 | ) |
| 66 | |
| 67 | return { |
| 68 | compiledExpression: compileExpression(clauseWithoutAggregates), |
| 69 | compareOptions: buildCompareOptions(clause, collection), |
| 70 | } |
| 71 | }) |
| 72 | |
| 73 | // Create a value extractor function for the orderBy operator |
| 74 | const valueExtractor = (row: NamespacedRow & { $selected?: any }) => { |
| 75 | // The namespaced row contains: |
| 76 | // 1. Table aliases as top-level properties (e.g., row["tableName"]) |
| 77 | // 2. SELECT results in $selected (e.g., row.$selected["aggregateAlias"]) |
| 78 | // The replaceAggregatesByRefs function has already transformed: |
| 79 | // - Aggregate expressions that match SELECT aggregates to use the $selected namespace |
| 80 | // - $selected ref expressions are passed through unchanged (already using the correct namespace) |
| 81 | const orderByContext = row |
| 82 | |
| 83 | if (orderByClause.length > 1) { |
| 84 | // For multiple orderBy columns, create a composite key |
| 85 | return compiledOrderBy.map((compiled) => |
| 86 | compiled.compiledExpression(orderByContext), |
| 87 | ) |
| 88 | } else if (orderByClause.length === 1) { |
| 89 | // For a single orderBy column, use the value directly |
| 90 | const compiled = compiledOrderBy[0]! |
| 91 | return compiled.compiledExpression(orderByContext) |
| 92 | } |
| 93 | |
| 94 | // Default case - no ordering |
| 95 | return null |
| 96 | } |
| 97 | |
| 98 | // Create a multi-property comparator that respects the order and direction of each property |
| 99 | const compare = (a: unknown, b: unknown) => { |
| 100 | // If we're comparing arrays (multiple properties), compare each property in order |
| 101 | if (orderByClause.length > 1) { |
| 102 | const arrayA = a as Array<unknown> |
| 103 | const arrayB = b as Array<unknown> |
| 104 | for (let i = 0; i < orderByClause.length; i++) { |
no test coverage detected