Skip to content

Commit ecc5dc1

Browse files
committed
enhances qualify helper functions
1 parent 9d7dffb commit ecc5dc1

5 files changed

Lines changed: 246 additions & 8 deletions

File tree

expr/binary.go

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,82 @@ import (
44
"github.com/viant/sqlparser/node"
55
)
66

7-
//Binary represents binary expr
7+
// Binary represents binary expr
88
type Binary struct {
99
X, Y node.Node
1010
Op string
1111
}
1212

13-
//HasPlaceholder returns true if x or y operand is placeholder
13+
func (b *Binary) Walk(fn func(ident node.Node, values Values, operator, parentOperator string) error) error {
14+
switch b.Op[0] {
15+
case 'A', 'O', 'a', 'o':
16+
if x, ok := b.X.(*Binary); ok {
17+
if err := x.walk(fn, b.Op); err != nil {
18+
return err
19+
}
20+
}
21+
if y, ok := b.Y.(*Binary); ok {
22+
if err := y.walk(fn, b.Op); err != nil {
23+
return err
24+
}
25+
}
26+
return nil
27+
}
28+
return b.walk(fn, "")
29+
30+
}
31+
32+
func (b *Binary) walk(fn func(ident node.Node, values Values, operator, parentOperator string) error, operator string) error {
33+
switch b.Op[0] {
34+
case 'A', 'O':
35+
if x, ok := b.X.(*Binary); ok {
36+
if err := x.walk(fn, b.Op); err != nil {
37+
return err
38+
}
39+
}
40+
if y, ok := b.Y.(*Binary); ok {
41+
if err := y.walk(fn, b.Op); err != nil {
42+
return err
43+
}
44+
}
45+
return nil
46+
}
47+
sel, values, err := b.Predicate()
48+
if err != nil {
49+
return err
50+
}
51+
return fn(sel, values, b.Op, operator)
52+
53+
}
54+
55+
// Predicate binary predicate or nil
56+
func (b *Binary) Predicate() (node.Node, Values, error) {
57+
switch b.Op[0] {
58+
case 'A', 'O':
59+
return nil, nil, nil
60+
}
61+
identifier := b.Identifier()
62+
if identifier == nil {
63+
return nil, nil, nil
64+
}
65+
values, err := b.Values()
66+
if err != nil {
67+
return nil, nil, err
68+
}
69+
return identifier, values, nil
70+
}
71+
72+
// Placeholder returns placeholder
73+
func (b *Binary) Placeholder() *Placeholder {
74+
r, ok := b.X.(*Placeholder)
75+
if ok {
76+
return r
77+
}
78+
r, ok = b.Y.(*Placeholder)
79+
return r
80+
}
81+
82+
// HasPlaceholder returns true if x or y operand is placeholder
1483
func (b *Binary) HasPlaceholder() bool {
1584
if _, ok := b.X.(*Placeholder); ok {
1685
return ok
@@ -19,7 +88,7 @@ func (b *Binary) HasPlaceholder() bool {
1988
return ok
2089
}
2190

22-
//Parenthesis returns parenthesis
91+
// Parenthesis returns parenthesis
2392
func (b *Binary) Parenthesis() *Parenthesis {
2493
if p, ok := b.X.(*Parenthesis); ok {
2594
return p
@@ -28,20 +97,28 @@ func (b *Binary) Parenthesis() *Parenthesis {
2897
return p
2998
}
3099

31-
//HasIdentifier returns true if x or y opperand is identity
100+
// HasIdentifier returns true if x or y opperand is identity
32101
func (b *Binary) HasIdentifier() bool {
33102
return b.Identifier() != nil
34103
}
35104

36-
//Identifier returns an identifier node or nil
105+
// Identifier returns an identifier node or nil
37106
func (b *Binary) Identifier() node.Node {
38107
if x := Identity(b.X); x != nil {
39108
return x
40109
}
41110
return Identity(b.Y)
42111
}
43112

44-
//NewBinary returns a binary expr
113+
// Values returns expression values
114+
func (b *Binary) Values() (Values, error) {
115+
if x := Identity(b.X); x == nil {
116+
return NewValues(b.X)
117+
}
118+
return NewValues(b.Y)
119+
}
120+
121+
// NewBinary returns a binary expr
45122
func NewBinary(x node.Node) *Binary {
46123
return &Binary{X: x}
47124
}

expr/qualify.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package expr
22

33
import "github.com/viant/sqlparser/node"
44

5-
//Qualify represents qualify node
5+
// Qualify represents qualify node
66
type Qualify struct {
77
X node.Node
88
}
99

10-
//NewQualify returns qualify node
10+
// NewQualify returns qualify node
1111
func NewQualify() *Qualify {
1212
return &Qualify{}
1313
}

expr/values.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package expr
2+
3+
import (
4+
"fmt"
5+
"github.com/viant/sqlparser/node"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
type (
11+
//Value represents predicate value
12+
Value struct {
13+
Placeholder bool
14+
Raw string
15+
Value interface{}
16+
Kind string
17+
}
18+
Values []Value
19+
)
20+
21+
// NewValues creates predicate values
22+
func NewValues(n node.Node) (Values, error) {
23+
var values Values
24+
switch actual := n.(type) {
25+
case *Placeholder:
26+
return append(values, Value{Placeholder: true}), nil
27+
case *Literal:
28+
switch actual.Kind {
29+
case "int":
30+
v, err := strconv.Atoi(actual.Value)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return append(values, Value{Value: v, Kind: actual.Kind}), nil
35+
case "null":
36+
return append(values, Value{Value: nil, Kind: actual.Kind}), nil
37+
case "string":
38+
return append(values, Value{Value: strings.Trim(actual.Value, "'"), Kind: actual.Kind}), nil
39+
case "numeric":
40+
v, err := strconv.ParseFloat(actual.Value, 64)
41+
if err != nil {
42+
return nil, err
43+
}
44+
return append(values, Value{Value: v, Kind: actual.Kind}), nil
45+
}
46+
case *Parenthesis:
47+
list, ok := actual.X.([]node.Node)
48+
if ok {
49+
for _, item := range list {
50+
v, err := NewValues(item)
51+
if err != nil {
52+
return nil, err
53+
}
54+
values = append(values, v...)
55+
}
56+
return values, nil
57+
}
58+
}
59+
return nil, fmt.Errorf("unsupported value node: %T", n)
60+
}

operand.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ func expectOperand(cursor *parsly.Cursor) (node.Node, error) {
109109
result.X = result.X
110110
if binary.Y != nil {
111111
result.X = binary
112+
} else {
113+
exprCursor := parsly.NewCursor(cursor.Path, []byte(rawExpr), cursor.Pos-len(raw))
114+
115+
var list []node.Node
116+
tokens := append([]*parsly.Token{placeholderMatcher}, literalTokens...)
117+
for i := 0; i < len(rawExpr); i++ {
118+
matched := exprCursor.MatchAfterOptional(whitespaceMatcher, tokens...)
119+
switch matched.Code {
120+
case nextCode:
121+
case placeholderTokenCode:
122+
list = append(list, &expr.Placeholder{Name: matched.Text(exprCursor)})
123+
case nullKeyword:
124+
list = append(list, expr.NewNullLiteral(matched.Text(exprCursor)))
125+
case singleQuotedStringLiteral, doubleQuotedStringLiteral:
126+
list = append(list, expr.NewStringLiteral(matched.Text(exprCursor)))
127+
case boolLiteral:
128+
list = append(list, expr.NewBoolLiteral(matched.Text(exprCursor)))
129+
case intLiteral:
130+
list = append(list, expr.NewIntLiteral(matched.Text(exprCursor)))
131+
case numericLiteral:
132+
list = append(list, expr.NewNumericLiteral(matched.Text(exprCursor)))
133+
default:
134+
break
135+
}
136+
}
137+
if len(list) > 0 {
138+
result.X = list
139+
}
140+
112141
}
113142
return result, nil
114143
case notOperator:

qualify_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package sqlparser
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"github.com/viant/parsly"
6+
"github.com/viant/sqlparser/expr"
7+
"github.com/viant/sqlparser/node"
8+
"testing"
9+
)
10+
11+
func TestParseQualify(t *testing.T) {
12+
var testCases = []struct {
13+
expr string
14+
expectEq map[string]interface{}
15+
expectParentOp string
16+
}{
17+
{
18+
expr: "col1 = ?",
19+
expectEq: map[string]interface{}{
20+
"col1": "?",
21+
},
22+
},
23+
{
24+
expr: "col1 in(1.0,2.0,3.0)",
25+
expectEq: map[string]interface{}{
26+
"col1": []interface{}{1.0, 2.0, 3.0},
27+
},
28+
},
29+
}
30+
31+
for _, testCase := range testCases[1:] {
32+
cursor := parsly.NewCursor("", []byte(testCase.expr), 0)
33+
qualify := &expr.Qualify{}
34+
err := ParseQualify(cursor, qualify)
35+
if !assert.Nil(t, err) {
36+
continue
37+
}
38+
actualEq := make(map[string]interface{})
39+
err = qualify.X.(*expr.Binary).Walk(func(ident node.Node, values expr.Values, operator, parentOperator string) error {
40+
value := toValues(values)
41+
actualEq[Stringify(ident)] = value
42+
return nil
43+
})
44+
if !assert.Nil(t, err) {
45+
continue
46+
}
47+
assert.EqualValues(t, testCase.expectEq, actualEq)
48+
}
49+
50+
}
51+
52+
func toValues(values expr.Values) interface{} {
53+
var value interface{}
54+
if len(values) == 1 {
55+
if values[0].Placeholder {
56+
value = "?"
57+
} else {
58+
value = values[0].Value
59+
}
60+
} else {
61+
aSlice := make([]interface{}, len(values))
62+
for i, v := range values {
63+
if v.Placeholder {
64+
aSlice[i] = "?"
65+
} else {
66+
aSlice[i] = v.Value
67+
}
68+
}
69+
value = aSlice
70+
}
71+
return value
72+
}

0 commit comments

Comments
 (0)