@@ -353,20 +353,28 @@ function getAggregateFunction(aggExpr: Aggregate) {
353353 const valueExtractor = ( [ , namespacedRow ] : [ string , NamespacedRow ] ) => {
354354 const value = compiledExpr ( namespacedRow )
355355 // Ensure we return a number for numeric aggregate functions
356- return typeof value === `number` ? value : value != null ? Number ( value ) : 0
356+ if ( typeof value === `number` ) {
357+ return value
358+ }
359+ return value != null ? Number ( value ) : 0
357360 }
358361
359- // Create a value extractor function for the expression to aggregate
360- const valueExtractorWithDate = ( [ , namespacedRow ] : [
362+ // Create a value extractor function for min/max that preserves comparable types
363+ const valueExtractorForMinMax = ( [ , namespacedRow ] : [
361364 string ,
362365 NamespacedRow ,
363366 ] ) => {
364367 const value = compiledExpr ( namespacedRow )
365- return typeof value === `number` || value instanceof Date
366- ? value
367- : value != null
368- ? Number ( value )
369- : 0
368+ // Preserve strings, numbers, Dates, and bigints for comparison
369+ if (
370+ typeof value === `number` ||
371+ typeof value === `string` ||
372+ typeof value === `bigint` ||
373+ value instanceof Date
374+ ) {
375+ return value
376+ }
377+ return value != null ? Number ( value ) : 0
370378 }
371379
372380 // Create a raw value extractor function for the expression to aggregate
@@ -383,9 +391,9 @@ function getAggregateFunction(aggExpr: Aggregate) {
383391 case `avg` :
384392 return avg ( valueExtractor )
385393 case `min` :
386- return min ( valueExtractorWithDate )
394+ return min ( valueExtractorForMinMax )
387395 case `max` :
388- return max ( valueExtractorWithDate )
396+ return max ( valueExtractorForMinMax )
389397 default :
390398 throw new UnsupportedAggregateFunctionError ( aggExpr . name )
391399 }
@@ -394,20 +402,15 @@ function getAggregateFunction(aggExpr: Aggregate) {
394402/**
395403 * Transforms expressions to replace aggregate functions with references to computed values.
396404 *
397- * This function is used in both ORDER BY and HAVING clauses to transform expressions that reference:
398- * 1. Aggregate functions (e.g., `max()`, `count()`) - replaces with references to computed aggregates in SELECT
399- * 2. SELECT field references via $selected namespace (e.g., `$selected.latestActivity`) - validates and passes through unchanged
400- *
401- * For aggregate expressions, it finds matching aggregates in the SELECT clause and replaces them with
402- * PropRef([resultAlias, alias]) to reference the computed aggregate value.
405+ * For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them
406+ * with PropRef([resultAlias, alias]) to reference the computed aggregate value.
403407 *
404- * For ref expressions using the $selected namespace, it validates that the field exists in the SELECT clause
405- * and passes them through unchanged (since $selected is already the correct namespace). All other ref expressions
406- * are passed through unchanged (treating them as table column references).
408+ * Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged.
409+ * Function expressions are recursively transformed.
407410 *
408411 * @param havingExpr - The expression to transform (can be aggregate, ref, func, or val)
409412 * @param selectClause - The SELECT clause containing aliases and aggregate definitions
410- * @param resultAlias - The namespace alias for SELECT results (default: '$selected', used for aggregate references )
413+ * @param resultAlias - The namespace alias for SELECT results (default: '$selected')
411414 * @returns A transformed BasicExpression that references computed values instead of raw expressions
412415 */
413416export function replaceAggregatesByRefs (
@@ -439,41 +442,11 @@ export function replaceAggregatesByRefs(
439442 return new Func ( funcExpr . name , transformedArgs )
440443 }
441444
442- case `ref` : {
443- const refExpr = havingExpr
444- const path = refExpr . path
445-
446- if ( path . length === 0 ) {
447- // Empty path - pass through
448- return havingExpr as BasicExpression
449- }
450-
451- // Check if this is a $selected reference
452- if ( path . length > 0 && path [ 0 ] === `$selected` ) {
453- // Extract the field path after $selected
454- const fieldPath = path . slice ( 1 )
455-
456- if ( fieldPath . length === 0 ) {
457- // Just $selected without a field - pass through unchanged
458- return havingExpr as BasicExpression
459- }
460-
461- // Verify the field exists in SELECT clause
462- const alias = fieldPath . join ( `.` )
463- if ( alias in selectClause ) {
464- // Pass through unchanged - $selected is already the correct namespace
465- return havingExpr as BasicExpression
466- }
467-
468- // Field doesn't exist in SELECT - this is an error, but we'll pass through for now
469- // (Could throw an error here in the future)
470- return havingExpr as BasicExpression
471- }
472-
473- // Not a $selected reference - this is a table column reference, pass through unchanged
474- // SELECT fields should only be accessed via $selected namespace
445+ case `ref` :
446+ // Ref expressions are passed through unchanged - they reference either:
447+ // - $selected fields (which are already in the correct namespace)
448+ // - Table column references (which remain valid)
475449 return havingExpr as BasicExpression
476- }
477450
478451 case `val` :
479452 // Return as-is
0 commit comments