diff --git a/config/attributescfg.go b/config/attributescfg.go
index 2032569d4..192679c5b 100644
--- a/config/attributescfg.go
+++ b/config/attributescfg.go
@@ -42,6 +42,7 @@ type AttributeSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
NestedFields bool
AnyContext bool
Opts *AttributesOpts
@@ -123,6 +124,10 @@ func (alS *AttributeSCfg) loadFromJSONCfg(jsnCfg *AttributeSJsonCfg) (err error)
copy(sif, *jsnCfg.Suffix_indexed_fields)
alS.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ alS.ExistsIndexedFields = &eif
+ }
if jsnCfg.Nested_fields != nil {
alS.NestedFields = *jsnCfg.Nested_fields
}
@@ -168,6 +173,10 @@ func (alS *AttributeSCfg) AsMapInterface() (initialMP map[string]any) {
copy(suffixIndexedFields, *alS.SuffixIndexedFields)
initialMP[utils.SuffixIndexedFieldsCfg] = suffixIndexedFields
}
+ if alS.ExistsIndexedFields != nil {
+ eif := slices.Clone(*alS.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
if alS.StatSConns != nil {
statSConns := make([]string, len(alS.StatSConns))
for i, item := range alS.StatSConns {
@@ -253,5 +262,9 @@ func (alS AttributeSCfg) Clone() (cln *AttributeSCfg) {
copy(idx, *alS.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if alS.ExistsIndexedFields != nil {
+ idx := slices.Clone(*alS.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/attributescfg_test.go b/config/attributescfg_test.go
index bcc575ddb..44ff5eb5a 100644
--- a/config/attributescfg_test.go
+++ b/config/attributescfg_test.go
@@ -35,6 +35,7 @@ func TestAttributeSCfgloadFromJsonCfg(t *testing.T) {
String_indexed_fields: &[]string{"*req.index1"},
Prefix_indexed_fields: &[]string{"*req.index1", "*req.index2"},
Suffix_indexed_fields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
Nested_fields: utils.BoolPointer(true),
Any_context: utils.BoolPointer(true),
Opts: &AttributesOptsJson{
@@ -51,6 +52,7 @@ func TestAttributeSCfgloadFromJsonCfg(t *testing.T) {
StringIndexedFields: &[]string{"*req.index1"},
PrefixIndexedFields: &[]string{"*req.index1", "*req.index2"},
SuffixIndexedFields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
NestedFields: true,
AnyContext: true,
Opts: &AttributesOpts{
@@ -80,7 +82,8 @@ func TestAttributeSCfgAsMapInterface(t *testing.T) {
"stats_conns": ["*internal"],
"resources_conns": ["*internal"],
"apiers_conns": ["*internal"],
- "prefix_indexed_fields": ["*req.index1","*req.index2"],
+ "prefix_indexed_fields": ["*req.index1","*req.index2"],
+ "exists_indexed_fields": ["*req.index1","*req.index2"],
"string_indexed_fields": ["*req.index1"],
"opts": {
"*processRuns": 3,
@@ -95,6 +98,7 @@ func TestAttributeSCfgAsMapInterface(t *testing.T) {
utils.ApierSConnsCfg: []string{utils.MetaInternal},
utils.StringIndexedFieldsCfg: []string{"*req.index1"},
utils.PrefixIndexedFieldsCfg: []string{"*req.index1", "*req.index2"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.index1", "*req.index2"},
utils.IndexedSelectsCfg: true,
utils.NestedFieldsCfg: false,
utils.SuffixIndexedFieldsCfg: []string{},
@@ -118,6 +122,7 @@ func TestAttributeSCfgAsMapInterface2(t *testing.T) {
cfgJSONStr := `{
"attributes": {
"suffix_indexed_fields": ["*req.index1","*req.index2"],
+ "exists_indexed_fields": ["*req.index1","*req.index2"],
"nested_fields": true,
"enabled": true,
"opts": {
@@ -133,6 +138,7 @@ func TestAttributeSCfgAsMapInterface2(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{"*req.index1", "*req.index2"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.index1", "*req.index2"},
utils.NestedFieldsCfg: true,
utils.AnyContextCfg: true,
utils.OptsCfg: map[string]any{
@@ -163,6 +169,7 @@ func TestAttributeSCfgAsMapInterface3(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AnyContextCfg: true,
utils.OptsCfg: map[string]any{
diff --git a/config/chargerscfg.go b/config/chargerscfg.go
index 77c0dcb5c..f3510c539 100644
--- a/config/chargerscfg.go
+++ b/config/chargerscfg.go
@@ -19,6 +19,8 @@ along with this program. If not, see
package config
import (
+ "slices"
+
"github.com/cgrates/cgrates/utils"
)
@@ -30,6 +32,7 @@ type ChargerSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
NestedFields bool
}
@@ -68,6 +71,10 @@ func (cS *ChargerSCfg) loadFromJSONCfg(jsnCfg *ChargerSJsonCfg) (err error) {
copy(sif, *jsnCfg.Suffix_indexed_fields)
cS.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ cS.ExistsIndexedFields = &eif
+ }
if jsnCfg.Nested_fields != nil {
cS.NestedFields = *jsnCfg.Nested_fields
}
@@ -106,6 +113,10 @@ func (cS *ChargerSCfg) AsMapInterface() (initialMP map[string]any) {
copy(sufixIndexedFields, *cS.SuffixIndexedFields)
initialMP[utils.SuffixIndexedFieldsCfg] = sufixIndexedFields
}
+ if cS.ExistsIndexedFields != nil {
+ eif := slices.Clone(*cS.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
return
}
@@ -136,5 +147,9 @@ func (cS ChargerSCfg) Clone() (cln *ChargerSCfg) {
copy(idx, *cS.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if cS.ExistsIndexedFields != nil {
+ idx := slices.Clone(*cS.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/chargerscfg_test.go b/config/chargerscfg_test.go
index 57beefac3..9b205897a 100644
--- a/config/chargerscfg_test.go
+++ b/config/chargerscfg_test.go
@@ -32,6 +32,7 @@ func TestChargerSCfgloadFromJsonCfg(t *testing.T) {
String_indexed_fields: &[]string{"*req.Field1"},
Prefix_indexed_fields: &[]string{"*req.Field1", "*req.Field2"},
Suffix_indexed_fields: &[]string{"*req.Field1", "*req.Field2"},
+ ExistsIndexedFields: &[]string{"*req.Field1", "*req.Field2"},
Nested_fields: utils.BoolPointer(true),
}
expected := &ChargerSCfg{
@@ -41,6 +42,7 @@ func TestChargerSCfgloadFromJsonCfg(t *testing.T) {
StringIndexedFields: &[]string{"*req.Field1"},
PrefixIndexedFields: &[]string{"*req.Field1", "*req.Field2"},
SuffixIndexedFields: &[]string{"*req.Field1", "*req.Field2"},
+ ExistsIndexedFields: &[]string{"*req.Field1", "*req.Field2"},
NestedFields: true,
}
jsncfg := NewDefaultCGRConfig()
@@ -68,6 +70,7 @@ func TestChargerSCfgAsMapInterface(t *testing.T) {
utils.PrefixIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
}
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSONStr); err != nil {
t.Error(err)
@@ -83,8 +86,9 @@ func TestChargerSCfgAsMapInterface1(t *testing.T) {
"attributes_conns": ["*internal:*attributes", "*conn1"],
"indexed_selects":true,
"string_indexed_fields": ["*req.Field1","*req.Field2","*req.Field3"],
- "prefix_indexed_fields": ["*req.DestinationPrefix"],
- "suffix_indexed_fields": ["*req.Field1","*req.Field2","*req.Field3"],
+ "prefix_indexed_fields": ["*req.DestinationPrefix"],
+ "suffix_indexed_fields": ["*req.Field1","*req.Field2","*req.Field3"],
+ "exists_indexed_fields": ["*req.Field1","*req.Field2","*req.Field3"],
"nested_fields": false,
},
}`
@@ -96,6 +100,7 @@ func TestChargerSCfgAsMapInterface1(t *testing.T) {
utils.PrefixIndexedFieldsCfg: []string{"*req.DestinationPrefix"},
utils.NestedFieldsCfg: false,
utils.SuffixIndexedFieldsCfg: []string{"*req.Field1", "*req.Field2", "*req.Field3"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.Field1", "*req.Field2", "*req.Field3"},
}
if cgrCfg, err := NewCGRConfigFromJSONStringWithDefaults(cfgJSONStr); err != nil {
t.Error(err)
diff --git a/config/config_defaults.go b/config/config_defaults.go
index 8e65ba4c1..f8d7cc498 100644
--- a/config/config_defaults.go
+++ b/config/config_defaults.go
@@ -838,6 +838,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"any_context": true, // if we match the *any context
"opts": {
@@ -856,6 +857,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
},
@@ -868,6 +870,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"opts": {
"*usageID": "",
@@ -888,6 +891,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"opts": {
"*profileIDs": [],
@@ -927,6 +931,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"opts": {
"*profileIDs": [],
@@ -941,6 +946,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"attributes_conns": [], // connections to AttributeS for altering events before route queries: <""|*internal|$rpc_conns_id>
"resources_conns": [], // connections to ResourceS for *res sorting, empty to disable functionality: <""|*internal|$rpc_conns_id>
@@ -1226,6 +1232,7 @@ const CGRATES_CFG_JSON = `
//"string_indexed_fields": [], // query indexes based on these fields for faster processing
"prefix_indexed_fields": [], // query indexes based on these fields for faster processing
"suffix_indexed_fields": [], // query indexes based on these fields for faster processing
+ "exists_indexed_fields": [], // query indexes based on these fields for faster processing
"nested_fields": false, // determines which field is checked when matching indexed filters(true: all; false: only the one on the first level)
"attributes_conns": [], // connections to AttributeS for API authorization, empty to disable auth functionality: <""|*internal|$rpc_conns_id>
"any_subsystem": true, // if we match the *any subsystem
diff --git a/config/config_it_test.go b/config/config_it_test.go
index cc0349742..b68457356 100644
--- a/config/config_it_test.go
+++ b/config/config_it_test.go
@@ -155,6 +155,7 @@ func testCGRConfigReloadAttributeS(t *testing.T) {
StringIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.AccountField},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
IndexedSelects: true,
AnyContext: true,
Opts: &AttributesOpts{
@@ -210,6 +211,7 @@ func testCGRConfigReloadChargerS(t *testing.T) {
StringIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.AccountField},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
IndexedSelects: true,
AttributeSConns: []string{"*localhost"},
}
@@ -238,6 +240,7 @@ func testCGRConfigReloadThresholdS(t *testing.T) {
StringIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.AccountField},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
IndexedSelects: true,
Opts: &ThresholdsOpts{
ProfileIDs: []string{},
@@ -268,6 +271,7 @@ func testCGRConfigReloadStatS(t *testing.T) {
StringIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.AccountField},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
IndexedSelects: true,
ThresholdSConns: []string{utils.MetaLocalHost},
Opts: &StatsOpts{
@@ -300,6 +304,7 @@ func testCGRConfigReloadResourceS(t *testing.T) {
StringIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.AccountField},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
IndexedSelects: true,
ThresholdSConns: []string{utils.MetaLocalHost},
Opts: &ResourcesOpts{
@@ -332,6 +337,7 @@ func testCGRConfigReloadSupplierS(t *testing.T) {
StringIndexedFields: &[]string{"*req.LCRProfile"},
PrefixIndexedFields: &[]string{utils.MetaReq + utils.NestingSep + utils.Destination},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
ResourceSConns: []string{},
StatSConns: []string{},
AttributeSConns: []string{},
diff --git a/config/config_json_test.go b/config/config_json_test.go
index f559bbc63..5bbcbe9c0 100644
--- a/config/config_json_test.go
+++ b/config/config_json_test.go
@@ -1143,6 +1143,7 @@ func TestDfAttributeServJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Nested_fields: utils.BoolPointer(false),
Any_context: utils.BoolPointer(true),
Opts: &AttributesOptsJson{
@@ -1171,6 +1172,7 @@ func TestDfChargerServJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Nested_fields: utils.BoolPointer(false),
}
dfCgrJSONCfg, err := NewCgrJsonCfgFromBytes([]byte(CGRATES_CFG_JSON))
@@ -1212,6 +1214,7 @@ func TestDfResourceLimiterSJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Nested_fields: utils.BoolPointer(false),
Opts: &ResourcesOptsJson{
UsageID: utils.StringPointer(utils.EmptyString),
@@ -1239,6 +1242,7 @@ func TestDfStatServiceJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Nested_fields: utils.BoolPointer(false),
Ees_conns: &[]string{},
Ees_exporter_ids: &[]string{},
@@ -1267,6 +1271,7 @@ func TestDfThresholdSJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Nested_fields: utils.BoolPointer(false),
Opts: &ThresholdsOptsJson{
ProfileIDs: &[]string{},
@@ -1291,6 +1296,7 @@ func TestDfRouteSJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Attributes_conns: &[]string{},
Resources_conns: &[]string{},
Stats_conns: &[]string{},
@@ -1932,6 +1938,7 @@ func TestDfDispatcherSJsonCfg(t *testing.T) {
String_indexed_fields: nil,
Prefix_indexed_fields: &[]string{},
Suffix_indexed_fields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Attributes_conns: &[]string{},
Nested_fields: utils.BoolPointer(false),
Any_subsystem: utils.BoolPointer(true),
diff --git a/config/config_test.go b/config/config_test.go
index 199daf14d..ece1aba0c 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -717,6 +717,7 @@ func TestCgrCfgJSONDefaultSChargerSCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
}
if !reflect.DeepEqual(eChargerSCfg, cgrCfg.chargerSCfg) {
t.Errorf("received: %+v, expecting: %+v", eChargerSCfg, cgrCfg.chargerSCfg)
@@ -732,6 +733,7 @@ func TestCgrCfgJSONDefaultsResLimCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Opts: &ResourcesOpts{
UsageID: utils.EmptyString,
Units: 1,
@@ -752,6 +754,7 @@ func TestCgrCfgJSONDefaultStatsCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Opts: &StatsOpts{
ProfileIDs: []string{},
},
@@ -770,6 +773,7 @@ func TestCgrCfgJSONDefaultThresholdSCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
Opts: &ThresholdsOpts{
ProfileIDs: []string{},
},
@@ -786,6 +790,7 @@ func TestCgrCfgJSONDefaultRouteSCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
AttributeSConns: []string{},
ResourceSConns: []string{},
StatSConns: []string{},
@@ -1832,6 +1837,7 @@ func TestAttributeSConfig(t *testing.T) {
IndexedSelects: true,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
NestedFields: false,
AnyContext: true,
Opts: &AttributesOpts{
@@ -1853,6 +1859,7 @@ func TestChargersConfig(t *testing.T) {
AttributeSConns: []string{},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
NestedFields: false,
}
cgrConfig := NewDefaultCGRConfig()
@@ -1870,6 +1877,7 @@ func TestResourceSConfig(t *testing.T) {
ThresholdSConns: []string{},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
NestedFields: false,
Opts: &ResourcesOpts{
UsageID: "",
@@ -1892,6 +1900,7 @@ func TestStatSConfig(t *testing.T) {
ThresholdSConns: []string{},
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
NestedFields: false,
Opts: &StatsOpts{
ProfileIDs: []string{},
@@ -1912,6 +1921,7 @@ func TestThresholdSConfig(t *testing.T) {
StoreInterval: 0,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
NestedFields: false,
Opts: &ThresholdsOpts{
ProfileIDs: []string{},
@@ -1930,6 +1940,7 @@ func TestRouteSConfig(t *testing.T) {
IndexedSelects: true,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
AttributeSConns: []string{},
ResourceSConns: []string{},
StatSConns: []string{},
@@ -2102,6 +2113,7 @@ func TestDispatcherSConfig(t *testing.T) {
IndexedSelects: true,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
AttributeSConns: []string{},
NestedFields: false,
AnySubsystem: true,
@@ -3123,6 +3135,7 @@ func TestCgrCfgJSONDefaultDispatcherSCfg(t *testing.T) {
StringIndexedFields: nil,
PrefixIndexedFields: &[]string{},
SuffixIndexedFields: &[]string{},
+ ExistsIndexedFields: &[]string{},
AttributeSConns: []string{},
AnySubsystem: true,
}
@@ -3952,6 +3965,7 @@ func TestV1GetConfigAttribute(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AnyContextCfg: true,
utils.OptsCfg: map[string]any{
@@ -3980,6 +3994,7 @@ func TestV1GetConfigChargers(t *testing.T) {
utils.PrefixIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
},
}
cfgCgr := NewDefaultCGRConfig()
@@ -4000,6 +4015,7 @@ func TestV1GetConfigResourceS(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaUnitsCfg: 1.,
@@ -4026,6 +4042,7 @@ func TestV1GetConfigStats(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
@@ -4052,6 +4069,7 @@ func TestV1GetConfigThresholds(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
@@ -4075,6 +4093,7 @@ func TestV1GetConfigRoutes(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AttributeSConnsCfg: []string{},
utils.ResourceSConnsCfg: []string{},
@@ -4145,6 +4164,7 @@ func TestV1GetConfigDispatcherS(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AttributeSConnsCfg: []string{},
utils.AnySubsystemCfg: true,
@@ -4941,7 +4961,7 @@ func TestV1GetConfigAsJSONDNSAgent(t *testing.T) {
func TestV1GetConfigAsJSONAttributes(t *testing.T) {
var reply string
- expected := `{"attributes":{"any_context":true,"apiers_conns":[],"enabled":false,"indexed_selects":true,"nested_fields":false,"opts":{"*processRuns":1,"*profileIDs":[],"*profileIgnoreFilters":false,"*profileRuns":0},"prefix_indexed_fields":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]}}`
+ expected := `{"attributes":{"any_context":true,"apiers_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"opts":{"*processRuns":1,"*profileIDs":[],"*profileIgnoreFilters":false,"*profileRuns":0},"prefix_indexed_fields":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: ATTRIBUTE_JSN}, &reply); err != nil {
t.Error(err)
@@ -4952,7 +4972,7 @@ func TestV1GetConfigAsJSONAttributes(t *testing.T) {
func TestV1GetConfigAsJSONChargerS(t *testing.T) {
var reply string
- expected := `{"chargers":{"attributes_conns":[],"enabled":false,"indexed_selects":true,"nested_fields":false,"prefix_indexed_fields":[],"suffix_indexed_fields":[]}}`
+ expected := `{"chargers":{"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"prefix_indexed_fields":[],"suffix_indexed_fields":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: ChargerSCfgJson}, &reply); err != nil {
t.Error(err)
@@ -4963,7 +4983,7 @@ func TestV1GetConfigAsJSONChargerS(t *testing.T) {
func TestV1GetConfigAsJSONResourceS(t *testing.T) {
var reply string
- expected := `{"resources":{"enabled":false,"indexed_selects":true,"nested_fields":false,"opts":{"*units":1,"*usageID":""},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[],"thresholds_conns":[]}}`
+ expected := `{"resources":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"opts":{"*units":1,"*usageID":""},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[],"thresholds_conns":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: RESOURCES_JSON}, &reply); err != nil {
t.Error(err)
@@ -4974,7 +4994,7 @@ func TestV1GetConfigAsJSONResourceS(t *testing.T) {
func TestV1GetConfigAsJSONStatS(t *testing.T) {
var reply string
- expected := `{"stats":{"ees_conns":[],"ees_exporter_ids":[],"enabled":false,"indexed_selects":true,"nested_fields":false,"opts":{"*profileIDs":[],"*profileIgnoreFilters":false},"prefix_indexed_fields":[],"store_interval":"","store_uncompressed_limit":0,"suffix_indexed_fields":[],"thresholds_conns":[]}}`
+ expected := `{"stats":{"ees_conns":[],"ees_exporter_ids":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"opts":{"*profileIDs":[],"*profileIgnoreFilters":false},"prefix_indexed_fields":[],"store_interval":"","store_uncompressed_limit":0,"suffix_indexed_fields":[],"thresholds_conns":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: STATS_JSON}, &reply); err != nil {
t.Error(err)
@@ -4985,7 +5005,7 @@ func TestV1GetConfigAsJSONStatS(t *testing.T) {
func TestV1GetConfigAsJSONThresholdS(t *testing.T) {
var reply string
- expected := `{"thresholds":{"enabled":false,"indexed_selects":true,"nested_fields":false,"opts":{"*profileIDs":[],"*profileIgnoreFilters":false},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[]}}`
+ expected := `{"thresholds":{"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"opts":{"*profileIDs":[],"*profileIgnoreFilters":false},"prefix_indexed_fields":[],"store_interval":"","suffix_indexed_fields":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: THRESHOLDS_JSON}, &reply); err != nil {
t.Error(err)
@@ -4996,7 +5016,7 @@ func TestV1GetConfigAsJSONThresholdS(t *testing.T) {
func TestV1GetConfigAsJSONRouteS(t *testing.T) {
var reply string
- expected := `{"routes":{"attributes_conns":[],"default_ratio":1,"enabled":false,"indexed_selects":true,"nested_fields":false,"opts":{"*context":"*routes","*ignoreErrors":false,"*maxCost":""},"prefix_indexed_fields":[],"rals_conns":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]}}`
+ expected := `{"routes":{"attributes_conns":[],"default_ratio":1,"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"opts":{"*context":"*routes","*ignoreErrors":false,"*maxCost":""},"prefix_indexed_fields":[],"rals_conns":[],"resources_conns":[],"stats_conns":[],"suffix_indexed_fields":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: RouteSJson}, &reply); err != nil {
t.Error(err)
@@ -5020,7 +5040,7 @@ func TestV1GetConfigAsJSONSureTax(t *testing.T) {
func TestV1GetConfigAsJSONDispatcherS(t *testing.T) {
var reply string
- expected := `{"dispatchers":{"any_subsystem":true,"attributes_conns":[],"enabled":false,"indexed_selects":true,"nested_fields":false,"prefix_indexed_fields":[],"prevent_loop":false,"suffix_indexed_fields":[]}}`
+ expected := `{"dispatchers":{"any_subsystem":true,"attributes_conns":[],"enabled":false,"exists_indexed_fields":[],"indexed_selects":true,"nested_fields":false,"prefix_indexed_fields":[],"prevent_loop":false,"suffix_indexed_fields":[]}}`
cgrCfg := NewDefaultCGRConfig()
if err := cgrCfg.V1GetConfigAsJSON(context.Background(), &SectionWithAPIOpts{Section: DispatcherSJson}, &reply); err != nil {
t.Error(err)
diff --git a/config/dispatcherscfg.go b/config/dispatcherscfg.go
index 6fbd45b44..ef3c310ee 100644
--- a/config/dispatcherscfg.go
+++ b/config/dispatcherscfg.go
@@ -19,6 +19,8 @@ along with this program. If not, see
package config
import (
+ "slices"
+
"github.com/cgrates/cgrates/utils"
)
@@ -29,6 +31,7 @@ type DispatcherSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
AttributeSConns []string
NestedFields bool
AnySubsystem bool
@@ -60,6 +63,10 @@ func (dps *DispatcherSCfg) loadFromJSONCfg(jsnCfg *DispatcherSJsonCfg) (err erro
copy(sif, *jsnCfg.Suffix_indexed_fields)
dps.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ dps.ExistsIndexedFields = &eif
+ }
if jsnCfg.Attributes_conns != nil {
dps.AttributeSConns = make([]string, len(*jsnCfg.Attributes_conns))
for idx, connID := range *jsnCfg.Attributes_conns {
@@ -106,6 +113,10 @@ func (dps *DispatcherSCfg) AsMapInterface() (mp map[string]any) {
copy(suffixIndexedFields, *dps.SuffixIndexedFields)
mp[utils.SuffixIndexedFieldsCfg] = suffixIndexedFields
}
+ if dps.ExistsIndexedFields != nil {
+ eif := slices.Clone(*dps.ExistsIndexedFields)
+ mp[utils.ExistsIndexedFieldsCfg] = eif
+ }
if dps.AttributeSConns != nil {
attributeSConns := make([]string, len(dps.AttributeSConns))
for i, item := range dps.AttributeSConns {
@@ -148,5 +159,9 @@ func (dps DispatcherSCfg) Clone() (cln *DispatcherSCfg) {
copy(idx, *dps.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if dps.ExistsIndexedFields != nil {
+ idx := slices.Clone(*dps.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/dispatcherscfg_test.go b/config/dispatcherscfg_test.go
index 73374e47b..092fa2924 100644
--- a/config/dispatcherscfg_test.go
+++ b/config/dispatcherscfg_test.go
@@ -32,6 +32,7 @@ func TestDispatcherSCfgloadFromJsonCfg(t *testing.T) {
String_indexed_fields: &[]string{"*req.prefix", "*req.indexed"},
Prefix_indexed_fields: &[]string{"*req.prefix", "*req.indexed", "*req.fields"},
Suffix_indexed_fields: &[]string{"*req.prefix", "*req.indexed", "*req.fields"},
+ ExistsIndexedFields: &[]string{"*req.exists", "*req.indexed", "*req.fields"},
Attributes_conns: &[]string{utils.MetaInternal, "*conn1"},
Nested_fields: utils.BoolPointer(true),
Any_subsystem: utils.BoolPointer(true),
@@ -42,6 +43,7 @@ func TestDispatcherSCfgloadFromJsonCfg(t *testing.T) {
StringIndexedFields: &[]string{"*req.prefix", "*req.indexed"},
PrefixIndexedFields: &[]string{"*req.prefix", "*req.indexed", "*req.fields"},
SuffixIndexedFields: &[]string{"*req.prefix", "*req.indexed", "*req.fields"},
+ ExistsIndexedFields: &[]string{"*req.exists", "*req.indexed", "*req.fields"},
AttributeSConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes), "*conn1"},
NestedFields: true,
AnySubsystem: true,
@@ -61,6 +63,7 @@ func TestDispatcherSCfgAsMapInterface(t *testing.T) {
"indexed_selects":true,
"prefix_indexed_fields": [],
"suffix_indexed_fields": [],
+ "exists_indexed_fields": [],
"nested_fields": false,
"attributes_conns": [],
},
@@ -71,6 +74,7 @@ func TestDispatcherSCfgAsMapInterface(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AttributeSConnsCfg: []string{},
utils.AnySubsystemCfg: true,
@@ -91,6 +95,7 @@ func TestDispatcherSCfgAsMapInterface1(t *testing.T) {
"string_indexed_fields": ["*req.prefix"],
"prefix_indexed_fields": ["*req.prefix","*req.indexed","*req.fields"],
"suffix_indexed_fields": ["*req.prefix"],
+ "exists_indexed_fields": ["*req.exists"],
"nested_fields": false,
"attributes_conns": ["*internal:*attributes", "*conn1"],
"prevent_loop": true
@@ -103,6 +108,7 @@ func TestDispatcherSCfgAsMapInterface1(t *testing.T) {
utils.StringIndexedFieldsCfg: []string{"*req.prefix"},
utils.PrefixIndexedFieldsCfg: []string{"*req.prefix", "*req.indexed", "*req.fields"},
utils.SuffixIndexedFieldsCfg: []string{"*req.prefix"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.exists"},
utils.NestedFieldsCfg: false,
utils.AttributeSConnsCfg: []string{"*internal", "*conn1"},
utils.AnySubsystemCfg: true,
@@ -124,6 +130,7 @@ func TestDispatcherSCfgAsMapInterface2(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.AttributeSConnsCfg: []string{},
utils.AnySubsystemCfg: true,
diff --git a/config/libconfig_json.go b/config/libconfig_json.go
index 82bcc2710..6a873eaa5 100644
--- a/config/libconfig_json.go
+++ b/config/libconfig_json.go
@@ -639,7 +639,8 @@ type AttributeSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Any_context *bool
Opts *AttributesOptsJson
}
@@ -652,7 +653,8 @@ type ChargerSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
}
type ResourcesOptsJson struct {
@@ -670,7 +672,8 @@ type ResourceSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Opts *ResourcesOptsJson
}
@@ -689,7 +692,8 @@ type StatServJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Opts *StatsOptsJson
Ees_conns *[]string
Ees_exporter_ids *[]string
@@ -730,7 +734,8 @@ type ThresholdSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Opts *ThresholdsOptsJson
}
@@ -750,7 +755,8 @@ type RouteSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Attributes_conns *[]string
Resources_conns *[]string
Stats_conns *[]string
@@ -824,7 +830,8 @@ type DispatcherSJsonCfg struct {
String_indexed_fields *[]string
Prefix_indexed_fields *[]string
Suffix_indexed_fields *[]string
- Nested_fields *bool // applies when indexed fields is not defined
+ ExistsIndexedFields *[]string `json:"exists_indexed_fields"`
+ Nested_fields *bool // applies when indexed fields is not defined
Attributes_conns *[]string
Any_subsystem *bool
Prevent_loop *bool
diff --git a/config/resourcescfg.go b/config/resourcescfg.go
index 3ab93938d..fee3ff2de 100644
--- a/config/resourcescfg.go
+++ b/config/resourcescfg.go
@@ -19,6 +19,7 @@ along with this program. If not, see
package config
import (
+ "slices"
"time"
"github.com/cgrates/cgrates/utils"
@@ -39,6 +40,7 @@ type ResourceSConfig struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
NestedFields bool
Opts *ResourcesOpts
}
@@ -103,6 +105,10 @@ func (rlcfg *ResourceSConfig) loadFromJSONCfg(jsnCfg *ResourceSJsonCfg) (err err
copy(sif, *jsnCfg.Suffix_indexed_fields)
rlcfg.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ rlcfg.ExistsIndexedFields = &eif
+ }
if jsnCfg.Nested_fields != nil {
rlcfg.NestedFields = *jsnCfg.Nested_fields
}
@@ -153,6 +159,10 @@ func (rlcfg *ResourceSConfig) AsMapInterface() (initialMP map[string]any) {
copy(suffixIndexedFields, *rlcfg.SuffixIndexedFields)
initialMP[utils.SuffixIndexedFieldsCfg] = suffixIndexedFields
}
+ if rlcfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*rlcfg.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
if rlcfg.StoreInterval != 0 {
initialMP[utils.StoreIntervalCfg] = rlcfg.StoreInterval.String()
}
@@ -200,5 +210,9 @@ func (rlcfg ResourceSConfig) Clone() (cln *ResourceSConfig) {
copy(idx, *rlcfg.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if rlcfg.ExistsIndexedFields != nil {
+ idx := slices.Clone(*rlcfg.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/resourcescfg_test.go b/config/resourcescfg_test.go
index da0a7de7c..95c9bf741 100644
--- a/config/resourcescfg_test.go
+++ b/config/resourcescfg_test.go
@@ -34,6 +34,7 @@ func TestResourceSConfigloadFromJsonCfgCase1(t *testing.T) {
String_indexed_fields: &[]string{"*req.index1"},
Prefix_indexed_fields: &[]string{"*req.index1"},
Suffix_indexed_fields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
Nested_fields: utils.BoolPointer(true),
}
expected := &ResourceSConfig{
@@ -44,6 +45,7 @@ func TestResourceSConfigloadFromJsonCfgCase1(t *testing.T) {
StringIndexedFields: &[]string{"*req.index1"},
PrefixIndexedFields: &[]string{"*req.index1"},
SuffixIndexedFields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
NestedFields: true,
Opts: &ResourcesOpts{
Units: 1,
@@ -96,6 +98,7 @@ func TestResourceSConfigAsMapInterface(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaUnitsCfg: 1.,
@@ -119,6 +122,7 @@ func TestResourceSConfigAsMapInterface1(t *testing.T) {
"string_indexed_fields": ["*req.index1"],
"prefix_indexed_fields": ["*req.prefix_indexed_fields1","*req.prefix_indexed_fields2"],
"suffix_indexed_fields": ["*req.prefix_indexed_fields1"],
+ "exists_indexed_fields": ["*req.exists_indexed_field"],
"nested_fields": true,
"opts":{
"*usageTTL":"1"
@@ -134,6 +138,7 @@ func TestResourceSConfigAsMapInterface1(t *testing.T) {
utils.StringIndexedFieldsCfg: []string{"*req.index1"},
utils.PrefixIndexedFieldsCfg: []string{"*req.prefix_indexed_fields1", "*req.prefix_indexed_fields2"},
utils.SuffixIndexedFieldsCfg: []string{"*req.prefix_indexed_fields1"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.exists_indexed_field"},
utils.NestedFieldsCfg: true,
utils.OptsCfg: map[string]any{
utils.MetaUnitsCfg: 1.,
diff --git a/config/routescfg.go b/config/routescfg.go
index b7bf11b9b..1f5018567 100644
--- a/config/routescfg.go
+++ b/config/routescfg.go
@@ -19,6 +19,8 @@ along with this program. If not, see
package config
import (
+ "slices"
+
"github.com/cgrates/cgrates/utils"
)
@@ -38,6 +40,7 @@ type RouteSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
AttributeSConns []string
ResourceSConns []string
StatSConns []string
@@ -96,6 +99,10 @@ func (rts *RouteSCfg) loadFromJSONCfg(jsnCfg *RouteSJsonCfg) (err error) {
copy(sif, *jsnCfg.Suffix_indexed_fields)
rts.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ rts.ExistsIndexedFields = &eif
+ }
if jsnCfg.Attributes_conns != nil {
rts.AttributeSConns = make([]string, len(*jsnCfg.Attributes_conns))
for idx, conn := range *jsnCfg.Attributes_conns {
@@ -188,6 +195,10 @@ func (rts *RouteSCfg) AsMapInterface() (initialMP map[string]any) {
copy(suffixIndexedFieldsCfg, *rts.SuffixIndexedFields)
initialMP[utils.SuffixIndexedFieldsCfg] = suffixIndexedFieldsCfg
}
+ if rts.ExistsIndexedFields != nil {
+ eif := slices.Clone(*rts.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
if rts.AttributeSConns != nil {
attributeSConns := make([]string, len(rts.AttributeSConns))
for i, item := range rts.AttributeSConns {
@@ -292,5 +303,9 @@ func (rts RouteSCfg) Clone() (cln *RouteSCfg) {
copy(idx, *rts.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if rts.ExistsIndexedFields != nil {
+ idx := slices.Clone(*rts.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/routescfg_test.go b/config/routescfg_test.go
index b6363a3af..5c6ff2088 100644
--- a/config/routescfg_test.go
+++ b/config/routescfg_test.go
@@ -31,6 +31,7 @@ func TestRouteSCfgloadFromJsonCfg(t *testing.T) {
String_indexed_fields: &[]string{"*req.index1"},
Prefix_indexed_fields: &[]string{"*req.index1", "*req.index2"},
Suffix_indexed_fields: &[]string{"*req.index1", "*req.index2"},
+ ExistsIndexedFields: &[]string{"*req.index1", "*req.index2"},
Attributes_conns: &[]string{utils.MetaInternal, "conn1"},
Resources_conns: &[]string{utils.MetaInternal, "conn1"},
Stats_conns: &[]string{utils.MetaInternal, "conn1"},
@@ -50,6 +51,7 @@ func TestRouteSCfgloadFromJsonCfg(t *testing.T) {
StringIndexedFields: &[]string{"*req.index1"},
PrefixIndexedFields: &[]string{"*req.index1", "*req.index2"},
SuffixIndexedFields: &[]string{"*req.index1", "*req.index2"},
+ ExistsIndexedFields: &[]string{"*req.index1", "*req.index2"},
AttributeSConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaAttributes), "conn1"},
ResourceSConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaResources), "conn1"},
StatSConns: []string{utils.ConcatenatedKey(utils.MetaInternal, utils.MetaStats), "conn1"},
@@ -124,6 +126,7 @@ func TestRouteSCfgAsMapInterface1(t *testing.T) {
"string_indexed_fields": ["*req.string"],
"prefix_indexed_fields": ["*req.prefix","*req.indexed","*req.fields"],
"suffix_indexed_fields": ["*req.prefix","*req.indexed"],
+ "exists_indexed_fields": ["*req.exists","*req.indexed"],
"nested_fields": true,
"attributes_conns": ["*internal:*attributes", "conn1"],
"resources_conns": ["*internal:*resources", "conn1"],
@@ -138,6 +141,7 @@ func TestRouteSCfgAsMapInterface1(t *testing.T) {
utils.StringIndexedFieldsCfg: []string{"*req.string"},
utils.PrefixIndexedFieldsCfg: []string{"*req.prefix", "*req.indexed", "*req.fields"},
utils.SuffixIndexedFieldsCfg: []string{"*req.prefix", "*req.indexed"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.exists", "*req.indexed"},
utils.NestedFieldsCfg: true,
utils.AttributeSConnsCfg: []string{utils.MetaInternal, "conn1"},
utils.ResourceSConnsCfg: []string{utils.MetaInternal, "conn1"},
diff --git a/config/statscfg.go b/config/statscfg.go
index 0a7f27186..f5af406cb 100644
--- a/config/statscfg.go
+++ b/config/statscfg.go
@@ -40,6 +40,7 @@ type StatSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
NestedFields bool
Opts *StatsOpts
EEsConns []string
@@ -114,6 +115,10 @@ func (st *StatSCfg) loadFromJSONCfg(jsnCfg *StatServJsonCfg) (err error) {
copy(sif, *jsnCfg.Suffix_indexed_fields)
st.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ st.ExistsIndexedFields = &eif
+ }
if jsnCfg.Nested_fields != nil {
st.NestedFields = *jsnCfg.Nested_fields
}
@@ -164,6 +169,10 @@ func (st *StatSCfg) AsMapInterface() (initialMP map[string]any) {
initialMP[utils.SuffixIndexedFieldsCfg] = suffixIndexedFields
}
+ if st.ExistsIndexedFields != nil {
+ eif := slices.Clone(*st.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
if st.ThresholdSConns != nil {
thresholdSConns := make([]string, len(st.ThresholdSConns))
for i, item := range st.ThresholdSConns {
@@ -231,5 +240,9 @@ func (st StatSCfg) Clone() (cln *StatSCfg) {
copy(idx, *st.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if st.ExistsIndexedFields != nil {
+ idx := slices.Clone(*st.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/statscfg_test.go b/config/statscfg_test.go
index d3d94738c..c1bb4d1b3 100644
--- a/config/statscfg_test.go
+++ b/config/statscfg_test.go
@@ -34,6 +34,7 @@ func TestStatSCfgloadFromJsonCfgCase1(t *testing.T) {
String_indexed_fields: &[]string{"*req.string"},
Prefix_indexed_fields: &[]string{"*req.index1", "*req.index2"},
Suffix_indexed_fields: &[]string{"*req.index1", "*req.index2"},
+ ExistsIndexedFields: &[]string{"*req.index1", "*req.index2"},
Nested_fields: utils.BoolPointer(true),
Ees_conns: &[]string{utils.MetaInternal, "*conn1"},
Ees_exporter_ids: &[]string{"exporterID"},
@@ -47,6 +48,7 @@ func TestStatSCfgloadFromJsonCfgCase1(t *testing.T) {
StringIndexedFields: &[]string{"*req.string"},
PrefixIndexedFields: &[]string{"*req.index1", "*req.index2"},
SuffixIndexedFields: &[]string{"*req.index1", "*req.index2"},
+ ExistsIndexedFields: &[]string{"*req.index1", "*req.index2"},
NestedFields: true,
Opts: &StatsOpts{
ProfileIDs: []string{},
@@ -90,6 +92,7 @@ func TestStatSCfgAsMapInterface(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
@@ -116,6 +119,7 @@ func TestStatSCfgAsMapInterface1(t *testing.T) {
"string_indexed_fields": ["*req.string"],
"prefix_indexed_fields": ["*req.prefix_indexed_fields1","*req.prefix_indexed_fields2"],
"suffix_indexed_fields":["*req.suffix_indexed_fields"],
+ "exists_indexed_fields":["*req.exists_indexed_field"],
"nested_fields": true,
"ees_conns": ["*internal:*ees", "*conn1"],
"ees_exporter_ids":["exporterID"],
@@ -130,6 +134,7 @@ func TestStatSCfgAsMapInterface1(t *testing.T) {
utils.StringIndexedFieldsCfg: []string{"*req.string"},
utils.PrefixIndexedFieldsCfg: []string{"*req.prefix_indexed_fields1", "*req.prefix_indexed_fields2"},
utils.SuffixIndexedFieldsCfg: []string{"*req.suffix_indexed_fields"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.exists_indexed_field"},
utils.NestedFieldsCfg: true,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
diff --git a/config/thresholdscfg.go b/config/thresholdscfg.go
index 59cc50b08..9b115d5f9 100644
--- a/config/thresholdscfg.go
+++ b/config/thresholdscfg.go
@@ -39,6 +39,7 @@ type ThresholdSCfg struct {
StringIndexedFields *[]string
PrefixIndexedFields *[]string
SuffixIndexedFields *[]string
+ ExistsIndexedFields *[]string
NestedFields bool
Opts *ThresholdsOpts
}
@@ -95,6 +96,10 @@ func (t *ThresholdSCfg) loadFromJSONCfg(jsnCfg *ThresholdSJsonCfg) (err error) {
copy(sif, *jsnCfg.Suffix_indexed_fields)
t.SuffixIndexedFields = &sif
}
+ if jsnCfg.ExistsIndexedFields != nil {
+ eif := slices.Clone(*jsnCfg.ExistsIndexedFields)
+ t.ExistsIndexedFields = &eif
+ }
if jsnCfg.Nested_fields != nil {
t.NestedFields = *jsnCfg.Nested_fields
}
@@ -145,6 +150,10 @@ func (t *ThresholdSCfg) AsMapInterface() (initialMP map[string]any) {
copy(suffixIndexedFields, *t.SuffixIndexedFields)
initialMP[utils.SuffixIndexedFieldsCfg] = suffixIndexedFields
}
+ if t.ExistsIndexedFields != nil {
+ eif := slices.Clone(*t.ExistsIndexedFields)
+ initialMP[utils.ExistsIndexedFieldsCfg] = eif
+ }
return
}
@@ -183,5 +192,9 @@ func (t ThresholdSCfg) Clone() (cln *ThresholdSCfg) {
copy(idx, *t.SuffixIndexedFields)
cln.SuffixIndexedFields = &idx
}
+ if t.ExistsIndexedFields != nil {
+ idx := slices.Clone(*t.ExistsIndexedFields)
+ cln.ExistsIndexedFields = &idx
+ }
return
}
diff --git a/config/thresholdscfg_test.go b/config/thresholdscfg_test.go
index f4891f653..e512888da 100644
--- a/config/thresholdscfg_test.go
+++ b/config/thresholdscfg_test.go
@@ -32,6 +32,7 @@ func TestThresholdSCfgloadFromJsonCfgCase1(t *testing.T) {
String_indexed_fields: &[]string{"*req.prefix"},
Prefix_indexed_fields: &[]string{"*req.index1"},
Suffix_indexed_fields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
Nested_fields: utils.BoolPointer(true),
Opts: &ThresholdsOptsJson{
ProfileIDs: &[]string{},
@@ -45,6 +46,7 @@ func TestThresholdSCfgloadFromJsonCfgCase1(t *testing.T) {
StringIndexedFields: &[]string{"*req.prefix"},
PrefixIndexedFields: &[]string{"*req.index1"},
SuffixIndexedFields: &[]string{"*req.index1"},
+ ExistsIndexedFields: &[]string{"*req.index1"},
NestedFields: true,
Opts: &ThresholdsOpts{
ProfileIDs: []string{},
@@ -86,6 +88,7 @@ func TestThresholdSCfgAsMapInterfaceCase1(t *testing.T) {
utils.IndexedSelectsCfg: true,
utils.PrefixIndexedFieldsCfg: []string{},
utils.SuffixIndexedFieldsCfg: []string{},
+ utils.ExistsIndexedFieldsCfg: []string{},
utils.NestedFieldsCfg: false,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
@@ -108,6 +111,7 @@ func TestThresholdSCfgAsMapInterfaceCase2(t *testing.T) {
"string_indexed_fields": ["*req.string"],
"prefix_indexed_fields": ["*req.prefix","*req.indexed","*req.fields"],
"suffix_indexed_fields": ["*req.suffix_indexed_fields1", "*req.suffix_indexed_fields2"],
+ "exists_indexed_fields": ["*req.exists_indexed_field"],
"nested_fields": true,
},
}`
@@ -118,6 +122,7 @@ func TestThresholdSCfgAsMapInterfaceCase2(t *testing.T) {
utils.StringIndexedFieldsCfg: []string{"*req.string"},
utils.PrefixIndexedFieldsCfg: []string{"*req.prefix", "*req.indexed", "*req.fields"},
utils.SuffixIndexedFieldsCfg: []string{"*req.suffix_indexed_fields1", "*req.suffix_indexed_fields2"},
+ utils.ExistsIndexedFieldsCfg: []string{"*req.exists_indexed_field"},
utils.NestedFieldsCfg: true,
utils.OptsCfg: map[string]any{
utils.MetaProfileIDs: []string{},
diff --git a/dispatchers/dispatchers.go b/dispatchers/dispatchers.go
index 246657498..f997f6986 100644
--- a/dispatchers/dispatchers.go
+++ b/dispatchers/dispatchers.go
@@ -132,6 +132,7 @@ func (dS *DispatcherService) dispatcherProfilesForEvent(tnt string, ev *utils.CG
dS.cfg.DispatcherSCfg().StringIndexedFields,
dS.cfg.DispatcherSCfg().PrefixIndexedFields,
dS.cfg.DispatcherSCfg().SuffixIndexedFields,
+ dS.cfg.DispatcherSCfg().ExistsIndexedFields,
dS.dm, utils.CacheDispatcherFilterIndexes, idxKeyPrfx,
dS.cfg.DispatcherSCfg().IndexedSelects,
dS.cfg.DispatcherSCfg().NestedFields,
@@ -146,6 +147,7 @@ func (dS *DispatcherService) dispatcherProfilesForEvent(tnt string, ev *utils.CG
dS.cfg.DispatcherSCfg().StringIndexedFields,
dS.cfg.DispatcherSCfg().PrefixIndexedFields,
dS.cfg.DispatcherSCfg().SuffixIndexedFields,
+ dS.cfg.DispatcherSCfg().ExistsIndexedFields,
dS.dm, utils.CacheDispatcherFilterIndexes, anyIdxPrfx,
dS.cfg.DispatcherSCfg().IndexedSelects,
dS.cfg.DispatcherSCfg().NestedFields,
diff --git a/docs/datadb.rst b/docs/datadb.rst
index b63abc40a..1e028240d 100644
--- a/docs/datadb.rst
+++ b/docs/datadb.rst
@@ -3,5 +3,67 @@
DataDB
======
+**DataDB** is the subsystem within **CGRateS** responsible for storing internal engine data, supporting various databases such as Redis, Mongo, or the high-performance in-memory option: `*internal`.
-TBD
\ No newline at end of file
+When using `*internal` as the `db_type`, **CGRateS** leverages your machine’s memory to store all **DataDB** records directly inside the engine. This drastically increases read/write performance, as no data leaves the process, avoiding the overhead associated with external databases like Redis or Mongo.
+
+The `*internal` option is especially suitable for high-throughput environments, allowing **CGRateS** to operate at peak speed when accessing or modifying stored records. Additionally, this configuration supports periodic data dumps to disk to enable persistence across reboots.
+
+Configuration example
+---------------------
+
+A configuration for using `*internal` as **DataDB** looks as the following:
+
+.. code-block:: json
+
+ "data_db": {
+ "db_type": "*internal",
+ "opts": {
+ "internalDBDumpPath": "/var/lib/cgrates/internal_db/datadb",
+ "internalDBBackupPath": "/var/lib/cgrates/internal_db/backup/datadb",
+ "internalDBStartTimeout": "5m",
+ "internalDBDumpInterval": "1m",
+ "internalDBRewriteInterval": "15m",
+ "internalDBFileSizeLimit": "1GB"
+ }
+ }
+
+Parameters
+----------
+
+\internalDBDumpPath
+ Defines the path to the folder where the memory-stored **DataDB** will be dumped. This path is also used for recovery during engine startup. Ensure the folder exists before launching the engine.
+
+\internalDBBackupPath
+ Path where backup copies of the dump folder will be stored. Backups are triggered via the `APIerSv1.BackupDataDBDump `_ API call. This API can also specify a custom path for backups, otherwise the default `internalDBBackupPath` is used. Backups serve as a fallback in case of dump file corruption or loss. The created folders are timestamped in UNIX time for easy identification of the latest backup. To recover using a backup, simply transfer the folders from a backup in internalDBBackupPath to internalDBDumpPath and start the engine. If backups are zipped, they need to be unzipped manually when restoring.
+
+\internalDBStartTimeout
+ Specifies the maximum amount of time the engine will wait to recover the in-memory **DataDB** state from the dump files during startup. If this duration is exceeded, the engine will timeout and an error will be returned.
+
+\internalDBDumpInterval
+ Specifies the time interval at which **DataDB** will be dumped to disk. This duration should be chosen based on the machine's capacity and data load. If the interval is set too long and a lot of data changes during that period, the dumping process will take longer, and in the event of an engine crash, any data not dumped will be lost. Conversely, if the interval is too short, and a high number of queries are done often to **DataDB**, some of the needed processing power for the queries will be used by the dump process. Since machine resources and data loads vary, it is recommended to simulate the load on your system and determine the optimal "sweet spot" for this interval. At engine shutdown, any remaining undumped data will automatically be written to disk, regardless of the interval setting.
+
+- Setting the interval to `0s` disables the periodic dumping, meaning any data in **DataDB** will be lost when the engine shuts down.
+- Setting the interval to `-1` enables immediate dumping—whenever a record in **DataDB** is added, changed, or removed, it will be dumped to disk immediately.
+Manual dumping can be triggered using the `APIerSv1.DumpDataDB `_ API.
+
+\internalDBRewriteInterval
+ Defines the interval for rewriting files that are not currently being used for dumping data, converting them into an optimized, streamlined version and improving recovery time. Similar to `internalDBDumpInterval`, the rewriting will trigger based on specified intervals:
+
+- Setting the interval `0s` disables rewriting.
+- Setting the interval `-1` triggers rewriting only once when the engine starts.
+- Setting the interval `-2` triggers rewriting only once when the engine shuts down.
+
+Rewriting should be used sparingly, as the process temporarily loads the entire `internalDBDumpPath` folder into memory for optimization, and then writes it back to the dump folder once done. This results in a surge of memory usage, which could amount to the size of the dump file itself during the rewrite. As a rule of thumb, expect the engine's memory usage to approximately double while the rewrite process is running. Manual rewriting can be triggered at any time via the `APIerSv1.RewriteDataDB `_ API.
+
+\internalDBFileSizeLimit
+ Specifies the maximum size a single dump file can reach. Upon reaching the limit, a new dump file is created. Limiting file size improves recovery time and allows for limit reached files to be rewritten.
+
+Use cases
+---------
+
+* Deploying **CGRateS** in environments with extremely high read/write performance requirements.
+* Systems where external database dependencies are undesired or unavailable.
+* Lightweight deployments or containers requiring a self-contained runtime.
+* Scenarios requiring minimal latency for internal data access.
+* Temporary setups or testing environments that can leverage memory-based persistence.
diff --git a/engine/attributes.go b/engine/attributes.go
index ca11ebdcf..f5d07e2af 100644
--- a/engine/attributes.go
+++ b/engine/attributes.go
@@ -72,6 +72,7 @@ func (alS *AttributeService) attributeProfileForEvent(tnt string, ctx *string, a
alS.cgrcfg.AttributeSCfg().StringIndexedFields,
alS.cgrcfg.AttributeSCfg().PrefixIndexedFields,
alS.cgrcfg.AttributeSCfg().SuffixIndexedFields,
+ alS.cgrcfg.AttributeSCfg().ExistsIndexedFields,
alS.dm, utils.CacheAttributeFilterIndexes, attrIdxKey,
alS.cgrcfg.AttributeSCfg().IndexedSelects,
alS.cgrcfg.AttributeSCfg().NestedFields,
@@ -86,6 +87,7 @@ func (alS *AttributeService) attributeProfileForEvent(tnt string, ctx *string, a
alS.cgrcfg.AttributeSCfg().StringIndexedFields,
alS.cgrcfg.AttributeSCfg().PrefixIndexedFields,
alS.cgrcfg.AttributeSCfg().SuffixIndexedFields,
+ alS.cgrcfg.AttributeSCfg().ExistsIndexedFields,
alS.dm, utils.CacheAttributeFilterIndexes,
utils.ConcatenatedKey(tnt, utils.MetaAny),
alS.cgrcfg.AttributeSCfg().IndexedSelects,
diff --git a/engine/chargers.go b/engine/chargers.go
index 3f21351df..e1da4a89f 100644
--- a/engine/chargers.go
+++ b/engine/chargers.go
@@ -56,6 +56,7 @@ func (cS *ChargerService) matchingChargerProfilesForEvent(tnt string, cgrEv *uti
cS.cfg.ChargerSCfg().StringIndexedFields,
cS.cfg.ChargerSCfg().PrefixIndexedFields,
cS.cfg.ChargerSCfg().SuffixIndexedFields,
+ cS.cfg.ChargerSCfg().ExistsIndexedFields,
cS.dm, utils.CacheChargerFilterIndexes, tnt,
cS.cfg.ChargerSCfg().IndexedSelects,
cS.cfg.ChargerSCfg().NestedFields,
diff --git a/engine/filterhelpers.go b/engine/filterhelpers.go
index 864961279..8399fe608 100644
--- a/engine/filterhelpers.go
+++ b/engine/filterhelpers.go
@@ -36,11 +36,11 @@ import (
// MatchingItemIDsForEvent returns the list of item IDs matching fieldName/fieldValue for an event
// fieldIDs limits the fields which are checked against indexes
// helper on top of dataDB.GetIndexes, adding utils.MetaAny to list of fields queried
-func MatchingItemIDsForEvent(ev utils.MapStorage, stringFldIDs, prefixFldIDs, suffixFldIDs *[]string,
+func MatchingItemIDsForEvent(ev utils.MapStorage, stringFldIDs, prefixFldIDs, suffixFldIDs, existsFldIDs *[]string,
dm *DataManager, cacheID, itemIDPrefix string, indexedSelects, nestedFields bool) (itemIDs utils.StringSet, err error) {
itemIDs = make(utils.StringSet)
var allFieldIDs []string
- if indexedSelects && (stringFldIDs == nil || prefixFldIDs == nil || suffixFldIDs == nil) {
+ if indexedSelects && (stringFldIDs == nil || prefixFldIDs == nil || suffixFldIDs == nil || existsFldIDs == nil) {
allFieldIDs = ev.GetKeys(nestedFields, 2, utils.EmptyString)
}
// Guard will protect the function with automatic locking
@@ -58,13 +58,32 @@ func MatchingItemIDsForEvent(ev utils.MapStorage, stringFldIDs, prefixFldIDs, su
itemIDs = utils.NewStringSet(sliceIDs)
return
}
- stringFieldVals := map[string]string{utils.MetaAny: utils.MetaAny} // cache here field string values, start with default one
- filterIndexTypes := []string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix, utils.MetaNone} // the MetaNone is used for all items that do not have filters
- for i, fieldIDs := range []*[]string{stringFldIDs, prefixFldIDs, suffixFldIDs, {utils.MetaAny}} { // same routine for both string and prefix filter types
+ stringFieldVals := map[string]string{utils.MetaAny: utils.MetaAny} // cache here field string values, start with default one
+ filterIndexTypes := []string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix, utils.MetaExists, utils.MetaNone} // the MetaNone is used for all items that do not have filters
+ for i, fieldIDs := range []*[]string{stringFldIDs, prefixFldIDs, suffixFldIDs, existsFldIDs, {utils.MetaAny}} { // same routine for both string and prefix filter types
if fieldIDs == nil {
fieldIDs = &allFieldIDs
}
for _, fldName := range *fieldIDs {
+ var dbItemIDs utils.StringSet // list of items matched in DB
+ if filterIndexTypes[i] == utils.MetaExists {
+ var dbIndexes map[string]utils.StringSet // list of items matched in DB
+ key := utils.ConcatenatedKey(filterIndexTypes[i], fldName)
+ if dbIndexes, err = dm.GetIndexes(cacheID, itemIDPrefix, key, true, true); err != nil {
+ if err == utils.ErrNotFound {
+ err = nil
+ continue
+ }
+ return
+ }
+ dbItemIDs = dbIndexes[key]
+ for itemID := range dbItemIDs {
+ if _, hasIt := itemIDs[itemID]; !hasIt { // Add it to list if not already there
+ itemIDs[itemID] = dbItemIDs[itemID]
+ }
+ }
+ continue // no need to look at values for *exists indexes
+ }
var fieldValIf any
fieldValIf, err = ev.FieldAsInterface(utils.SplitPath(fldName, utils.NestingSep[0], -1))
if err != nil && filterIndexTypes[i] != utils.MetaNone {
@@ -81,7 +100,6 @@ func MatchingItemIDsForEvent(ev utils.MapStorage, stringFldIDs, prefixFldIDs, su
} else if filterIndexTypes[i] == utils.MetaSuffix {
fldVals = utils.SplitSuffix(fldVal) // all suffix till first digit
}
- var dbItemIDs utils.StringSet // list of items matched in DB
for _, val := range fldVals {
var dbIndexes map[string]utils.StringSet // list of items matched in DB
key := utils.ConcatenatedKey(filterIndexTypes[i], fldName, val)
diff --git a/engine/libindex.go b/engine/libindex.go
index 5b2d82f3f..440b865b5 100644
--- a/engine/libindex.go
+++ b/engine/libindex.go
@@ -28,7 +28,7 @@ import (
)
var (
- FilterIndexTypes = utils.NewStringSet([]string{utils.MetaPrefix, utils.MetaString, utils.MetaSuffix})
+ FilterIndexTypes = utils.NewStringSet([]string{utils.MetaPrefix, utils.MetaString, utils.MetaSuffix, utils.MetaExists})
// Element or values of a filter that starts with one of this should not be indexed
ToNotBeIndexed = []string{
utils.DynamicDataPrefix + utils.MetaAccounts,
@@ -86,6 +86,23 @@ func newFilterIndex(dm *DataManager, idxItmType, tnt, ctx, itemID string, filter
continue
}
isDyn := strings.HasPrefix(flt.Element, utils.DynamicDataPrefix)
+ if isDyn && flt.Type == utils.MetaExists {
+ idxKey := utils.ConcatenatedKey(flt.Type, flt.Element[1:])
+ var rcvIndx map[string]utils.StringSet
+ // only read from cache in case if we do not find the index to not cache the negative response
+ if rcvIndx, err = dm.GetIndexes(idxItmType, tntCtx,
+ idxKey, true, false); err != nil {
+ if err != utils.ErrNotFound {
+ return
+ }
+ err = nil
+ indexes[idxKey] = make(utils.StringSet) // create an empty index if is not found in DB in case we add them later
+ continue
+ }
+ for idxKey, idx := range rcvIndx { // parse the received indexes
+ indexes[idxKey] = idx
+ }
+ }
for _, fldVal := range flt.Values {
if IsDynamicDPPath(fldVal) {
continue
diff --git a/engine/libindex_test.go b/engine/libindex_test.go
index 8134b2877..2f2286107 100644
--- a/engine/libindex_test.go
+++ b/engine/libindex_test.go
@@ -20,6 +20,8 @@ package engine
import (
"errors"
+ "reflect"
+ "strconv"
"testing"
"github.com/cgrates/cgrates/config"
@@ -207,3 +209,590 @@ func TestLibIndexNewFilterIndexGetFilterErrNotFound(t *testing.T) {
t.Fatalf("Expected error %v, got %v", expectedErr, err)
}
}
+func TestLibIndex_newFilterIndex(t *testing.T) {
+ cfg := config.NewDefaultCGRConfig()
+ dataDB, err := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
+ if err != nil {
+ t.Fatal(err)
+ }
+ dm := NewDataManager(dataDB, cfg.CacheCfg(), nil)
+
+ for i := range 2 {
+ idx := strconv.Itoa(i + 1)
+ flt := &Filter{
+ Tenant: "cgrates.org",
+ ID: "FLTR_" + idx,
+ Rules: []*FilterRule{
+ {
+ Type: utils.MetaString,
+ Element: "~*req.Field" + idx,
+ Values: []string{"val" + idx},
+ },
+ {
+ Type: utils.MetaPrefix,
+ Element: "~*req.Field" + idx,
+ Values: []string{"val"},
+ },
+ {
+ Type: utils.MetaSuffix,
+ Element: "~*req.Field" + idx,
+ Values: []string{idx},
+ },
+ {
+ Type: utils.MetaExists,
+ Element: "~*req.Field" + idx,
+ },
+ },
+ }
+ if err := dm.SetFilter(flt, true); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ filterIDsList := [][]string{
+ {"*prefix:~*req.Field2:val", "*empty:~*req.Field3:", "FLTR_1"},
+ {"*suffix:~*req.Field1:1", "*notstring:~*req.Field2:val1", "FLTR_2"},
+ {"*exists:~*req.Field2:", "*suffix:~*req.Field1:1", "FLTR_1"},
+ }
+
+ for i, filterIDs := range filterIDsList {
+ idx := strconv.Itoa(i + 1)
+ if err := dm.SetAttributeProfile(&AttributeProfile{
+ Tenant: "cgrates.org",
+ ID: "ATTR_" + idx,
+ FilterIDs: filterIDs,
+ Contexts: []string{utils.MetaCDRs},
+ }, true); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ wantIndexes := map[string]utils.StringSet{
+ "*exists:*req.Field1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*exists:*req.Field2": {
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field1:val": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field2:val": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ },
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*string:*req.Field2:val2": {
+ "ATTR_2": {},
+ },
+ "*suffix:*req.Field1:1": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*suffix:*req.Field2:2": {
+ "ATTR_2": {},
+ },
+ }
+ tntCtx := utils.ConcatenatedKey("cgrates.org", utils.MetaCDRs)
+ gotIndexes, err := dm.GetIndexes(utils.CacheAttributeFilterIndexes, tntCtx, "", false, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(gotIndexes, wantIndexes) {
+ t.Errorf("dm.GetIndexes() = %s, want %s", utils.ToJSON(gotIndexes), utils.ToJSON(wantIndexes))
+ }
+
+ tests := []struct {
+ name string
+ idxItmType string
+ tnt string
+ ctx string
+ itemID string
+ filterIDs []string
+ newFlt *Filter
+ want map[string]utils.StringSet
+ wantErr bool
+ }{
+ {
+ name: "no filterIDs",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ want: map[string]utils.StringSet{
+ "*none:*any:*any": {},
+ },
+ },
+ {
+ name: "newFlt with no filterIDs",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ newFlt: &Filter{
+ Tenant: "cgrates.org",
+ ID: "FLTR_TEST",
+ Rules: []*FilterRule{
+ {
+ Type: utils.MetaString,
+ Element: "~*req.Field",
+ Values: []string{"val"},
+ },
+ },
+ },
+ want: map[string]utils.StringSet{
+ "*none:*any:*any": {},
+ },
+ },
+ {
+ name: "broken reference",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ itemID: "ATTR_1",
+ filterIDs: []string{"FLTR_1"},
+ wantErr: true,
+ },
+ {
+ name: "unindexable filter",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ filterIDs: []string{"*notstring:~*req.Field1:val2"},
+ want: make(map[string]utils.StringSet),
+ },
+ {
+ name: "dynamic element, constant value",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ filterIDs: []string{"*string:~*req.Field1:val1"},
+ want: map[string]utils.StringSet{
+ "*string:*req.Field1:val1": {},
+ },
+ },
+ {
+ name: "constant element, dynamic value",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ filterIDs: []string{"*string:val1:~*req.Field1"},
+ want: map[string]utils.StringSet{
+ "*string:*req.Field1:val1": {},
+ },
+ },
+ {
+ name: "dynamic element, dynamic value",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ filterIDs: []string{"*string:~*req.Field1:~*req.Field1"},
+ want: make(map[string]utils.StringSet),
+ },
+ {
+ name: "constant element, constant value",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ filterIDs: []string{"*string:val1:val1"},
+ want: make(map[string]utils.StringSet),
+ },
+ {
+ name: "filter and tenant without context",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ // ctx: utils.MetaCDRs,
+ filterIDs: []string{"*string:~*req.Field1:val1"},
+ want: map[string]utils.StringSet{
+ "*string:*req.Field1:val1": {},
+ },
+ },
+ {
+ name: "filter and tenant with context (without references)",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: utils.MetaCDRs,
+ filterIDs: []string{"*string:~*req.Random:val"},
+ want: map[string]utils.StringSet{
+ "*string:*req.Random:val": {},
+ },
+ },
+ {
+ name: "filter and tenant with context (with references)",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: utils.MetaCDRs,
+ filterIDs: []string{"*string:~*req.Field1:val1"},
+ want: map[string]utils.StringSet{
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ },
+ },
+ {
+ name: "filterID of newFlt",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: utils.MetaCDRs,
+ filterIDs: []string{"FLTR_TEST"},
+ newFlt: &Filter{
+ Tenant: "cgrates.org",
+ ID: "FLTR_TEST",
+ Rules: []*FilterRule{
+ {
+ Type: utils.MetaString,
+ Element: "~*req.Random",
+ Values: []string{"val"},
+ },
+ {
+ Type: utils.MetaString,
+ Element: "~*req.Field1",
+ Values: []string{"val1"},
+ },
+ },
+ },
+ want: map[string]utils.StringSet{
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*string:*req.Random:val": {},
+ },
+ },
+ {
+ name: "all indexable filters with references",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: "*cdrs",
+ filterIDs: []string{
+ "FLTR_1",
+ "FLTR_2",
+ "*prefix:~*req.Field2:val",
+ "*suffix:~*req.Field1:1",
+ "*suffix:~*req.Field1:1",
+ },
+ want: map[string]utils.StringSet{
+ "*exists:*req.Field1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*exists:*req.Field2": {
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field1:val": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field2:val": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ },
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*string:*req.Field2:val2": {
+ "ATTR_2": {},
+ },
+ "*suffix:*req.Field1:1": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*suffix:*req.Field2:2": {
+ "ATTR_2": {},
+ },
+ },
+ },
+ {
+ name: "all filters",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: "*cdrs",
+ filterIDs: []string{
+ "FLTR_1",
+ "FLTR_2",
+ "*prefix:~*req.Field2:val",
+ "*suffix:~*req.Field1:1",
+ "*exists:~*req.Field2:",
+ "*empty:~*req.Field3:",
+ "*notstring:~*req.Field2:val1",
+ },
+ want: map[string]utils.StringSet{
+ "*exists:*req.Field1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*exists:*req.Field2": {
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field1:val": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field2:val": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ },
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*string:*req.Field2:val2": {
+ "ATTR_2": {},
+ },
+ "*suffix:*req.Field1:1": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*suffix:*req.Field2:2": {
+ "ATTR_2": {},
+ },
+ },
+ },
+ {
+ name: "all filters (with extra unreferenced filters)",
+ idxItmType: utils.CacheAttributeFilterIndexes,
+ tnt: "cgrates.org",
+ ctx: "*cdrs",
+ filterIDs: []string{
+ "FLTR_1",
+ "FLTR_2",
+ "*prefix:~*req.Field2:val",
+ "*suffix:~*req.Field1:1",
+ "*exists:~*req.Field2:",
+ "*empty:~*req.Field3:",
+ "*notstring:~*req.Field2:val1",
+ "*prefix:~*req.Field4:val",
+ "*string:~*req.Field5:val5",
+ },
+ want: map[string]utils.StringSet{
+ "*exists:*req.Field1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*exists:*req.Field2": {
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field1:val": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*prefix:*req.Field2:val": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ },
+ "*string:*req.Field1:val1": {
+ "ATTR_1": {},
+ "ATTR_3": {},
+ },
+ "*string:*req.Field2:val2": {
+ "ATTR_2": {},
+ },
+ "*suffix:*req.Field1:1": {
+ "ATTR_1": {},
+ "ATTR_2": {},
+ "ATTR_3": {},
+ },
+ "*suffix:*req.Field2:2": {
+ "ATTR_2": {},
+ },
+ "*prefix:*req.Field4:val": {},
+ "*string:*req.Field5:val5": {},
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, gotErr := newFilterIndex(dm, tt.idxItmType, tt.tnt, tt.ctx, tt.itemID, tt.filterIDs, tt.newFlt)
+ if gotErr != nil {
+ if !tt.wantErr {
+ t.Errorf("newFilterIndex() failed: %v", gotErr)
+ }
+ return
+ }
+ if tt.wantErr {
+ t.Fatal("newFilterIndex() succeeded unexpectedly")
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("newFilterIndex() = %s, want %s", utils.ToJSON(got), utils.ToJSON(tt.want))
+ }
+ })
+ }
+}
+
+// func TestLibIndex_prepareFilterIndexMap(t *testing.T) {
+// cfg := config.NewDefaultCGRConfig()
+// dataDB := NewInternalDB(nil, nil, true, true, cfg.DataDbCfg().Items)
+// dm := NewDataManager(dataDB, cfg.CacheCfg(), nil)
+//
+// var flt *Filter // to be used as newFlt
+// for i := range 2 {
+// idx := strconv.Itoa(i + 1)
+// flt = &Filter{
+// Tenant: "cgrates.org",
+// ID: "FLTR_" + idx,
+// Rules: []*FilterRule{
+// {
+// Type: utils.MetaString,
+// Element: "~*req.Field" + idx,
+// Values: []string{"val" + idx},
+// },
+// {
+// Type: utils.MetaPrefix,
+// Element: "~*req.Field" + idx,
+// Values: []string{"val"},
+// },
+// {
+// Type: utils.MetaSuffix,
+// Element: "~*req.Field" + idx,
+// Values: []string{idx},
+// },
+// {
+// Type: utils.MetaExists,
+// Element: "~*req.Field" + idx,
+// },
+// },
+// }
+// if err := dm.SetFilter(flt, true); err != nil {
+// t.Fatal(err)
+// }
+// }
+//
+// filterIDsList := [][]string{
+// {"*prefix:~*req.Field2:val", "FLTR_1"},
+// {"*suffix:~*req.Field1:1", "FLTR_2"},
+// {"*exists:~*req.Field2:", "*suffix:~*req.Field1:1", "FLTR_1"},
+// }
+//
+// for i, filterIDs := range filterIDsList {
+// idx := strconv.Itoa(i + 1)
+// if err := dm.SetChargerProfile(&ChargerProfile{
+// Tenant: "cgrates.org",
+// ID: "CP_" + idx,
+// FilterIDs: filterIDs,
+// RunID: "DEFAULT" + idx,
+// AttributeIDs: []string{"*none"},
+// }, true); err != nil {
+// t.Fatal(err)
+// }
+// if err := dm.SetAttributeProfile(&AttributeProfile{
+// Tenant: "cgrates.org",
+// ID: "ATTR_CDRS_" + idx,
+// FilterIDs: filterIDs,
+// Contexts: []string{utils.MetaCDRs},
+// }, true); err != nil {
+// t.Fatal(err)
+// }
+// if err := dm.SetAttributeProfile(&AttributeProfile{
+// Tenant: "cgrates.org",
+// ID: "ATTR_SESSIONS_" + idx,
+// FilterIDs: filterIDs,
+// Contexts: []string{utils.MetaSessionS},
+// }, true); err != nil {
+// t.Fatal(err)
+// }
+// }
+//
+// wantTemplate := func(prefix string) map[string]utils.StringSet {
+// return map[string]utils.StringSet{
+// "*prefix:*req.Field1:val": {
+// prefix + "_1": {},
+// prefix + "_3": {},
+// },
+// "*prefix:*req.Field2:val": {
+// prefix + "_1": {},
+// prefix + "_2": {},
+// },
+// "*string:*req.Field1:val1": {
+// prefix + "_1": {},
+// prefix + "_3": {},
+// },
+// "*string:*req.Field2:val2": {
+// prefix + "_2": {},
+// },
+// "*suffix:*req.Field1:1": {
+// prefix + "_1": {},
+// prefix + "_2": {},
+// prefix + "_3": {},
+// },
+// "*suffix:*req.Field2:2": {
+// prefix + "_2": {},
+// },
+// }
+// }
+//
+// argsList := []struct {
+// itemType string
+// ctx string
+// prefix string // of the profile (CP, ATTR_CDRS, ATTR_SESSIONS)
+// }{
+// {
+// itemType: utils.CacheChargerFilterIndexes,
+// ctx: "",
+// prefix: "CP",
+// },
+// {
+// itemType: utils.CacheAttributeFilterIndexes,
+// ctx: utils.MetaCDRs,
+// prefix: "ATTR_CDRS",
+// },
+// {
+// itemType: utils.CacheAttributeFilterIndexes,
+// ctx: utils.MetaSessionS,
+// prefix: "ATTR_SESSIONS",
+// },
+// }
+//
+// for _, args := range argsList {
+// wantIndexes := wantTemplate(args.prefix)
+// tntCtx := "cgrates.org"
+// if args.ctx != "" {
+// tntCtx = utils.ConcatenatedKey(tntCtx, args.ctx)
+// }
+// gotIndexes, err := dm.GetIndexes(args.itemType, tntCtx, "", false, false)
+// if err != nil {
+// t.Fatal(err)
+// }
+// if !reflect.DeepEqual(gotIndexes, wantIndexes) {
+// t.Errorf("dm.GetIndexes() = %s, want %s", utils.ToJSON(gotIndexes), utils.ToJSON(wantIndexes))
+// }
+//
+// }
+//
+// tests := []struct {
+// name string
+// idxItmType string
+// tnt string
+// ctx string
+// itemID string
+// filterIDs []string
+// newFlt *Filter
+// want map[string]utils.StringSet
+// wantErr bool
+// }{
+// {
+// name: "test1",
+// idxItmType: utils.CacheAttributeFilterIndexes,
+// tnt: "cgrates.org",
+// ctx: "*cdrs",
+// itemID: "ATTR_CDRS",
+// filterIDs: []string{"FLTR_1"},
+// newFlt: flt,
+// want: map[string]utils.StringSet{},
+// },
+// }
+//
+// for _, tt := range tests {
+// t.Run(tt.name, func(t *testing.T) {
+// got, gotErr := prepareFilterIndexMap(dm, tt.idxItmType, tt.tnt, tt.ctx, tt.itemID, tt.filterIDs, tt.newFlt)
+// if gotErr != nil {
+// if !tt.wantErr {
+// t.Errorf("prepareFilterIndexMap() failed: %v", gotErr)
+// }
+// return
+// }
+// if tt.wantErr {
+// t.Fatal("prepareFilterIndexMap() succeeded unexpectedly")
+// }
+// if !reflect.DeepEqual(got, tt.want) {
+// t.Errorf("prepareFilterIndexMap() = %s, want %s", utils.ToJSON(got), utils.ToJSON(tt.want))
+// }
+// })
+// }
+// }
diff --git a/engine/libtest.go b/engine/libtest.go
index d17a5c3f2..302a993b5 100644
--- a/engine/libtest.go
+++ b/engine/libtest.go
@@ -630,15 +630,23 @@ func startEngine(t testing.TB, cfg *config.CGRConfig, logBuffer io.Writer, grace
t.Cleanup(func() {
if gracefulShutdown {
if err := engine.Process.Signal(syscall.SIGTERM); err != nil {
- t.Errorf("failed to kill cgr-engine process (%d): %v", engine.Process.Pid, err)
+ t.Errorf("failed to terminate cgr-engine process (%d): %v",
+ engine.Process.Pid, err)
}
+
+ // TODO: check if it's a good idea to wait for the Kill signal
+ // also. Might prevent scenarios where engine from previous test is
+ // still running (even though kill should be almost instant)
if err := engine.Wait(); err != nil {
- t.Errorf("cgr-engine process failed to exit cleanly: %v", err)
- }
- } else {
- if err := engine.Process.Kill(); err != nil {
- t.Errorf("failed to kill cgr-engine process (%d): %v", engine.Process.Pid, err)
+ t.Errorf("could not wait for cgr-engine process (%d) to shut down: %v",
+ engine.Process.Pid, err)
}
+
+ return
+ }
+ if err := engine.Process.Kill(); err != nil {
+ t.Errorf("failed to kill cgr-engine process (%d): %v",
+ engine.Process.Pid, err)
}
})
backoff := utils.FibDuration(time.Millisecond, 0)
diff --git a/engine/resources.go b/engine/resources.go
index 9f736e7a5..9f6d8a754 100644
--- a/engine/resources.go
+++ b/engine/resources.go
@@ -659,6 +659,7 @@ func (rS *ResourceService) matchingResourcesForEvent(tnt string, ev *utils.CGREv
rS.cgrcfg.ResourceSCfg().StringIndexedFields,
rS.cgrcfg.ResourceSCfg().PrefixIndexedFields,
rS.cgrcfg.ResourceSCfg().SuffixIndexedFields,
+ rS.cgrcfg.ResourceSCfg().ExistsIndexedFields,
rS.dm, utils.CacheResourceFilterIndexes, tnt,
rS.cgrcfg.ResourceSCfg().IndexedSelects,
rS.cgrcfg.ResourceSCfg().NestedFields,
diff --git a/engine/routes.go b/engine/routes.go
index 6b236e8f8..f9d250693 100644
--- a/engine/routes.go
+++ b/engine/routes.go
@@ -230,6 +230,7 @@ func (rpS *RouteService) matchingRouteProfilesForEvent(tnt string, ev *utils.CGR
rpS.cgrcfg.RouteSCfg().StringIndexedFields,
rpS.cgrcfg.RouteSCfg().PrefixIndexedFields,
rpS.cgrcfg.RouteSCfg().SuffixIndexedFields,
+ rpS.cgrcfg.RouteSCfg().ExistsIndexedFields,
rpS.dm, utils.CacheRouteFilterIndexes, tnt,
rpS.cgrcfg.RouteSCfg().IndexedSelects,
rpS.cgrcfg.RouteSCfg().NestedFields,
diff --git a/engine/stats.go b/engine/stats.go
index 49bdb6c89..02a4e401a 100644
--- a/engine/stats.go
+++ b/engine/stats.go
@@ -165,6 +165,7 @@ func (sS *StatService) matchingStatQueuesForEvent(tnt string, statsIDs []string,
sS.cgrcfg.StatSCfg().StringIndexedFields,
sS.cgrcfg.StatSCfg().PrefixIndexedFields,
sS.cgrcfg.StatSCfg().SuffixIndexedFields,
+ sS.cgrcfg.StatSCfg().ExistsIndexedFields,
sS.dm, utils.CacheStatFilterIndexes, tnt,
sS.cgrcfg.StatSCfg().IndexedSelects,
sS.cgrcfg.StatSCfg().NestedFields,
diff --git a/engine/thresholds.go b/engine/thresholds.go
index 61ce5b969..8ec0d5b74 100644
--- a/engine/thresholds.go
+++ b/engine/thresholds.go
@@ -411,6 +411,7 @@ func (tS *ThresholdService) matchingThresholdsForEvent(tnt string, args *utils.C
tS.cgrcfg.ThresholdSCfg().StringIndexedFields,
tS.cgrcfg.ThresholdSCfg().PrefixIndexedFields,
tS.cgrcfg.ThresholdSCfg().SuffixIndexedFields,
+ tS.cgrcfg.ThresholdSCfg().ExistsIndexedFields,
tS.dm, utils.CacheThresholdFilterIndexes, tnt,
tS.cgrcfg.ThresholdSCfg().IndexedSelects,
tS.cgrcfg.ThresholdSCfg().NestedFields,
diff --git a/engine/z_filterhelpers_test.go b/engine/z_filterhelpers_test.go
index ae0d68a19..aaa196bd8 100644
--- a/engine/z_filterhelpers_test.go
+++ b/engine/z_filterhelpers_test.go
@@ -103,7 +103,7 @@ func TestFilterMatchingItemIDsForEvent(t *testing.T) {
utils.AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"Field": "profile",
}}
- aPrflIDs, err := MatchingItemIDsForEvent(matchEV, nil, nil, nil,
+ aPrflIDs, err := MatchingItemIDsForEvent(matchEV, nil, nil, nil, nil,
dmMatch, utils.CacheAttributeFilterIndexes, tntCtx, true, false)
if err != nil {
t.Errorf("Error: %+v", err)
@@ -116,7 +116,7 @@ func TestFilterMatchingItemIDsForEvent(t *testing.T) {
matchEV = utils.MapStorage{utils.MetaReq: map[string]any{
"Field": "profilePrefix",
}}
- aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil,
+ aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil, nil,
dmMatch, utils.CacheAttributeFilterIndexes, tntCtx, true, false)
if err != nil {
t.Errorf("Error: %+v", err)
@@ -129,7 +129,7 @@ func TestFilterMatchingItemIDsForEvent(t *testing.T) {
matchEV = utils.MapStorage{utils.MetaReq: map[string]any{
"Field": "profilePrefix",
}}
- aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil,
+ aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil, nil,
dmMatch, utils.CacheAttributeFilterIndexes, tntCtx, true, false)
if err != nil {
t.Errorf("Error: %+v", err)
@@ -196,7 +196,7 @@ func TestFilterMatchingItemIDsForEvent2(t *testing.T) {
utils.AnswerTime: time.Date(2014, 7, 14, 14, 30, 0, 0, time.UTC),
"CallCost": map[string]any{"Account": 1001},
}}
- aPrflIDs, err := MatchingItemIDsForEvent(matchEV, nil, nil, nil,
+ aPrflIDs, err := MatchingItemIDsForEvent(matchEV, nil, nil, nil, nil,
dmMatch, utils.CacheAttributeFilterIndexes, tntCtx, true, true)
if err != nil {
t.Errorf("Error: %+v", err)
@@ -208,7 +208,7 @@ func TestFilterMatchingItemIDsForEvent2(t *testing.T) {
matchEV = utils.MapStorage{utils.MetaReq: map[string]any{
"CallCost": map[string]any{"Field": "profilePrefix"},
}}
- aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil,
+ aPrflIDs, err = MatchingItemIDsForEvent(matchEV, nil, nil, nil, nil,
dmMatch, utils.CacheAttributeFilterIndexes, tntCtx, true, true)
if err != nil {
t.Errorf("Error: %+v", err)
diff --git a/utils/consts.go b/utils/consts.go
index e5e8021be..5a3415d21 100644
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -2163,6 +2163,7 @@ const (
StringIndexedFieldsCfg = "string_indexed_fields"
PrefixIndexedFieldsCfg = "prefix_indexed_fields"
SuffixIndexedFieldsCfg = "suffix_indexed_fields"
+ ExistsIndexedFieldsCfg = "exists_indexed_fields"
MongoQueryTimeoutCfg = "mongoQueryTimeout"
MongoConnSchemeCfg = "mongoConnScheme"
PgSSLModeCfg = "pgSSLMode"