aboutsummaryrefslogtreecommitdiff
path: root/weed/query/engine/cockroach_parser.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/query/engine/cockroach_parser.go')
-rw-r--r--weed/query/engine/cockroach_parser.go408
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
+}