Skip to content

Commit 7ed4e94

Browse files
authored
Merge pull request #80 from ulule/feat/values
feat: handle bidirectional operator
2 parents be8dac3 + dc0a656 commit 7ed4e94

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

builder/select_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,93 @@ import (
99
"github.com/ulule/loukoum/v3/stmt"
1010
)
1111

12+
func TestSelect_Value(t *testing.T) {
13+
RunBuilderTests(t, []BuilderTest{
14+
{
15+
Name: "ValueEqual",
16+
Builder: loukoum.Select("id").
17+
From("video").
18+
Where(
19+
loukoum.Value("fr").
20+
Equal(loukoum.Raw("ANY(string_to_array(languages, ','))")),
21+
),
22+
String: "SELECT id FROM video WHERE ('fr' = ANY(string_to_array(languages, ',')))",
23+
Query: "SELECT id FROM video WHERE ($1 = ANY(string_to_array(languages, ',')))",
24+
NamedQuery: "SELECT id FROM video WHERE (:arg_1 = ANY(string_to_array(languages, ',')))",
25+
Args: []interface{}{"fr"},
26+
},
27+
{
28+
Name: "ValueNotEqual",
29+
Builder: loukoum.Select("id").
30+
From("video").
31+
Where(
32+
loukoum.Value("fr").
33+
NotEqual(loukoum.Raw("ANY(string_to_array(languages, ','))")),
34+
),
35+
String: "SELECT id FROM video WHERE ('fr' != ANY(string_to_array(languages, ',')))",
36+
Query: "SELECT id FROM video WHERE ($1 != ANY(string_to_array(languages, ',')))",
37+
NamedQuery: "SELECT id FROM video WHERE (:arg_1 != ANY(string_to_array(languages, ',')))",
38+
Args: []interface{}{"fr"},
39+
},
40+
{
41+
Name: "ValueOverlap",
42+
Builder: loukoum.Select("id").
43+
From("video").
44+
Where(
45+
loukoum.Value("{fr}").
46+
Overlap(loukoum.Raw("languages")),
47+
),
48+
String: "SELECT id FROM video WHERE ('{fr}' && languages)",
49+
Query: "SELECT id FROM video WHERE ($1 && languages)",
50+
NamedQuery: "SELECT id FROM video WHERE (:arg_1 && languages)",
51+
Args: []interface{}{"{fr}"},
52+
},
53+
{
54+
Name: "ValueIsContainedBy",
55+
Builder: loukoum.Select("id").
56+
From("video").
57+
Where(
58+
loukoum.Value("{fr}").
59+
IsContainedBy(loukoum.Raw("languages")),
60+
),
61+
String: "SELECT id FROM video WHERE ('{fr}' <@ languages)",
62+
Query: "SELECT id FROM video WHERE ($1 <@ languages)",
63+
NamedQuery: "SELECT id FROM video WHERE (:arg_1 <@ languages)",
64+
Args: []interface{}{"{fr}"},
65+
},
66+
{
67+
Name: "IdentifierContains",
68+
Builder: loukoum.Select("id").
69+
From("video").
70+
Where(loukoum.Condition("languages").Contains("{fr}")),
71+
String: "SELECT id FROM video WHERE (languages @> '{fr}')",
72+
Query: "SELECT id FROM video WHERE (languages @> $1)",
73+
NamedQuery: "SELECT id FROM video WHERE (languages @> :arg_1)",
74+
Args: []interface{}{"{fr}"},
75+
},
76+
{
77+
Name: "IdentifierIsContainsBy",
78+
Builder: loukoum.Select("id").
79+
From("video").
80+
Where(loukoum.Condition("languages").IsContainedBy("{fr}")),
81+
String: "SELECT id FROM video WHERE (languages <@ '{fr}')",
82+
Query: "SELECT id FROM video WHERE (languages <@ $1)",
83+
NamedQuery: "SELECT id FROM video WHERE (languages <@ :arg_1)",
84+
Args: []interface{}{"{fr}"},
85+
},
86+
{
87+
Name: "IdentifierOverlap",
88+
Builder: loukoum.Select("id").
89+
From("video").
90+
Where(loukoum.Condition("languages").Overlap("{fr}")),
91+
String: "SELECT id FROM video WHERE (languages && '{fr}')",
92+
Query: "SELECT id FROM video WHERE (languages && $1)",
93+
NamedQuery: "SELECT id FROM video WHERE (languages && :arg_1)",
94+
Args: []interface{}{"{fr}"},
95+
},
96+
})
97+
}
98+
1299
func TestSelect_Columns(t *testing.T) {
13100
RunBuilderTests(t, []BuilderTest{
14101
{

loukoum.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ func Pair(key, value interface{}) types.Pair {
3131
return types.Pair{Key: key, Value: value}
3232
}
3333

34+
// Value is a wrapper to create a new Value expression.
35+
func Value(value interface{}) stmt.Value {
36+
return stmt.NewValue(value)
37+
}
38+
3439
// Select starts a SelectBuilder using the given columns.
3540
func Select(columns ...interface{}) builder.Select {
3641
return builder.NewSelect().Columns(columns...)

stmt/expression.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ func (identifier Identifier) IsEmpty() bool {
7979
return identifier.Identifier == ""
8080
}
8181

82+
// Contains performs a "contains" comparison.
83+
func (identifier Identifier) Contains(value interface{}) InfixExpression {
84+
operator := NewComparisonOperator(types.Contains)
85+
return NewInfixExpression(identifier, operator, NewWrapper(NewExpression(value)))
86+
}
87+
88+
// IsContainedBy performs a "is contained by" comparison.
89+
func (identifier Identifier) IsContainedBy(value interface{}) InfixExpression {
90+
operator := NewComparisonOperator(types.IsContainedBy)
91+
return NewInfixExpression(identifier, operator, NewWrapper(NewExpression(value)))
92+
}
93+
94+
// Overlap performs an "overlap" comparison.
95+
func (identifier Identifier) Overlap(value interface{}) InfixExpression {
96+
operator := NewComparisonOperator(types.Overlap)
97+
return NewInfixExpression(identifier, operator, NewWrapper(NewExpression(value)))
98+
}
99+
82100
// Equal performs an "equal" comparison.
83101
func (identifier Identifier) Equal(value interface{}) InfixExpression {
84102
operator := NewComparisonOperator(types.Equal)
@@ -209,6 +227,36 @@ func (value Value) Write(ctx types.Context) {
209227
}
210228
}
211229

230+
// Overlap performs an "overlap" comparison.
231+
func (value Value) Overlap(what interface{}) InfixExpression {
232+
operator := NewComparisonOperator(types.Overlap)
233+
return NewInfixExpression(value, operator, NewWrapper(NewExpression(what)))
234+
}
235+
236+
// Equal performs an "equal" comparison.
237+
func (value Value) Equal(what interface{}) InfixExpression {
238+
operator := NewComparisonOperator(types.Equal)
239+
return NewInfixExpression(value, operator, NewWrapper(NewExpression(what)))
240+
}
241+
242+
// NotEqual performs a "not equal" comparison.
243+
func (value Value) NotEqual(what interface{}) InfixExpression {
244+
operator := NewComparisonOperator(types.NotEqual)
245+
return NewInfixExpression(value, operator, NewWrapper(NewExpression(what)))
246+
}
247+
248+
// Contains performs a "contains" comparison.
249+
func (value Value) Contains(what interface{}) InfixExpression {
250+
operator := NewComparisonOperator(types.Contains)
251+
return NewInfixExpression(value, operator, NewWrapper(NewExpression(what)))
252+
}
253+
254+
// IsContainedBy performs a "is contained by" comparison.
255+
func (value Value) IsContainedBy(what interface{}) InfixExpression {
256+
operator := NewComparisonOperator(types.IsContainedBy)
257+
return NewInfixExpression(value, operator, NewWrapper(NewExpression(what)))
258+
}
259+
212260
// IsEmpty returns true if statement is undefined.
213261
func (value Value) IsEmpty() bool {
214262
return false

types/operator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,7 @@ const (
3939
NotILike = ComparisonOperator("NOT ILIKE")
4040
Between = ComparisonOperator("BETWEEN")
4141
NotBetween = ComparisonOperator("NOT BETWEEN")
42+
Contains = ComparisonOperator("@>")
43+
IsContainedBy = ComparisonOperator("<@")
44+
Overlap = ComparisonOperator("&&")
4245
)

0 commit comments

Comments
 (0)