|  |  | @ -1008,6 +1008,7 @@ type AggregationSpec struct { | 
			
		
	
		
			
				
					|  |  |  | 	Function string // COUNT, SUM, AVG, MIN, MAX
 | 
			
		
	
		
			
				
					|  |  |  | 	Column   string // Column name, or "*" for COUNT(*)
 | 
			
		
	
		
			
				
					|  |  |  | 	Alias    string // Optional alias for the result column
 | 
			
		
	
		
			
				
					|  |  |  | 	Distinct bool   // Support for DISTINCT keyword
 | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | // AggregationResult holds the computed result of an aggregation
 | 
			
		
	
	
		
			
				
					|  |  | @ -1137,11 +1138,26 @@ func (e *SQLEngine) computeAggregations(results []HybridScanResult, aggregations | 
			
		
	
		
			
				
					|  |  |  | 			if spec.Column == "*" { | 
			
		
	
		
			
				
					|  |  |  | 				// COUNT(*) counts all rows
 | 
			
		
	
		
			
				
					|  |  |  | 				aggResults[i].Count = int64(len(results)) | 
			
		
	
		
			
				
					|  |  |  | 			} else if spec.Distinct { | 
			
		
	
		
			
				
					|  |  |  | 				// COUNT(DISTINCT column) counts unique non-null values
 | 
			
		
	
		
			
				
					|  |  |  | 				uniqueValues := make(map[string]bool) | 
			
		
	
		
			
				
					|  |  |  | 				for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 					if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 						if !e.isNullValue(value) { | 
			
		
	
		
			
				
					|  |  |  | 							// Use string representation for uniqueness check
 | 
			
		
	
		
			
				
					|  |  |  | 							rawValue := e.extractRawValue(value) | 
			
		
	
		
			
				
					|  |  |  | 							if rawValue != nil { | 
			
		
	
		
			
				
					|  |  |  | 								uniqueValues[fmt.Sprintf("%v", rawValue)] = true | 
			
		
	
		
			
				
					|  |  |  | 							} | 
			
		
	
		
			
				
					|  |  |  | 						} | 
			
		
	
		
			
				
					|  |  |  | 					} | 
			
		
	
		
			
				
					|  |  |  | 				} | 
			
		
	
		
			
				
					|  |  |  | 				aggResults[i].Count = int64(len(uniqueValues)) | 
			
		
	
		
			
				
					|  |  |  | 			} else { | 
			
		
	
		
			
				
					|  |  |  | 				// COUNT(column) counts non-null values
 | 
			
		
	
		
			
				
					|  |  |  | 				count := int64(0) | 
			
		
	
		
			
				
					|  |  |  | 				for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 					if value := e.findColumnValue(result.Values, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 						if !e.isNullValue(value) { | 
			
		
	
		
			
				
					|  |  |  | 							count++ | 
			
		
	
		
			
				
					|  |  |  | 						} | 
			
		
	
	
		
			
				
					|  |  | @ -1153,7 +1169,7 @@ func (e *SQLEngine) computeAggregations(results []HybridScanResult, aggregations | 
			
		
	
		
			
				
					|  |  |  | 		case "SUM": | 
			
		
	
		
			
				
					|  |  |  | 			sum := float64(0) | 
			
		
	
		
			
				
					|  |  |  | 			for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result.Values, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if numValue := e.convertToNumber(value); numValue != nil { | 
			
		
	
		
			
				
					|  |  |  | 						sum += *numValue | 
			
		
	
		
			
				
					|  |  |  | 					} | 
			
		
	
	
		
			
				
					|  |  | @ -1165,7 +1181,7 @@ func (e *SQLEngine) computeAggregations(results []HybridScanResult, aggregations | 
			
		
	
		
			
				
					|  |  |  | 			sum := float64(0) | 
			
		
	
		
			
				
					|  |  |  | 			count := int64(0) | 
			
		
	
		
			
				
					|  |  |  | 			for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result.Values, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if numValue := e.convertToNumber(value); numValue != nil { | 
			
		
	
		
			
				
					|  |  |  | 						sum += *numValue | 
			
		
	
		
			
				
					|  |  |  | 						count++ | 
			
		
	
	
		
			
				
					|  |  | @ -1179,9 +1195,11 @@ func (e *SQLEngine) computeAggregations(results []HybridScanResult, aggregations | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 		case "MIN": | 
			
		
	
		
			
				
					|  |  |  | 			var min interface{} | 
			
		
	
		
			
				
					|  |  |  | 			var minValue *schema_pb.Value | 
			
		
	
		
			
				
					|  |  |  | 			for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result.Values, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if min == nil || e.compareValues(value, min) < 0 { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if minValue == nil || e.compareValues(value, minValue) < 0 { | 
			
		
	
		
			
				
					|  |  |  | 						minValue = value | 
			
		
	
		
			
				
					|  |  |  | 						min = e.extractRawValue(value) | 
			
		
	
		
			
				
					|  |  |  | 					} | 
			
		
	
		
			
				
					|  |  |  | 				} | 
			
		
	
	
		
			
				
					|  |  | @ -1190,9 +1208,11 @@ func (e *SQLEngine) computeAggregations(results []HybridScanResult, aggregations | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 		case "MAX": | 
			
		
	
		
			
				
					|  |  |  | 			var max interface{} | 
			
		
	
		
			
				
					|  |  |  | 			var maxValue *schema_pb.Value | 
			
		
	
		
			
				
					|  |  |  | 			for _, result := range results { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result.Values, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if max == nil || e.compareValues(value, max) > 0 { | 
			
		
	
		
			
				
					|  |  |  | 				if value := e.findColumnValue(result, spec.Column); value != nil { | 
			
		
	
		
			
				
					|  |  |  | 					if maxValue == nil || e.compareValues(value, maxValue) > 0 { | 
			
		
	
		
			
				
					|  |  |  | 						maxValue = value | 
			
		
	
		
			
				
					|  |  |  | 						max = e.extractRawValue(value) | 
			
		
	
		
			
				
					|  |  |  | 					} | 
			
		
	
		
			
				
					|  |  |  | 				} | 
			
		
	
	
		
			
				
					|  |  | @ -1241,20 +1261,29 @@ func (e *SQLEngine) extractRawValue(value *schema_pb.Value) interface{} { | 
			
		
	
		
			
				
					|  |  |  | 		return v.StringValue | 
			
		
	
		
			
				
					|  |  |  | 	case *schema_pb.Value_BoolValue: | 
			
		
	
		
			
				
					|  |  |  | 		return v.BoolValue | 
			
		
	
		
			
				
					|  |  |  | 	case *schema_pb.Value_BytesValue: | 
			
		
	
		
			
				
					|  |  |  | 		return string(v.BytesValue) // Convert bytes to string for comparison
 | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 	return nil | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 interface{}) int { | 
			
		
	
		
			
				
					|  |  |  | func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 *schema_pb.Value) int { | 
			
		
	
		
			
				
					|  |  |  | 	if value2 == nil { | 
			
		
	
		
			
				
					|  |  |  | 		return 1 // value1 > nil
 | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 	raw1 := e.extractRawValue(value1) | 
			
		
	
		
			
				
					|  |  |  | 	raw2 := e.extractRawValue(value2) | 
			
		
	
		
			
				
					|  |  |  | 	if raw1 == nil { | 
			
		
	
		
			
				
					|  |  |  | 		return -1 | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 	if raw2 == nil { | 
			
		
	
		
			
				
					|  |  |  | 		return 1 | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 	// Simple comparison - in a full implementation this would handle type coercion
 | 
			
		
	
		
			
				
					|  |  |  | 	switch v1 := raw1.(type) { | 
			
		
	
		
			
				
					|  |  |  | 	case int32: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := value2.(int32); ok { | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(int32); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 < v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return -1 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 > v2 { | 
			
		
	
	
		
			
				
					|  |  | @ -1263,7 +1292,16 @@ func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 interface{}) i | 
			
		
	
		
			
				
					|  |  |  | 			return 0 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	case int64: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := value2.(int64); ok { | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(int64); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 < v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return -1 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 > v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return 1 | 
			
		
	
		
			
				
					|  |  |  | 			} | 
			
		
	
		
			
				
					|  |  |  | 			return 0 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	case float32: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(float32); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 < v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return -1 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 > v2 { | 
			
		
	
	
		
			
				
					|  |  | @ -1272,7 +1310,7 @@ func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 interface{}) i | 
			
		
	
		
			
				
					|  |  |  | 			return 0 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	case float64: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := value2.(float64); ok { | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(float64); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 < v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return -1 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 > v2 { | 
			
		
	
	
		
			
				
					|  |  | @ -1281,7 +1319,7 @@ func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 interface{}) i | 
			
		
	
		
			
				
					|  |  |  | 			return 0 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	case string: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := value2.(string); ok { | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(string); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 < v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return -1 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 > v2 { | 
			
		
	
	
		
			
				
					|  |  | @ -1289,6 +1327,15 @@ func (e *SQLEngine) compareValues(value1 *schema_pb.Value, value2 interface{}) i | 
			
		
	
		
			
				
					|  |  |  | 			} | 
			
		
	
		
			
				
					|  |  |  | 			return 0 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	case bool: | 
			
		
	
		
			
				
					|  |  |  | 		if v2, ok := raw2.(bool); ok { | 
			
		
	
		
			
				
					|  |  |  | 			if v1 == v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return 0 | 
			
		
	
		
			
				
					|  |  |  | 			} else if v1 && !v2 { | 
			
		
	
		
			
				
					|  |  |  | 				return 1 | 
			
		
	
		
			
				
					|  |  |  | 			} | 
			
		
	
		
			
				
					|  |  |  | 			return -1 | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 	return 0 | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -1337,15 +1384,27 @@ func (e *SQLEngine) convertRawValueToSQL(value interface{}) sqltypes.Value { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | // findColumnValue performs case-insensitive lookup of column values
 | 
			
		
	
		
			
				
					|  |  |  | func (e *SQLEngine) findColumnValue(values map[string]*schema_pb.Value, columnName string) *schema_pb.Value { | 
			
		
	
		
			
				
					|  |  |  | // Now includes support for system columns stored in HybridScanResult
 | 
			
		
	
		
			
				
					|  |  |  | func (e *SQLEngine) findColumnValue(result HybridScanResult, columnName string) *schema_pb.Value { | 
			
		
	
		
			
				
					|  |  |  | 	// Check system columns first (stored separately in HybridScanResult)
 | 
			
		
	
		
			
				
					|  |  |  | 	lowerColumnName := strings.ToLower(columnName) | 
			
		
	
		
			
				
					|  |  |  | 	switch lowerColumnName { | 
			
		
	
		
			
				
					|  |  |  | 	case "_timestamp_ns", "timestamp_ns": | 
			
		
	
		
			
				
					|  |  |  | 		return &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: result.Timestamp}} | 
			
		
	
		
			
				
					|  |  |  | 	case "_key", "key": | 
			
		
	
		
			
				
					|  |  |  | 		return &schema_pb.Value{Kind: &schema_pb.Value_BytesValue{BytesValue: result.Key}} | 
			
		
	
		
			
				
					|  |  |  | 	case "_source", "source": | 
			
		
	
		
			
				
					|  |  |  | 		return &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: result.Source}} | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 	// Then check regular columns in Values map
 | 
			
		
	
		
			
				
					|  |  |  | 	// First try exact match
 | 
			
		
	
		
			
				
					|  |  |  | 	if value, exists := values[columnName]; exists { | 
			
		
	
		
			
				
					|  |  |  | 	if value, exists := result.Values[columnName]; exists { | 
			
		
	
		
			
				
					|  |  |  | 		return value | 
			
		
	
		
			
				
					|  |  |  | 	} | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 	// Then try case-insensitive match
 | 
			
		
	
		
			
				
					|  |  |  | 	lowerColumnName := strings.ToLower(columnName) | 
			
		
	
		
			
				
					|  |  |  | 	for key, value := range values { | 
			
		
	
		
			
				
					|  |  |  | 	for key, value := range result.Values { | 
			
		
	
		
			
				
					|  |  |  | 		if strings.ToLower(key) == lowerColumnName { | 
			
		
	
		
			
				
					|  |  |  | 			return value | 
			
		
	
		
			
				
					|  |  |  | 		} | 
			
		
	
	
		
			
				
					|  |  | 
 |