Skip to content

Commit 6f7e9be

Browse files
committed
Replace upper bound tick number filter if it exceeds max tick
* Previously we had two filters. One always restricting to max tick. Only having one filter is beneficial performance wise and easier to maintain.
1 parent 1292ff2 commit 6f7e9be

File tree

2 files changed

+237
-2
lines changed

2 files changed

+237
-2
lines changed

v2/domain/repository/elastic/transaction.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"log"
99
"sort"
10+
"strconv"
1011
"strings"
1112

1213
api "github.com/qubic/archive-query-service/v2/api/archive-query-service/v2"
@@ -166,9 +167,17 @@ func createIdentitiesQuery(identity string, filters map[string][]string, ranges
166167

167168
includeFilters, excludeFilters := splitFilters(filters)
168169

170+
// Check if there's an upper bound tickNumber range filter (lt/lte) and adjust if needed
171+
hasUpperBoundTickFilter, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
172+
if err != nil {
173+
return "", err
174+
}
175+
169176
filterStrings := make([]string, 0, len(includeFilters)+len(ranges)+1)
170-
// restrict to max tick (we don't care about potential duplicate tickNumber range filter)
171-
filterStrings = append(filterStrings, fmt.Sprintf(`{"range":{"tickNumber":{"lte":"%d"}}}`, maxTick))
177+
// restrict to max tick only if no upper bound tickNumber filter is present
178+
if !hasUpperBoundTickFilter {
179+
filterStrings = append(filterStrings, fmt.Sprintf(`{"range":{"tickNumber":{"lte":"%d"}}}`, maxTick))
180+
}
172181
// normal filters
173182
filterStrings = append(filterStrings, getFilterStrings(includeFilters)...)
174183
// append range filters
@@ -214,6 +223,34 @@ func createIdentitiesQuery(identity string, filters map[string][]string, ranges
214223
return query, nil
215224
}
216225

226+
func modifyUpperBoundTickNumberFilterIfNecessary(ranges map[string][]*entities.Range, maxTick uint32) (bool, error) {
227+
hasUpperBoundTickFilter := false
228+
if tickRanges, ok := ranges["tickNumber"]; ok {
229+
for _, r := range tickRanges {
230+
if r.Operation == "lt" || r.Operation == "lte" {
231+
hasUpperBoundTickFilter = true
232+
// Parse the value and compare with maxTick
233+
tickValue, err := strconv.ParseUint(r.Value, 10, 32)
234+
if err != nil {
235+
return false, fmt.Errorf("parsing tickNumber range value: %w", err)
236+
}
237+
maxTickValue := If(r.Operation == "lte", maxTick, maxTick+1)
238+
if uint32(tickValue) > maxTickValue {
239+
r.Value = fmt.Sprintf("%d", maxTickValue)
240+
}
241+
}
242+
}
243+
}
244+
return hasUpperBoundTickFilter, nil
245+
}
246+
247+
func If[T any](cond bool, vtrue, vfalse T) T {
248+
if cond {
249+
return vtrue
250+
}
251+
return vfalse
252+
}
253+
217254
const excludeSuffix = "-exclude"
218255

219256
func splitFilters(filters map[string][]string) (map[string][]string, map[string][]string) {

v2/domain/repository/elastic/transaction_identities_query_test.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,201 @@ func Test_createIdentitiesQuery_givenRangesAndFilters_returnQueryWithAllFilters(
168168
log.Println(query)
169169
require.JSONEq(t, expectedQuery, query)
170170
}
171+
172+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_noRangeFilter(t *testing.T) {
173+
ranges := map[string][]*entities.Range{}
174+
maxTick := uint32(1000)
175+
176+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
177+
require.NoError(t, err)
178+
require.False(t, hasUpperBound)
179+
}
180+
181+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_upperBoundReplacedWithMaxTick(t *testing.T) {
182+
ranges := map[string][]*entities.Range{
183+
"tickNumber": {
184+
{Operation: "lte", Value: "5000"},
185+
},
186+
}
187+
maxTick := uint32(1000)
188+
189+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
190+
require.NoError(t, err)
191+
require.True(t, hasUpperBound)
192+
require.Equal(t, "1000", ranges["tickNumber"][0].Value)
193+
}
194+
195+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_upperBoundNotReplaced(t *testing.T) {
196+
ranges := map[string][]*entities.Range{
197+
"tickNumber": {
198+
{Operation: "lte", Value: "999"},
199+
},
200+
}
201+
maxTick := uint32(1000)
202+
203+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
204+
require.NoError(t, err)
205+
require.True(t, hasUpperBound)
206+
require.Equal(t, "999", ranges["tickNumber"][0].Value)
207+
}
208+
209+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_onlyLowerBound(t *testing.T) {
210+
ranges := map[string][]*entities.Range{
211+
"tickNumber": {
212+
{Operation: "gte", Value: "100"},
213+
},
214+
}
215+
maxTick := uint32(1000)
216+
217+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
218+
require.NoError(t, err)
219+
require.False(t, hasUpperBound)
220+
require.Equal(t, "100", ranges["tickNumber"][0].Value)
221+
}
222+
223+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_ltOperatorNotReplacedWithMaxTick(t *testing.T) {
224+
ranges := map[string][]*entities.Range{
225+
"tickNumber": {
226+
{Operation: "lt", Value: "1001"},
227+
},
228+
}
229+
maxTick := uint32(1000)
230+
231+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
232+
require.NoError(t, err)
233+
require.True(t, hasUpperBound)
234+
require.Equal(t, "1001", ranges["tickNumber"][0].Value)
235+
}
236+
237+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_ltOperatorReplacedWithMaxTick(t *testing.T) {
238+
ranges := map[string][]*entities.Range{
239+
"tickNumber": {
240+
{Operation: "lt", Value: "1002"},
241+
},
242+
}
243+
maxTick := uint32(1000)
244+
245+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
246+
require.NoError(t, err)
247+
require.True(t, hasUpperBound)
248+
require.Equal(t, "1001", ranges["tickNumber"][0].Value)
249+
}
250+
251+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_lteOperatorReplacedWithMaxTick(t *testing.T) {
252+
ranges := map[string][]*entities.Range{
253+
"tickNumber": {
254+
{Operation: "lte", Value: "5000"},
255+
},
256+
}
257+
maxTick := uint32(1000)
258+
259+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
260+
require.NoError(t, err)
261+
require.True(t, hasUpperBound)
262+
require.Equal(t, "1000", ranges["tickNumber"][0].Value)
263+
}
264+
265+
func Test_modifyUpperBoundTickNumberFilterIfNecessary_invalidValueReturnsError(t *testing.T) {
266+
ranges := map[string][]*entities.Range{
267+
"tickNumber": {
268+
{Operation: "lte", Value: "not-a-number"},
269+
},
270+
}
271+
maxTick := uint32(1000)
272+
273+
hasUpperBound, err := modifyUpperBoundTickNumberFilterIfNecessary(ranges, maxTick)
274+
require.Error(t, err)
275+
require.False(t, hasUpperBound)
276+
require.Contains(t, err.Error(), "parsing tickNumber range value")
277+
}
278+
279+
func Test_createIdentitiesQuery_withTickNumberUpperBound_omitsMaxTickFilter(t *testing.T) {
280+
expectedQuery := `{
281+
"query": {
282+
"bool": {
283+
"should": [
284+
{ "term":{"source":"some-identity"} },
285+
{ "term":{"destination":"some-identity"} }
286+
],
287+
"minimum_should_match": 1,
288+
"filter": [
289+
{"range":{"tickNumber":{"lte":"500"}}}
290+
]
291+
}
292+
},
293+
"sort": [ {"tickNumber":{"order":"desc"}} ],
294+
"from": 0,
295+
"size": 10,
296+
"track_total_hits": 10000
297+
}`
298+
299+
ranges := map[string][]*entities.Range{
300+
"tickNumber": {{Operation: "lte", Value: "500"}},
301+
}
302+
query, err := createIdentitiesQuery(testIdentity, nil, ranges, 0, 10, 1000)
303+
require.NoError(t, err)
304+
require.NotEmpty(t, query)
305+
306+
require.JSONEq(t, expectedQuery, query)
307+
}
308+
309+
func Test_createIdentitiesQuery_withTickNumberUpperBoundExceedingMaxTick_replacesWithMaxTick(t *testing.T) {
310+
expectedQuery := `{
311+
"query": {
312+
"bool": {
313+
"should": [
314+
{ "term":{"source":"some-identity"} },
315+
{ "term":{"destination":"some-identity"} }
316+
],
317+
"minimum_should_match": 1,
318+
"filter": [
319+
{"range":{"tickNumber":{"lt":"1001"}}}
320+
]
321+
}
322+
},
323+
"sort": [ {"tickNumber":{"order":"desc"}} ],
324+
"from": 0,
325+
"size": 10,
326+
"track_total_hits": 10000
327+
}`
328+
329+
ranges := map[string][]*entities.Range{
330+
"tickNumber": {{Operation: "lt", Value: "5000"}},
331+
}
332+
query, err := createIdentitiesQuery(testIdentity, nil, ranges, 0, 10, 1000)
333+
require.NoError(t, err)
334+
require.NotEmpty(t, query)
335+
336+
require.JSONEq(t, expectedQuery, query)
337+
}
338+
339+
func Test_createIdentitiesQuery_withTickNumberLowerBoundOnly_includesMaxTickFilter(t *testing.T) {
340+
expectedQuery := `{
341+
"query": {
342+
"bool": {
343+
"should": [
344+
{ "term":{"source":"some-identity"} },
345+
{ "term":{"destination":"some-identity"} }
346+
],
347+
"minimum_should_match": 1,
348+
"filter": [
349+
{"range":{"tickNumber":{"lte":"1000"}}},
350+
{"range":{"tickNumber":{"gte":"100"}}}
351+
]
352+
}
353+
},
354+
"sort": [ {"tickNumber":{"order":"desc"}} ],
355+
"from": 0,
356+
"size": 10,
357+
"track_total_hits": 10000
358+
}`
359+
360+
ranges := map[string][]*entities.Range{
361+
"tickNumber": {{Operation: "gte", Value: "100"}},
362+
}
363+
query, err := createIdentitiesQuery(testIdentity, nil, ranges, 0, 10, 1000)
364+
require.NoError(t, err)
365+
require.NotEmpty(t, query)
366+
367+
require.JSONEq(t, expectedQuery, query)
368+
}

0 commit comments

Comments
 (0)