Skip to content

Commit 425a642

Browse files
committed
Added search engine
1 parent d6d05cb commit 425a642

File tree

8 files changed

+980
-0
lines changed

8 files changed

+980
-0
lines changed

db/db.go

+9
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ type Recommendation struct {
339339
RecommendedVal int64
340340
}
341341

342+
// BadInputError Special error indicating bad user input as opposed to a database error
343+
type BadInputError struct {
344+
Details error
345+
}
346+
347+
func (bi BadInputError) Error() string {
348+
return bi.Details.Error()
349+
}
350+
342351
// DBType - database type
343352
type DBType struct {
344353
Driver DialectName // driver name (used in the code)

db/helpers.go

+4
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ func ParseScheme(s string) (scheme string, uri string, err error) {
277277
return parts[0], parts[1], nil
278278
}
279279

280+
func FormatTimeStamp(timestamp time.Time) string {
281+
return fmt.Sprintf("%vns", timestamp.UTC().UnixNano())
282+
}
283+
280284
// Cond represents a condition
281285
type Cond struct {
282286
Col string

db/search/compare.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package search
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/acronis/perfkit/db"
7+
)
8+
9+
type comparator[T Searchable[T]] func(a, b T) bool
10+
type comparators[T Searchable[T]] map[string]map[string]comparator[T]
11+
12+
func makeComparator[T Searchable[T]](values []string, comparable comparators[T]) (comparator[T], error) {
13+
var less func(a, b T) bool
14+
if len(values) == 0 {
15+
return less, nil
16+
}
17+
18+
var finalLess func(a, b T) bool
19+
20+
for i := len(values) - 1; i >= 0; i-- {
21+
value := values[i]
22+
23+
fnc, field, err := db.ParseFunc(value)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
if fnc == "" {
29+
return nil, fmt.Errorf("empty order function")
30+
}
31+
32+
if field == "" {
33+
return nil, fmt.Errorf("empty order field")
34+
}
35+
36+
fieldComparators, ok := comparable[field]
37+
if !ok {
38+
return nil, fmt.Errorf("bad order field '%v'", field)
39+
}
40+
41+
less, ok := fieldComparators[fnc]
42+
if !ok {
43+
return nil, fmt.Errorf("bad order function '%v'", fnc)
44+
}
45+
46+
if finalLess == nil {
47+
finalLess = less
48+
} else {
49+
var deepLess = finalLess
50+
51+
finalLess = func(a, b T) bool {
52+
if less(a, b) {
53+
return true
54+
} else if less(b, a) {
55+
return false
56+
}
57+
58+
return deepLess(a, b)
59+
}
60+
}
61+
}
62+
63+
return finalLess, nil
64+
}

db/search/cursor.go

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Package search provides search/filter building functionality for task-manager
2+
// nolint:goconst // TODO: Not sure that we need some consts here
3+
package search
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/acronis/perfkit/db"
9+
)
10+
11+
type sorting struct {
12+
Field string
13+
Func string
14+
}
15+
16+
func uniqueSort(encodedSorts []string, uniqueFields map[string]bool, cursors map[string]string) ([]string, []sorting, error) {
17+
var hasUniqueSorting = false
18+
var uniqueOrderDirection int
19+
20+
var encoded []string
21+
var sorts []sorting
22+
23+
for _, v := range encodedSorts {
24+
var fnc, field, err = db.ParseFunc(v)
25+
if err != nil {
26+
return nil, nil, err
27+
}
28+
29+
var nullable, unique = uniqueFields[field]
30+
hasUniqueSorting = hasUniqueSorting || (unique && !nullable)
31+
32+
encoded = append(encoded, v)
33+
sorts = append(sorts, sorting{
34+
Field: field,
35+
Func: fnc,
36+
})
37+
38+
switch fnc {
39+
case "asc":
40+
uniqueOrderDirection++
41+
case "desc":
42+
uniqueOrderDirection--
43+
}
44+
45+
if unique {
46+
if !nullable {
47+
break
48+
} else if cursors != nil {
49+
if val, ok := cursors[field]; ok && val != db.SpecialConditionIsNull {
50+
if fnc != "desc" {
51+
break
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
if !hasUniqueSorting {
59+
if uniqueOrderDirection >= 0 {
60+
encoded = append(encoded, "asc(id)")
61+
sorts = append(sorts, sorting{Field: "id", Func: "asc"})
62+
} else {
63+
encoded = append(encoded, "desc(id)")
64+
sorts = append(sorts, sorting{Field: "id", Func: "desc"})
65+
}
66+
}
67+
68+
return encoded, sorts, nil
69+
}
70+
71+
func orderCondition(val, fnc string) (expr string, flag bool, err error) {
72+
var direction string
73+
switch fnc {
74+
case "asc":
75+
switch val {
76+
case db.SpecialConditionIsNull:
77+
return db.SpecialConditionIsNotNull, false, nil
78+
case db.SpecialConditionIsNotNull:
79+
return "", true, nil
80+
default:
81+
direction = "gt"
82+
}
83+
case "desc":
84+
switch val {
85+
case db.SpecialConditionIsNotNull:
86+
return db.SpecialConditionIsNull, false, nil
87+
case db.SpecialConditionIsNull:
88+
return "", true, nil
89+
default:
90+
direction = "lt"
91+
}
92+
default:
93+
return "", false, fmt.Errorf("missing ordering for cursor")
94+
}
95+
96+
return fmt.Sprintf("%s(%v)", direction, val), false, nil
97+
}
98+
99+
func splitQueryOnLightWeightQueries(pt PageToken, uniqueFields map[string]bool) ([]PageToken, error) {
100+
var tokens []PageToken
101+
102+
if len(pt.Fields) == 0 {
103+
tokens = append(tokens, pt)
104+
return tokens, nil
105+
}
106+
107+
// check for unique sorting
108+
var encodedSorts, sorts, err = uniqueSort(pt.Order, uniqueFields, pt.Cursor)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
if len(pt.Cursor) == 0 {
114+
pt.Order = encodedSorts
115+
tokens = append(tokens, pt)
116+
return tokens, nil
117+
}
118+
119+
// construct sort map for fast access
120+
var orderFunctions = map[string]string{}
121+
for _, sort := range sorts {
122+
orderFunctions[sort.Field] = sort.Func
123+
}
124+
125+
// add condition based on cursor
126+
var whereFromCursor = func(fld, val string, pt *PageToken) (bool, error) {
127+
var filter, empty, filterErr = orderCondition(val, orderFunctions[fld])
128+
if filterErr != nil {
129+
return false, filterErr
130+
}
131+
132+
if empty {
133+
return true, nil
134+
}
135+
136+
pt.Filter[fld] = append(pt.Filter[fld], filter)
137+
return false, nil
138+
}
139+
140+
for cursor := range pt.Cursor {
141+
if _, ok := orderFunctions[cursor]; !ok {
142+
return nil, fmt.Errorf("prohibited cursor, not mentioned it order: %v", cursor)
143+
}
144+
}
145+
146+
// split to x page tokens
147+
for i := range sorts {
148+
var cpt = pt
149+
var last = len(sorts) - 1 - i
150+
151+
// copy filters
152+
cpt.Filter = make(map[string][]string, len(sorts)-1-i)
153+
for k, v := range pt.Filter {
154+
cpt.Filter[k] = v
155+
}
156+
157+
// add equal condition on all fields except last in sorts
158+
for j := 0; j <= last-1; j++ {
159+
var fld = sorts[j].Field
160+
var val = pt.Cursor[fld]
161+
162+
cpt.Filter[fld] = append(cpt.Filter[fld], val)
163+
}
164+
165+
// add gt / lt condition for last sorting
166+
var empty bool
167+
if val, ok := cpt.Cursor[sorts[last].Field]; ok {
168+
if empty, err = whereFromCursor(sorts[last].Field, val, &cpt); err != nil {
169+
return nil, err
170+
}
171+
} else {
172+
continue
173+
}
174+
175+
if empty {
176+
continue
177+
}
178+
179+
// Add only needed sort to cpt
180+
cpt.Order = []string{}
181+
for j := last; j <= len(sorts)-1; j++ {
182+
cpt.Order = append(cpt.Order, encodedSorts[j])
183+
184+
var sortField = sorts[j].Field
185+
if nullable, unique := uniqueFields[sortField]; unique {
186+
if !nullable {
187+
break
188+
}
189+
190+
var becomeUnique = false
191+
// for ASC if we have a value, that means we already select all null rows
192+
// for DESC Nulls can start at any row
193+
if sorts[j].Func == "asc" {
194+
for _, val := range cpt.Filter[sortField] {
195+
if val != db.SpecialConditionIsNull {
196+
becomeUnique = true
197+
break
198+
}
199+
}
200+
}
201+
if becomeUnique {
202+
break
203+
}
204+
}
205+
}
206+
207+
cpt.Cursor = nil
208+
209+
tokens = append(tokens, cpt)
210+
}
211+
212+
return tokens, nil
213+
}
214+
215+
func createNextCursorBasedPageToken[T Searchable[T]](previousPageToken PageToken, items []T, limit int64,
216+
cursorGen func(entity *T, field string) (string, error), uniqueFields map[string]bool) (*PageToken, error) {
217+
if int64(len(items)) < limit {
218+
return nil, nil
219+
}
220+
221+
var pt PageToken
222+
pt.Cursor = make(map[string]string)
223+
pt.Fields = previousPageToken.Fields
224+
225+
var encoded, sorts, err = uniqueSort(previousPageToken.Order, uniqueFields, previousPageToken.Cursor)
226+
if err != nil {
227+
return nil, err
228+
}
229+
pt.Order = encoded
230+
231+
var last = items[len(items)-1]
232+
for _, sort := range sorts {
233+
var value string
234+
if value, err = cursorGen(&last, sort.Field); err != nil {
235+
return nil, err
236+
}
237+
pt.Cursor[sort.Field] = value
238+
}
239+
240+
return &pt, nil
241+
}

0 commit comments

Comments
 (0)