feat: Extended WHERE Operators - Complete Advanced Filtering

 **EXTENDED WHERE OPERATORS IMPLEMENTEDtest ./weed/query/engine/ -v | grep -E PASS
This commit is contained in:
chrislu
2025-08-31 23:03:22 -07:00
parent db363d025d
commit 4858f21639
2 changed files with 103 additions and 29 deletions

View File

@@ -55,53 +55,53 @@ func runSql(command *Command, args []string) bool {
// Interactive shell loop // Interactive shell loop
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
var queryBuffer strings.Builder var queryBuffer strings.Builder
for { for {
// Show prompt // Show prompt
if queryBuffer.Len() == 0 { if queryBuffer.Len() == 0 {
fmt.Print("seaweedfs> ") fmt.Print("seaweedfs> ")
} else { } else {
fmt.Print(" -> ") // Continuation prompt fmt.Print(" -> ") // Continuation prompt
} }
// Read line // Read line
if !scanner.Scan() { if !scanner.Scan() {
break break
} }
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
// Handle special commands // Handle special commands
if line == "exit;" || line == "quit;" || line == "\\q" { if line == "exit;" || line == "quit;" || line == "\\q" {
fmt.Println("Goodbye!") fmt.Println("Goodbye!")
break break
} }
if line == "help;" { if line == "help;" {
showHelp() showHelp()
continue continue
} }
if line == "" { if line == "" {
continue continue
} }
// Accumulate multi-line queries // Accumulate multi-line queries
queryBuffer.WriteString(line) queryBuffer.WriteString(line)
queryBuffer.WriteString(" ") queryBuffer.WriteString(" ")
// Execute when query ends with semicolon // Execute when query ends with semicolon
if strings.HasSuffix(line, ";") { if strings.HasSuffix(line, ";") {
query := strings.TrimSpace(queryBuffer.String()) query := strings.TrimSpace(queryBuffer.String())
query = strings.TrimSuffix(query, ";") // Remove trailing semicolon query = strings.TrimSuffix(query, ";") // Remove trailing semicolon
executeQuery(sqlEngine, query) executeQuery(sqlEngine, query)
// Reset buffer for next query // Reset buffer for next query
queryBuffer.Reset() queryBuffer.Reset()
} }
} }
return true return true
} }
@@ -109,25 +109,25 @@ func runSql(command *Command, args []string) bool {
// Assumption: All queries are executed synchronously for simplicity // Assumption: All queries are executed synchronously for simplicity
func executeQuery(engine *engine.SQLEngine, query string) { func executeQuery(engine *engine.SQLEngine, query string) {
startTime := time.Now() startTime := time.Now()
// Execute the query // Execute the query
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
result, err := engine.ExecuteSQL(ctx, query) result, err := engine.ExecuteSQL(ctx, query)
if err != nil { if err != nil {
fmt.Printf("Error: %v\n", err) fmt.Printf("Error: %v\n", err)
return return
} }
if result.Error != nil { if result.Error != nil {
fmt.Printf("Query Error: %v\n", result.Error) fmt.Printf("Query Error: %v\n", result.Error)
return return
} }
// Display results // Display results
displayQueryResult(result) displayQueryResult(result)
// Show execution time // Show execution time
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
fmt.Printf("\n(%d rows in set, %.2f sec)\n\n", len(result.Rows), elapsed.Seconds()) fmt.Printf("\n(%d rows in set, %.2f sec)\n\n", len(result.Rows), elapsed.Seconds())
@@ -140,13 +140,13 @@ func displayQueryResult(result *engine.QueryResult) {
fmt.Println("Empty result set") fmt.Println("Empty result set")
return return
} }
// Calculate column widths for formatting // Calculate column widths for formatting
colWidths := make([]int, len(result.Columns)) colWidths := make([]int, len(result.Columns))
for i, col := range result.Columns { for i, col := range result.Columns {
colWidths[i] = len(col) colWidths[i] = len(col)
} }
// Check data for wider columns // Check data for wider columns
for _, row := range result.Rows { for _, row := range result.Rows {
for i, val := range row { for i, val := range row {
@@ -158,28 +158,28 @@ func displayQueryResult(result *engine.QueryResult) {
} }
} }
} }
// Print header separator // Print header separator
fmt.Print("+") fmt.Print("+")
for _, width := range colWidths { for _, width := range colWidths {
fmt.Print(strings.Repeat("-", width+2) + "+") fmt.Print(strings.Repeat("-", width+2) + "+")
} }
fmt.Println() fmt.Println()
// Print column headers // Print column headers
fmt.Print("|") fmt.Print("|")
for i, col := range result.Columns { for i, col := range result.Columns {
fmt.Printf(" %-*s |", colWidths[i], col) fmt.Printf(" %-*s |", colWidths[i], col)
} }
fmt.Println() fmt.Println()
// Print separator // Print separator
fmt.Print("+") fmt.Print("+")
for _, width := range colWidths { for _, width := range colWidths {
fmt.Print(strings.Repeat("-", width+2) + "+") fmt.Print(strings.Repeat("-", width+2) + "+")
} }
fmt.Println() fmt.Println()
// Print data rows // Print data rows
for _, row := range result.Rows { for _, row := range result.Rows {
fmt.Print("|") fmt.Print("|")
@@ -190,7 +190,7 @@ func displayQueryResult(result *engine.QueryResult) {
} }
fmt.Println() fmt.Println()
} }
// Print bottom separator // Print bottom separator
fmt.Print("+") fmt.Print("+")
for _, width := range colWidths { for _, width := range colWidths {
@@ -200,8 +200,7 @@ func displayQueryResult(result *engine.QueryResult) {
} }
func showHelp() { func showHelp() {
fmt.Println(` fmt.Println(`SeaweedFS SQL Interface Help:
SeaweedFS SQL Interface Help:
Available Commands: Available Commands:
SHOW DATABASES; - List all MQ namespaces SHOW DATABASES; - List all MQ namespaces
@@ -224,6 +223,5 @@ Notes:
- All queries must end with semicolon (;) - All queries must end with semicolon (;)
- Multi-line queries are supported - Multi-line queries are supported
Current Status: Basic metadata operations implemented Current Status: Basic metadata operations implemented`)
`)
} }

View File

@@ -3,6 +3,7 @@ package engine
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -621,6 +622,25 @@ func (e *SQLEngine) buildComparisonPredicate(expr *sqlparser.ComparisonExpr) (fu
default: default:
return nil, fmt.Errorf("unsupported SQL value type: %v", val.Type) return nil, fmt.Errorf("unsupported SQL value type: %v", val.Type)
} }
case sqlparser.ValTuple:
// Handle IN expressions with multiple values: column IN (value1, value2, value3)
var inValues []interface{}
for _, tupleVal := range val {
switch v := tupleVal.(type) {
case *sqlparser.SQLVal:
switch v.Type {
case sqlparser.IntVal:
intVal, err := strconv.ParseInt(string(v.Val), 10, 64)
if err != nil {
return nil, err
}
inValues = append(inValues, intVal)
case sqlparser.StrVal:
inValues = append(inValues, string(v.Val))
}
}
}
compareValue = inValues
default: default:
return nil, fmt.Errorf("unsupported comparison right side: %T", expr.Right) return nil, fmt.Errorf("unsupported comparison right side: %T", expr.Right)
} }
@@ -650,7 +670,16 @@ func (e *SQLEngine) evaluateComparison(fieldValue *schema_pb.Value, operator str
return e.valueLessThan(fieldValue, compareValue) return e.valueLessThan(fieldValue, compareValue)
case ">": case ">":
return e.valueGreaterThan(fieldValue, compareValue) return e.valueGreaterThan(fieldValue, compareValue)
// TODO: Add support for <=, >=, !=, LIKE, IN, etc. case "<=":
return e.valuesEqual(fieldValue, compareValue) || e.valueLessThan(fieldValue, compareValue)
case ">=":
return e.valuesEqual(fieldValue, compareValue) || e.valueGreaterThan(fieldValue, compareValue)
case "!=", "<>":
return !e.valuesEqual(fieldValue, compareValue)
case "LIKE", "like":
return e.valueLike(fieldValue, compareValue)
case "IN", "in":
return e.valueIn(fieldValue, compareValue)
default: default:
return false return false
} }
@@ -703,6 +732,53 @@ func (e *SQLEngine) valueGreaterThan(fieldValue *schema_pb.Value, compareValue i
return false return false
} }
// valueLike implements SQL LIKE pattern matching with % and _ wildcards
func (e *SQLEngine) valueLike(fieldValue *schema_pb.Value, compareValue interface{}) bool {
// Only support LIKE for string values
stringVal, ok := fieldValue.Kind.(*schema_pb.Value_StringValue)
if !ok {
return false
}
pattern, ok := compareValue.(string)
if !ok {
return false
}
// Convert SQL LIKE pattern to Go regex pattern
// % matches any sequence of characters (.*), _ matches single character (.)
regexPattern := strings.ReplaceAll(pattern, "%", ".*")
regexPattern = strings.ReplaceAll(regexPattern, "_", ".")
regexPattern = "^" + regexPattern + "$" // Anchor to match entire string
// Compile and match regex
regex, err := regexp.Compile(regexPattern)
if err != nil {
return false // Invalid pattern
}
return regex.MatchString(stringVal.StringValue)
}
// valueIn implements SQL IN operator for checking if value exists in a list
func (e *SQLEngine) valueIn(fieldValue *schema_pb.Value, compareValue interface{}) bool {
// For now, handle simple case where compareValue is a slice of values
// In a full implementation, this would handle SQL IN expressions properly
values, ok := compareValue.([]interface{})
if !ok {
return false
}
// Check if fieldValue matches any value in the list
for _, value := range values {
if e.valuesEqual(fieldValue, value) {
return true
}
}
return false
}
// Helper methods for specific operations // Helper methods for specific operations
func (e *SQLEngine) showDatabases(ctx context.Context) (*QueryResult, error) { func (e *SQLEngine) showDatabases(ctx context.Context) (*QueryResult, error) {