diff --git a/weed/query/engine/engine.go b/weed/query/engine/engine.go index 6c1ac0d4c..4b07acf45 100644 --- a/weed/query/engine/engine.go +++ b/weed/query/engine/engine.go @@ -71,10 +71,11 @@ type TypeRef struct { func (d *DDLStatement) isStatement() {} type SelectStatement struct { - SelectExprs []SelectExpr - From []TableExpr - Where *WhereClause - Limit *LimitClause + SelectExprs []SelectExpr + From []TableExpr + Where *WhereClause + Limit *LimitClause + WindowFunctions []*WindowFunction } type WhereClause struct { @@ -87,6 +88,24 @@ type LimitClause struct { func (s *SelectStatement) isStatement() {} +// Window function types for time-series analytics +type WindowSpec struct { + PartitionBy []ExprNode + OrderBy []*OrderByClause +} + +type WindowFunction struct { + Function string // ROW_NUMBER, RANK, LAG, LEAD + Args []ExprNode // Function arguments + Over *WindowSpec + Alias string // Column alias for the result +} + +type OrderByClause struct { + Column string + Order string // ASC or DESC +} + type SelectExpr interface { isSelectExpr() } @@ -2004,13 +2023,13 @@ func (e *SQLEngine) decimalToString(decimalValue *schema_pb.DecimalValue) string if decimalValue == nil || decimalValue.Value == nil { return "0" } - + // Convert bytes back to big.Int intValue := new(big.Int).SetBytes(decimalValue.Value) - + // Convert to string with proper decimal placement str := intValue.String() - + // Handle decimal placement based on scale scale := int(decimalValue.Scale) if scale > 0 && len(str) > scale { @@ -2018,7 +2037,7 @@ func (e *SQLEngine) decimalToString(decimalValue *schema_pb.DecimalValue) string decimalPos := len(str) - scale return str[:decimalPos] + "." + str[decimalPos:] } - + return str } diff --git a/weed/query/engine/window_functions_demo.go b/weed/query/engine/window_functions_demo.go new file mode 100644 index 000000000..1f958e1c9 --- /dev/null +++ b/weed/query/engine/window_functions_demo.go @@ -0,0 +1,90 @@ +package engine + +import ( + "sort" + "time" + + "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb" +) + +// WindowFunctionDemo demonstrates basic window function concepts for timestamp-based analytics +// This provides a foundation for full window function implementation + +// ApplyRowNumber applies ROW_NUMBER() OVER (ORDER BY timestamp) to a result set +func (e *SQLEngine) ApplyRowNumber(results []HybridScanResult, orderByColumn string) []HybridScanResult { + // Sort results by timestamp if ordering by timestamp-related fields + if orderByColumn == "timestamp" || orderByColumn == "_timestamp_ns" { + sort.Slice(results, func(i, j int) bool { + return results[i].Timestamp < results[j].Timestamp + }) + } + + // Add ROW_NUMBER as a synthetic column + for i := range results { + if results[i].Values == nil { + results[i].Values = make(map[string]*schema_pb.Value) + } + results[i].Values["row_number"] = &schema_pb.Value{ + Kind: &schema_pb.Value_Int64Value{Int64Value: int64(i + 1)}, + } + } + + return results +} + +// ExtractYear extracts the year from a TIMESTAMP logical type +func (e *SQLEngine) ExtractYear(timestampValue *schema_pb.TimestampValue) int { + if timestampValue == nil { + return 0 + } + + // Convert microseconds to seconds and create time + t := time.Unix(timestampValue.TimestampMicros/1_000_000, 0) + return t.Year() +} + +// ExtractMonth extracts the month from a TIMESTAMP logical type +func (e *SQLEngine) ExtractMonth(timestampValue *schema_pb.TimestampValue) int { + if timestampValue == nil { + return 0 + } + + t := time.Unix(timestampValue.TimestampMicros/1_000_000, 0) + return int(t.Month()) +} + +// ExtractDay extracts the day from a TIMESTAMP logical type +func (e *SQLEngine) ExtractDay(timestampValue *schema_pb.TimestampValue) int { + if timestampValue == nil { + return 0 + } + + t := time.Unix(timestampValue.TimestampMicros/1_000_000, 0) + return t.Day() +} + +// FilterByYear demonstrates filtering TIMESTAMP values by year +func (e *SQLEngine) FilterByYear(results []HybridScanResult, targetYear int) []HybridScanResult { + var filtered []HybridScanResult + + for _, result := range results { + if timestampField := result.Values["timestamp"]; timestampField != nil { + if timestampVal, ok := timestampField.Kind.(*schema_pb.Value_TimestampValue); ok { + year := e.ExtractYear(timestampVal.TimestampValue) + if year == targetYear { + filtered = append(filtered, result) + } + } + } + } + + return filtered +} + +// This demonstrates the foundation for more complex window functions like: +// - LAG(value, offset) OVER (ORDER BY timestamp) - Access previous row value +// - LEAD(value, offset) OVER (ORDER BY timestamp) - Access next row value +// - RANK() OVER (ORDER BY timestamp) - Ranking with gaps for ties +// - DENSE_RANK() OVER (ORDER BY timestamp) - Ranking without gaps +// - FIRST_VALUE(value) OVER (PARTITION BY category ORDER BY timestamp) - First value in window +// - LAST_VALUE(value) OVER (PARTITION BY category ORDER BY timestamp) - Last value in window