diff options
Diffstat (limited to 'weed/query/engine/cockroach_parser.go')
| -rw-r--r-- | weed/query/engine/cockroach_parser.go | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/weed/query/engine/cockroach_parser.go b/weed/query/engine/cockroach_parser.go new file mode 100644 index 000000000..79fd2d94b --- /dev/null +++ b/weed/query/engine/cockroach_parser.go @@ -0,0 +1,408 @@ +package engine + +import ( + "fmt" + "strings" + + "github.com/cockroachdb/cockroachdb-parser/pkg/sql/parser" + "github.com/cockroachdb/cockroachdb-parser/pkg/sql/sem/tree" +) + +// CockroachSQLParser wraps CockroachDB's PostgreSQL-compatible SQL parser for use in SeaweedFS +type CockroachSQLParser struct{} + +// NewCockroachSQLParser creates a new instance of the CockroachDB SQL parser wrapper +func NewCockroachSQLParser() *CockroachSQLParser { + return &CockroachSQLParser{} +} + +// ParseSQL parses a SQL statement using CockroachDB's parser +func (p *CockroachSQLParser) ParseSQL(sql string) (Statement, error) { + // Parse using CockroachDB's parser + stmts, err := parser.Parse(sql) + if err != nil { + return nil, fmt.Errorf("CockroachDB parser error: %v", err) + } + + if len(stmts) != 1 { + return nil, fmt.Errorf("expected exactly one statement, got %d", len(stmts)) + } + + stmt := stmts[0].AST + + // Convert CockroachDB AST to SeaweedFS AST format + switch s := stmt.(type) { + case *tree.Select: + return p.convertSelectStatement(s) + default: + return nil, fmt.Errorf("unsupported statement type: %T", s) + } +} + +// convertSelectStatement converts CockroachDB's Select AST to SeaweedFS format +func (p *CockroachSQLParser) convertSelectStatement(crdbSelect *tree.Select) (*SelectStatement, error) { + selectClause, ok := crdbSelect.Select.(*tree.SelectClause) + if !ok { + return nil, fmt.Errorf("expected SelectClause, got %T", crdbSelect.Select) + } + + seaweedSelect := &SelectStatement{ + SelectExprs: make([]SelectExpr, 0, len(selectClause.Exprs)), + From: []TableExpr{}, + } + + // Convert SELECT expressions + for _, expr := range selectClause.Exprs { + seaweedExpr, err := p.convertSelectExpr(expr) + if err != nil { + return nil, fmt.Errorf("failed to convert select expression: %v", err) + } + seaweedSelect.SelectExprs = append(seaweedSelect.SelectExprs, seaweedExpr) + } + + // Convert FROM clause + if len(selectClause.From.Tables) > 0 { + for _, fromExpr := range selectClause.From.Tables { + seaweedTableExpr, err := p.convertFromExpr(fromExpr) + if err != nil { + return nil, fmt.Errorf("failed to convert FROM clause: %v", err) + } + seaweedSelect.From = append(seaweedSelect.From, seaweedTableExpr) + } + } + + // Convert WHERE clause if present + if selectClause.Where != nil { + whereExpr, err := p.convertExpr(selectClause.Where.Expr) + if err != nil { + return nil, fmt.Errorf("failed to convert WHERE clause: %v", err) + } + seaweedSelect.Where = &WhereClause{ + Expr: whereExpr, + } + } + + // Convert LIMIT and OFFSET clauses if present + if crdbSelect.Limit != nil { + limitClause := &LimitClause{} + + // Convert LIMIT (Count) + if crdbSelect.Limit.Count != nil { + countExpr, err := p.convertExpr(crdbSelect.Limit.Count) + if err != nil { + return nil, fmt.Errorf("failed to convert LIMIT clause: %v", err) + } + limitClause.Rowcount = countExpr + } + + // Convert OFFSET + if crdbSelect.Limit.Offset != nil { + offsetExpr, err := p.convertExpr(crdbSelect.Limit.Offset) + if err != nil { + return nil, fmt.Errorf("failed to convert OFFSET clause: %v", err) + } + limitClause.Offset = offsetExpr + } + + seaweedSelect.Limit = limitClause + } + + return seaweedSelect, nil +} + +// convertSelectExpr converts CockroachDB SelectExpr to SeaweedFS format +func (p *CockroachSQLParser) convertSelectExpr(expr tree.SelectExpr) (SelectExpr, error) { + // Handle star expressions (SELECT *) + if _, isStar := expr.Expr.(tree.UnqualifiedStar); isStar { + return &StarExpr{}, nil + } + + // CockroachDB's SelectExpr is a struct, not an interface, so handle it directly + seaweedExpr := &AliasedExpr{} + + // Convert the main expression + convertedExpr, err := p.convertExpr(expr.Expr) + if err != nil { + return nil, fmt.Errorf("failed to convert expression: %v", err) + } + seaweedExpr.Expr = convertedExpr + + // Convert alias if present + if expr.As != "" { + seaweedExpr.As = aliasValue(expr.As) + } + + return seaweedExpr, nil +} + +// convertExpr converts CockroachDB expressions to SeaweedFS format +func (p *CockroachSQLParser) convertExpr(expr tree.Expr) (ExprNode, error) { + switch e := expr.(type) { + case *tree.FuncExpr: + // Function call + seaweedFunc := &FuncExpr{ + Name: stringValue(strings.ToUpper(e.Func.String())), // Convert to uppercase for consistency + Exprs: make([]SelectExpr, 0, len(e.Exprs)), + } + + // Convert function arguments + for _, arg := range e.Exprs { + // Special case: Handle star expressions in function calls like COUNT(*) + if _, isStar := arg.(tree.UnqualifiedStar); isStar { + seaweedFunc.Exprs = append(seaweedFunc.Exprs, &StarExpr{}) + } else { + convertedArg, err := p.convertExpr(arg) + if err != nil { + return nil, fmt.Errorf("failed to convert function argument: %v", err) + } + seaweedFunc.Exprs = append(seaweedFunc.Exprs, &AliasedExpr{Expr: convertedArg}) + } + } + + return seaweedFunc, nil + + case *tree.BinaryExpr: + // Arithmetic/binary operations (including string concatenation ||) + seaweedArith := &ArithmeticExpr{ + Operator: e.Operator.String(), + } + + // Convert left operand + left, err := p.convertExpr(e.Left) + if err != nil { + return nil, fmt.Errorf("failed to convert left operand: %v", err) + } + seaweedArith.Left = left + + // Convert right operand + right, err := p.convertExpr(e.Right) + if err != nil { + return nil, fmt.Errorf("failed to convert right operand: %v", err) + } + seaweedArith.Right = right + + return seaweedArith, nil + + case *tree.ComparisonExpr: + // Comparison operations (=, >, <, >=, <=, !=, etc.) used in WHERE clauses + seaweedComp := &ComparisonExpr{ + Operator: e.Operator.String(), + } + + // Convert left operand + left, err := p.convertExpr(e.Left) + if err != nil { + return nil, fmt.Errorf("failed to convert comparison left operand: %v", err) + } + seaweedComp.Left = left + + // Convert right operand + right, err := p.convertExpr(e.Right) + if err != nil { + return nil, fmt.Errorf("failed to convert comparison right operand: %v", err) + } + seaweedComp.Right = right + + return seaweedComp, nil + + case *tree.StrVal: + // String literal + return &SQLVal{ + Type: StrVal, + Val: []byte(string(e.RawString())), + }, nil + + case *tree.NumVal: + // Numeric literal + valStr := e.String() + if strings.Contains(valStr, ".") { + return &SQLVal{ + Type: FloatVal, + Val: []byte(valStr), + }, nil + } else { + return &SQLVal{ + Type: IntVal, + Val: []byte(valStr), + }, nil + } + + case *tree.UnresolvedName: + // Column name + return &ColName{ + Name: stringValue(e.String()), + }, nil + + case *tree.AndExpr: + // AND expression + left, err := p.convertExpr(e.Left) + if err != nil { + return nil, fmt.Errorf("failed to convert AND left operand: %v", err) + } + right, err := p.convertExpr(e.Right) + if err != nil { + return nil, fmt.Errorf("failed to convert AND right operand: %v", err) + } + return &AndExpr{ + Left: left, + Right: right, + }, nil + + case *tree.OrExpr: + // OR expression + left, err := p.convertExpr(e.Left) + if err != nil { + return nil, fmt.Errorf("failed to convert OR left operand: %v", err) + } + right, err := p.convertExpr(e.Right) + if err != nil { + return nil, fmt.Errorf("failed to convert OR right operand: %v", err) + } + return &OrExpr{ + Left: left, + Right: right, + }, nil + + case *tree.Tuple: + // Tuple expression for IN clauses: (value1, value2, value3) + tupleValues := make(ValTuple, 0, len(e.Exprs)) + for _, tupleExpr := range e.Exprs { + convertedExpr, err := p.convertExpr(tupleExpr) + if err != nil { + return nil, fmt.Errorf("failed to convert tuple element: %v", err) + } + tupleValues = append(tupleValues, convertedExpr) + } + return tupleValues, nil + + case *tree.CastExpr: + // Handle INTERVAL expressions: INTERVAL '1 hour' + // CockroachDB represents these as cast expressions + if p.isIntervalCast(e) { + // Extract the string value being cast to interval + if strVal, ok := e.Expr.(*tree.StrVal); ok { + return &IntervalExpr{ + Value: string(strVal.RawString()), + }, nil + } + return nil, fmt.Errorf("invalid INTERVAL expression: expected string literal") + } + // For non-interval casts, just convert the inner expression + return p.convertExpr(e.Expr) + + case *tree.RangeCond: + // Handle BETWEEN expressions: column BETWEEN value1 AND value2 + seaweedBetween := &BetweenExpr{ + Not: e.Not, // Handle NOT BETWEEN + } + + // Convert the left operand (the expression being tested) + left, err := p.convertExpr(e.Left) + if err != nil { + return nil, fmt.Errorf("failed to convert BETWEEN left operand: %v", err) + } + seaweedBetween.Left = left + + // Convert the FROM operand (lower bound) + from, err := p.convertExpr(e.From) + if err != nil { + return nil, fmt.Errorf("failed to convert BETWEEN from operand: %v", err) + } + seaweedBetween.From = from + + // Convert the TO operand (upper bound) + to, err := p.convertExpr(e.To) + if err != nil { + return nil, fmt.Errorf("failed to convert BETWEEN to operand: %v", err) + } + seaweedBetween.To = to + + return seaweedBetween, nil + + case *tree.IsNullExpr: + // Handle IS NULL expressions: column IS NULL + expr, err := p.convertExpr(e.Expr) + if err != nil { + return nil, fmt.Errorf("failed to convert IS NULL expression: %v", err) + } + + return &IsNullExpr{ + Expr: expr, + }, nil + + case *tree.IsNotNullExpr: + // Handle IS NOT NULL expressions: column IS NOT NULL + expr, err := p.convertExpr(e.Expr) + if err != nil { + return nil, fmt.Errorf("failed to convert IS NOT NULL expression: %v", err) + } + + return &IsNotNullExpr{ + Expr: expr, + }, nil + + default: + return nil, fmt.Errorf("unsupported expression type: %T", e) + } +} + +// convertFromExpr converts CockroachDB FROM expressions to SeaweedFS format +func (p *CockroachSQLParser) convertFromExpr(expr tree.TableExpr) (TableExpr, error) { + switch e := expr.(type) { + case *tree.TableName: + // Simple table name + tableName := TableName{ + Name: stringValue(e.Table()), + } + + // Extract database qualifier if present + + if e.Schema() != "" { + tableName.Qualifier = stringValue(e.Schema()) + } + + return &AliasedTableExpr{ + Expr: tableName, + }, nil + + case *tree.AliasedTableExpr: + // Handle aliased table expressions (which is what CockroachDB uses for qualified names) + if tableName, ok := e.Expr.(*tree.TableName); ok { + seaweedTableName := TableName{ + Name: stringValue(tableName.Table()), + } + + // Extract database qualifier if present + if tableName.Schema() != "" { + seaweedTableName.Qualifier = stringValue(tableName.Schema()) + } + + return &AliasedTableExpr{ + Expr: seaweedTableName, + }, nil + } + + return nil, fmt.Errorf("unsupported expression in AliasedTableExpr: %T", e.Expr) + + default: + return nil, fmt.Errorf("unsupported table expression type: %T", e) + } +} + +// isIntervalCast checks if a CastExpr is casting to an INTERVAL type +func (p *CockroachSQLParser) isIntervalCast(castExpr *tree.CastExpr) bool { + // Check if the target type is an interval type + // CockroachDB represents interval types in the Type field + // We need to check if it's an interval type by examining the type structure + if castExpr.Type != nil { + // Try to detect interval type by examining the AST structure + // Since we can't easily access the type string, we'll be more conservative + // and assume any cast expression on a string literal could be an interval + if _, ok := castExpr.Expr.(*tree.StrVal); ok { + // This is likely an INTERVAL expression since CockroachDB + // represents INTERVAL '1 hour' as casting a string to interval type + return true + } + } + return false +} |
