Skip to content

Commit 55d8b18

Browse files
authored
fix: handle multiple filter values for same field in $or queries (#1186)
* test: add e2e test for account_array column error with $or filter This test reproduces a bug where using $or with both exact addresses and partial addresses combined with PIT causes "column account_array does not exist" SQL error. The bug occurs because validateFilters only stores ONE value per field name, so the last address in the $or iteration overwrites previous ones. * fix: handle multiple filter values for same field in $or queries The validateFilters function was storing only ONE value per field name, causing issues when using $or with multiple address filters. When the iteration order made an exact address the last one processed, needAddressSegments became false, but ResolveFilter still generated filters on account_array for partial addresses, causing SQL error "column account_array does not exist". Changes: - Change filters map from map[string]any to map[string][]any - Modify validateFilters to append values instead of overwriting - Modify UseFilter to return true if at least one value matches * test: add e2e test for aggregated balances with $or filter This test covers the same bug pattern as volumes: using $or with both exact addresses and partial addresses combined with PIT could cause "column accounts_address_array does not exist" error.
1 parent 4835b76 commit 55d8b18

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

internal/storage/common/resource.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,32 @@ func (s EntitySchema) GetFieldByNameOrAlias(name string) (string, *Field) {
7373

7474
type RepositoryHandlerBuildContext[Opts any] struct {
7575
ResourceQuery[Opts]
76-
filters map[string]any
76+
filters map[string][]any
7777
}
7878

7979
func (ctx RepositoryHandlerBuildContext[Opts]) UseFilter(v string, matchers ...func(value any) bool) bool {
80-
value, ok := ctx.filters[v]
80+
values, ok := ctx.filters[v]
8181
if !ok {
8282
return false
8383
}
84-
for _, matcher := range matchers {
85-
if !matcher(value) {
86-
return false
84+
if len(matchers) == 0 {
85+
return true
86+
}
87+
// Return true if at least one value matches all matchers
88+
for _, value := range values {
89+
allMatch := true
90+
for _, matcher := range matchers {
91+
if !matcher(value) {
92+
allMatch = false
93+
break
94+
}
95+
}
96+
if allMatch {
97+
return true
8798
}
8899
}
89100

90-
return true
101+
return false
91102
}
92103

93104
type RepositoryHandler[Opts any] interface {
@@ -102,12 +113,12 @@ type ResourceRepository[ResourceType, OptionsType any] struct {
102113
resourceHandler RepositoryHandler[OptionsType]
103114
}
104115

105-
func (r *ResourceRepository[ResourceType, OptionsType]) validateFilters(builder query.Builder) (map[string]any, error) {
116+
func (r *ResourceRepository[ResourceType, OptionsType]) validateFilters(builder query.Builder) (map[string][]any, error) {
106117
if builder == nil {
107118
return nil, nil
108119
}
109120

110-
ret := make(map[string]any)
121+
ret := make(map[string][]any)
111122
properties := r.resourceHandler.Schema().Fields
112123
if err := builder.Walk(func(operator string, key string, value any) (err error) {
113124

@@ -128,7 +139,7 @@ func (r *ResourceRepository[ResourceType, OptionsType]) validateFilters(builder
128139
return NewErrInvalidQuery("invalid value '%v' for property '%s': %s", value, name, err)
129140
}
130141

131-
ret[name] = value
142+
ret[name] = append(ret[name], value)
132143

133144
return nil
134145
}

test/e2e/api_balances_aggregated_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,36 @@ var _ = Context("Ledger engine tests", func() {
213213

214214
Expect(response.V2AggregateBalancesResponse.Data).To(HaveLen(0))
215215
})
216+
// Test case to reproduce bug: column "accounts_address_array" does not exist
217+
// This happens when using $or with both exact addresses AND partial addresses with PIT
218+
It("should be ok when aggregating with PIT and $or filter mixing exact and partial addresses", func(specContext SpecContext) {
219+
// This test reproduces the same bug as in volumes:
220+
// Using $or with exact addresses and partial addresses combined with PIT
221+
// could cause "column accounts_address_array does not exist" error
222+
response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetBalancesAggregated(
223+
ctx,
224+
operations.V2GetBalancesAggregatedRequest{
225+
Ledger: "default",
226+
Pit: pointer.For(now),
227+
RequestBody: map[string]interface{}{
228+
"$or": []any{
229+
map[string]any{
230+
"$match": map[string]any{
231+
"address": "bank:", // partial address - requires accounts_address_array
232+
},
233+
},
234+
map[string]any{
235+
"$match": map[string]any{
236+
"address": "world", // exact address - does NOT require accounts_address_array
237+
},
238+
},
239+
},
240+
},
241+
},
242+
)
243+
Expect(err).ToNot(HaveOccurred())
244+
// bank1 + bank2 + world all have USD/2, total should be aggregated
245+
Expect(response.V2AggregateBalancesResponse.Data).To(HaveLen(1))
246+
})
216247
})
217248
})

test/e2e/api_volumes_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,42 @@ var _ = Context("Ledger accounts list API tests", func() {
296296
}
297297
})
298298
})
299+
300+
// Test case to reproduce bug: column "account_array" does not exist (SQLSTATE 42703)
301+
// This happens when using $or with both exact addresses AND partial addresses with PIT
302+
When("Get volumes with PIT and $or filter mixing exact and partial addresses", func() {
303+
It("should not fail with account_array column error", func(specContext SpecContext) {
304+
// This test reproduces the bug where using $or with:
305+
// - exact addresses (e.g., "account:user1")
306+
// - partial addresses (e.g., "account:")
307+
// combined with PIT causes "column account_array does not exist" error
308+
//
309+
// The bug occurs because validateFilters only stores ONE value per field name,
310+
// so if the last address in the $or is exact, needAddressSegments becomes false,
311+
// but ResolveFilter still generates filters on account_array for partial addresses.
312+
response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances(
313+
ctx,
314+
operations.V2GetVolumesWithBalancesRequest{
315+
EndTime: pointer.For(now),
316+
RequestBody: map[string]interface{}{
317+
"$or": []any{
318+
map[string]any{
319+
"$match": map[string]any{
320+
"account": "account:", // partial address - requires account_array
321+
},
322+
},
323+
map[string]any{
324+
"$match": map[string]any{
325+
"account": "bank", // exact address - does NOT require account_array
326+
},
327+
},
328+
},
329+
},
330+
Ledger: "default",
331+
},
332+
)
333+
Expect(err).ToNot(HaveOccurred())
334+
Expect(response.V2VolumesWithBalanceCursorResponse.Cursor.Data).To(HaveLen(3)) // account:user1, account:user2, bank
335+
})
336+
})
299337
})

0 commit comments

Comments
 (0)