Update tests with new changes on AttributeProfile

This commit is contained in:
TeoV
2018-01-09 15:46:51 +02:00
committed by Dan Christian Bogos
parent c16d864ff4
commit 62b4293f10
13 changed files with 134 additions and 128 deletions

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"github.com/cgrates/cgrates/cache"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -49,17 +48,22 @@ func (apierV1 *ApierV1) SetAttributeProfile(extAls *engine.ExternalAttributeProf
if err := apierV1.DataManager.SetAttributeProfile(alsPrf, true); err != nil {
return utils.APIErrorHandler(err)
}
cache.RemKey(utils.AttributeProfilePrefix+utils.ConcatenatedKey(extAls.Tenant, extAls.ID), true, "") // ToDo: Remove here with autoreload
*reply = utils.OK
return nil
}
type ArgRemoveAttrPrf struct {
Tenant string
ID string
Contexts []string
}
//RemAttributeProfile remove a specific Attribute Profile
func (apierV1 *ApierV1) RemAttributeProfile(arg utils.TenantID, contexts []string, reply *string) error {
if missing := utils.MissingStructFields(&arg, []string{"Tenant", "ID"}); len(missing) != 0 { //Params missing
func (apierV1 *ApierV1) RemAttributeProfile(arg ArgRemoveAttrPrf, reply *string) error {
if missing := utils.MissingStructFields(&arg, []string{"Tenant", "ID", "Contexts"}); len(missing) != 0 { //Params missing
return utils.NewErrMandatoryIeMissing(missing...)
}
if err := apierV1.DataManager.RemoveAttributeProfile(arg.Tenant, arg.ID, contexts, utils.NonTransactional, true); err != nil {
if err := apierV1.DataManager.RemoveAttributeProfile(arg.Tenant, arg.ID, arg.Contexts, utils.NonTransactional, true); err != nil {
if err.Error() != utils.ErrNotFound.Error() {
err = utils.NewErrServerError(err)
}

View File

@@ -335,7 +335,7 @@ func testAttributeSUpdateAlsPrf(t *testing.T) {
func testAttributeSRemAlsPrf(t *testing.T) {
var resp string
if err := attrSRPC.Call("ApierV1.RemAttributeProfile", &utils.TenantID{Tenant: "cgrates.org", ID: "ApierTest"}, &resp); err != nil {
if err := attrSRPC.Call("ApierV1.RemAttributeProfile", &ArgRemoveAttrPrf{Tenant: alsPrf.Tenant, ID: alsPrf.ID, Contexts: alsPrf.Contexts}, &resp); err != nil {
t.Error(err)
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)

View File

@@ -1282,7 +1282,7 @@ func testV1FIdxSetAttributeProfileIndexes(t *testing.T) {
alsPrf = &engine.ExternalAttributeProfile{
Tenant: "cgrates.org",
ID: "ApierTest",
Contexts: []string{"*rating1", "*rating2"},
Contexts: []string{"*rating"},
FilterIDs: []string{"FLTR_1"},
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC).Local(),
@@ -1495,22 +1495,23 @@ func testV1FIdxRemoveAttributeProfile(t *testing.T) {
t.Errorf("Error: %+v", reply2)
}
if err := tFIdxRpc.Call("ApierV1.RemAttributeProfile",
&utils.TenantID{Tenant: tenant, ID: "ApierTest"}, &resp); err != nil {
&ArgRemoveAttrPrf{Tenant: "cgrates.org", ID: "ApierTest", Contexts: []string{"*rating"}}, &resp); err != nil {
t.Error(err)
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
}
if err := tFIdxRpc.Call("ApierV1.RemAttributeProfile",
&utils.TenantID{Tenant: tenant, ID: "ApierTest2"}, &resp); err != nil {
&ArgRemoveAttrPrf{Tenant: "cgrates.org", ID: "ApierTest2", Contexts: []string{"*rating"}}, &resp); err != nil {
t.Error(err)
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
}
if err := tFIdxRpc.Call("ApierV1.GetAttributeProfile", &utils.TenantID{Tenant: tenant, ID: "ApierTest2"}, &reply2); err == nil ||
var reply *engine.ExternalAttributeProfile
if err := tFIdxRpc.Call("ApierV1.GetAttributeProfile", &utils.TenantID{Tenant: tenant, ID: "ApierTest2"}, &reply); err == nil ||
err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}
if err := tFIdxRpc.Call("ApierV1.GetAttributeProfile", &utils.TenantID{Tenant: tenant, ID: "ApierTest"}, &reply2); err == nil ||
if err := tFIdxRpc.Call("ApierV1.GetAttributeProfile", &utils.TenantID{Tenant: tenant, ID: "ApierTest"}, &reply); err == nil ||
err.Error() != utils.ErrNotFound.Error() {
t.Error(err)
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"github.com/cgrates/cgrates/cache"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -82,7 +81,6 @@ func (apierV1 *ApierV1) SetResourceProfile(res *engine.ResourceProfile, reply *s
if err := apierV1.DataManager.SetResourceProfile(res, true); err != nil {
return utils.APIErrorHandler(err)
}
cache.RemKey(utils.ResourceProfilesPrefix+utils.ConcatenatedKey(res.Tenant, res.ID), true, "") // ToDo: Remove here with autoreload
*reply = utils.OK
return nil
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"github.com/cgrates/cgrates/cache"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -46,8 +45,6 @@ func (apierV1 *ApierV1) SetStatQueueProfile(sqp *engine.StatQueueProfile, reply
if err := apierV1.DataManager.SetStatQueueProfile(sqp, true); err != nil {
return utils.APIErrorHandler(err)
}
cache.RemKey(utils.StatQueueProfilePrefix+utils.ConcatenatedKey(sqp.Tenant, sqp.ID),
true, utils.NonTransactional) // Temporary work around util proper cacheDataFromDB will be implemented
*reply = utils.OK
return nil
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"github.com/cgrates/cgrates/cache"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -48,7 +47,6 @@ func (apierV1 *ApierV1) SetSupplierProfile(spp *engine.SupplierProfile, reply *s
if err := apierV1.DataManager.SetSupplierProfile(spp, true); err != nil {
return utils.APIErrorHandler(err)
}
cache.RemKey(utils.SupplierProfilePrefix+utils.ConcatenatedKey(spp.Tenant, spp.ID), true, "") // ToDo: Remove here with autoreload
*reply = utils.OK
return nil
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package v1
import (
"github.com/cgrates/cgrates/cache"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
@@ -80,7 +79,6 @@ func (apierV1 *ApierV1) SetThresholdProfile(thp *engine.ThresholdProfile, reply
if err := apierV1.DataManager.SetThresholdProfile(thp, true); err != nil {
return utils.APIErrorHandler(err)
}
cache.RemKey(utils.ThresholdProfilePrefix+utils.ConcatenatedKey(thp.Tenant, thp.ID), true, "") // ToDo: Remove here with autoreload
*reply = utils.OK
return nil
}

View File

@@ -41,7 +41,7 @@ var sTestsAttributes = []func(t *testing.T){
func TestAttributes(t *testing.T) {
for _, stest := range sTestsAttributes {
t.Run("Test Suppliers", stest)
t.Run("Test Attributes", stest)
}
}
@@ -116,6 +116,7 @@ func testPopulateAttrService(t *testing.T) {
filterS: &FilterS{dm: dmAtr},
indexedFields: []string{"attributeprofile1", "attributeprofile2"},
}
ev := make(map[string]interface{})
ev["attributeprofile1"] = "Attribute"
ev["attributeprofile2"] = "Attribute"
@@ -131,7 +132,9 @@ func testPopulateAttrService(t *testing.T) {
}
for _, atr := range atrPs {
dmAtr.SetAttributeProfile(atr, false)
if err = dmAtr.SetAttributeProfile(atr, true); err != nil {
t.Errorf("Error: %+v", err)
}
}
prefix := utils.ConcatenatedKey(sev.Tenant, *sev.Context)
ref := NewReqFilterIndexer(dmAtr, utils.AttributeProfilePrefix, prefix)

View File

@@ -383,6 +383,7 @@ func (dm *DataManager) SetThresholdProfile(th *ThresholdProfile, withIndex bool)
if err = dm.DataDB().SetThresholdProfileDrv(th); err != nil {
return err
}
cache.RemKey(utils.ThresholdProfilePrefix+utils.ConcatenatedKey(th.Tenant, th.ID), true, "") // ToDo: Remove here with autoreload
if withIndex {
//remove old ThresholdProfile indexes
indexerRemove := NewReqFilterIndexer(dm, utils.ThresholdProfilePrefix, th.Tenant)
@@ -456,6 +457,8 @@ func (dm *DataManager) SetStatQueueProfile(sqp *StatQueueProfile, withIndex bool
if err = dm.DataDB().SetStatQueueProfileDrv(sqp); err != nil {
return err
}
cache.RemKey(utils.StatQueueProfilePrefix+utils.ConcatenatedKey(sqp.Tenant, sqp.ID),
true, utils.NonTransactional) // Temporary work around util proper cacheDataFromDB will be implemented
if withIndex {
indexer := NewReqFilterIndexer(dm, utils.StatQueueProfilePrefix, sqp.Tenant)
//remove old StatQueueProfile indexes
@@ -596,6 +599,7 @@ func (dm *DataManager) SetResourceProfile(rp *ResourceProfile, withIndex bool) (
if err = dm.DataDB().SetResourceProfileDrv(rp); err != nil {
return err
}
cache.RemKey(utils.ResourceProfilesPrefix+utils.ConcatenatedKey(rp.Tenant, rp.ID), true, "") // ToDo: Remove here with autoreload
//to be implemented in tests
if withIndex {
indexer := NewReqFilterIndexer(dm, utils.ResourceProfilesPrefix, rp.Tenant)
@@ -1054,6 +1058,11 @@ func (dm *DataManager) SetSupplierProfile(supp *SupplierProfile, withIndex bool)
if err = dm.DataDB().SetSupplierProfileDrv(supp); err != nil {
return err
}
cache.RemKey(utils.SupplierProfilePrefix+utils.ConcatenatedKey(supp.Tenant, supp.ID), true, "")
ids := []string{supp.ID}
if err = dm.CacheDataFromDB(utils.SupplierProfilePrefix, ids, true); err != nil {
return
}
//to be implemented in tests
if withIndex {
indexer := NewReqFilterIndexer(dm, utils.SupplierProfilePrefix, supp.Tenant)
@@ -1131,6 +1140,9 @@ func (dm *DataManager) SetAttributeProfile(ap *AttributeProfile, withIndex bool)
if err = dm.DataDB().SetAttributeProfileDrv(ap); err != nil {
return err
}
if err = dm.CacheDataFromDB(utils.AttributeProfilePrefix, []string{ap.ID}, true); err != nil {
return
}
//to be implemented in tests
if withIndex {
if oldAP != nil {
@@ -1164,28 +1176,21 @@ func (dm *DataManager) SetAttributeProfile(ap *AttributeProfile, withIndex bool)
}
return
}
for _, flt := range fltr.RequestFilters {
if flt.Type != MetaString {
continue
}
for _, fldVal := range flt.Values {
if err = indexer.loadFldNameFldValIndex(flt.FieldName, fldVal); err != nil && err != utils.ErrNotFound {
return err
}
}
}
indexer.IndexTPFilter(FilterToTPFilter(fltr), ap.ID)
}
if err = indexer.StoreIndexes(); err != nil {
return
}
<<<<<<< HEAD
for _, flt := range fltr.RequestFilters {
if flt.Type != MetaString {
continue
}
for _, fldVal := range flt.Values {
if err = indexer.loadFldNameFldValIndex(flt.FieldName, fldVal); err != nil && err != utils.ErrNotFound {
return err
}
}
}
indexer.IndexTPFilter(FilterToTPFilter(fltr), ap.ID)
}
if err = indexer.StoreIndexes(); err != nil {
return
=======
>>>>>>> Update Contexts and indexing for AttributeProfile
}
}
return

View File

@@ -43,69 +43,69 @@ var (
var sTestsOnStorIT = []func(t *testing.T){
testOnStorITFlush,
testOnStorITIsDBEmpty,
// testOnStorITSetGetDerivedCharges,
// testOnStorITSetFilterIndexes,
// testOnStorITGetFilterIndexes,
// testOnStorITMatchFilterIndex,
// testOnStorITCacheDestinations,
// testOnStorITCacheReverseDestinations,
// testOnStorITCacheRatingPlan,
// testOnStorITCacheRatingProfile,
// testOnStorITCacheActions,
// testOnStorITCacheActionPlan,
// testOnStorITCacheAccountActionPlans,
// testOnStorITCacheActionTriggers,
// testOnStorITCacheSharedGroup,
// testOnStorITCacheDerivedChargers,
// testOnStorITCacheLCR,
// testOnStorITCacheAlias,
// testOnStorITCacheReverseAlias,
// testOnStorITCacheResource,
// testOnStorITCacheResourceProfile,
// testOnStorITCacheStatQueueProfile,
// testOnStorITCacheStatQueue,
// testOnStorITCacheThresholdProfile,
// testOnStorITCacheThreshold,
// testOnStorITCacheTiming,
// testOnStorITCacheFilter,
// testOnStorITCacheSupplierProfile,
// testOnStorITCacheAttributeProfile,
testOnStorITSetGetDerivedCharges,
testOnStorITSetFilterIndexes,
testOnStorITGetFilterIndexes,
testOnStorITMatchFilterIndex,
testOnStorITCacheDestinations,
testOnStorITCacheReverseDestinations,
testOnStorITCacheRatingPlan,
testOnStorITCacheRatingProfile,
testOnStorITCacheActions,
testOnStorITCacheActionPlan,
testOnStorITCacheAccountActionPlans,
testOnStorITCacheActionTriggers,
testOnStorITCacheSharedGroup,
testOnStorITCacheDerivedChargers,
testOnStorITCacheLCR,
testOnStorITCacheAlias,
testOnStorITCacheReverseAlias,
testOnStorITCacheResource,
testOnStorITCacheResourceProfile,
testOnStorITCacheStatQueueProfile,
testOnStorITCacheStatQueue,
testOnStorITCacheThresholdProfile,
testOnStorITCacheThreshold,
testOnStorITCacheTiming,
testOnStorITCacheFilter,
testOnStorITCacheSupplierProfile,
testOnStorITCacheAttributeProfile,
// ToDo: test cache flush for a prefix
// ToDo: testOnStorITLoadAccountingCache
// testOnStorITHasData,
// testOnStorITPushPop,
// testOnStorITCRUDRatingPlan,
// testOnStorITCRUDRatingProfile,
// testOnStorITCRUDDestinations,
// testOnStorITCRUDReverseDestinations,
// testOnStorITCRUDLCR,
// testOnStorITCRUDCdrStats,
// testOnStorITCRUDActions,
// testOnStorITCRUDSharedGroup,
// testOnStorITCRUDActionTriggers,
// testOnStorITCRUDActionPlan,
// testOnStorITCRUDAccountActionPlans,
// testOnStorITCRUDAccount,
// testOnStorITCRUDCdrStatsQueue,
// testOnStorITCRUDSubscribers,
// testOnStorITCRUDUser,
// testOnStorITCRUDAlias,
// testOnStorITCRUDReverseAlias,
// testOnStorITCRUDResource,
// testOnStorITCRUDResourceProfile,
// testOnStorITCRUDTiming,
// testOnStorITCRUDHistory,
// testOnStorITCRUDStructVersion,
// testOnStorITCRUDStatQueueProfile,
// testOnStorITCRUDStoredStatQueue,
// testOnStorITCRUDThresholdProfile,
// testOnStorITCRUDThreshold,
// testOnStorITCRUDFilter,
// testOnStorITCRUDSupplierProfile,
// testOnStorITCRUDAttributeProfile,
// testOnStorITFlush,
// testOnStorITIsDBEmpty,
// testOnStorITTestThresholdFilterIndexes,
testOnStorITHasData,
testOnStorITPushPop,
testOnStorITCRUDRatingPlan,
testOnStorITCRUDRatingProfile,
testOnStorITCRUDDestinations,
testOnStorITCRUDReverseDestinations,
testOnStorITCRUDLCR,
testOnStorITCRUDCdrStats,
testOnStorITCRUDActions,
testOnStorITCRUDSharedGroup,
testOnStorITCRUDActionTriggers,
testOnStorITCRUDActionPlan,
testOnStorITCRUDAccountActionPlans,
testOnStorITCRUDAccount,
testOnStorITCRUDCdrStatsQueue,
testOnStorITCRUDSubscribers,
testOnStorITCRUDUser,
testOnStorITCRUDAlias,
testOnStorITCRUDReverseAlias,
testOnStorITCRUDResource,
testOnStorITCRUDResourceProfile,
testOnStorITCRUDTiming,
testOnStorITCRUDHistory,
testOnStorITCRUDStructVersion,
testOnStorITCRUDStatQueueProfile,
testOnStorITCRUDStoredStatQueue,
testOnStorITCRUDThresholdProfile,
testOnStorITCRUDThreshold,
testOnStorITCRUDFilter,
testOnStorITCRUDSupplierProfile,
testOnStorITCRUDAttributeProfile,
testOnStorITFlush,
testOnStorITIsDBEmpty,
testOnStorITTestThresholdFilterIndexes,
testOnStorITTestAttributeProfileFilterIndexes,
}
@@ -2907,7 +2907,7 @@ func testOnStorITTestAttributeProfileFilterIndexes(t *testing.T) {
}
attrProfile := &AttributeProfile{
Tenant: "cgrates.org",
ID: "ATTRPRF1",
ID: "AttrPrf",
FilterIDs: []string{"Filter1"},
ActivationInterval: &utils.ActivationInterval{
ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local(),
@@ -2922,14 +2922,14 @@ func testOnStorITTestAttributeProfileFilterIndexes(t *testing.T) {
}
eIdxes := map[string]utils.StringMap{
"EventType:Event1": utils.StringMap{
"ATTRPRF1": true,
"AttrPrf": true,
},
"EventType:Event2": utils.StringMap{
"ATTRPRF1": true,
"AttrPrf": true,
},
}
reverseIdxes := map[string]utils.StringMap{
"ATTRPRF1": utils.StringMap{
"AttrPrf": utils.StringMap{
"EventType:Event1": true,
"EventType:Event2": true,
},
@@ -2996,4 +2996,20 @@ func testOnStorITTestAttributeProfileFilterIndexes(t *testing.T) {
}
}
if err := onStor.RemoveAttributeProfile(attrProfile.Tenant, attrProfile.ID, attrProfile.Contexts, utils.NonTransactional, true); err != nil {
t.Error(err)
}
//check if index is removed
rfi = NewReqFilterIndexer(onStor, utils.AttributeProfilePrefix, utils.ConcatenatedKey("cgrates.org", "con3"))
if _, err := onStor.GetFilterIndexes(
GetDBIndexKey(rfi.itemType, rfi.dbKeySuffix, false),
nil); err != nil && err != utils.ErrNotFound {
t.Error(err)
}
if _, err := onStor.GetFilterReverseIndexes(
GetDBIndexKey(rfi.itemType, rfi.dbKeySuffix, true),
nil); err != nil && err != utils.ErrNotFound {
t.Error(err)
}
}

View File

@@ -1252,8 +1252,11 @@ func (ms *MapStorage) GetFilterIndexesDrv(dbKey string,
if _, has := indexes[utils.ConcatenatedKey(fldName, fldVal)]; !has {
indexes[utils.ConcatenatedKey(fldName, fldVal)] = make(utils.StringMap)
}
indexes[utils.ConcatenatedKey(fldName, fldVal)] = rcvidx[utils.ConcatenatedKey(fldName, fldVal)]
if len(rcvidx[utils.ConcatenatedKey(fldName, fldVal)]) != 0 {
indexes[utils.ConcatenatedKey(fldName, fldVal)] = rcvidx[utils.ConcatenatedKey(fldName, fldVal)]
}
}
return
} else {
err = ms.ms.Unmarshal(values, &indexes)

View File

@@ -271,7 +271,7 @@ func TestSuppliersPopulateSupplierService(t *testing.T) {
}
for _, spr := range sprsmatch {
dmspl.SetSupplierProfile(spr, false)
dmspl.SetSupplierProfile(spr, true)
}
ref := NewReqFilterIndexer(dmspl, utils.SupplierProfilePrefix, "cgrates.org")
ref.IndexTPFilter(FilterToTPFilter(filter3), "supplierprofile1")
@@ -280,23 +280,6 @@ func TestSuppliersPopulateSupplierService(t *testing.T) {
if err != nil {
t.Errorf("Error: %+v", err)
}
//test here GetReqFilterIndexes for StorageMap with a specific map
expidx := map[string]utils.StringMap{
"supplierprofile1:Supplier": {
"supplierprofile1": true,
},
}
splPrf1 := make(map[string]string)
splPrf1["supplierprofile1"] = "Supplier"
if rcvidx, err := dmspl.GetFilterIndexes(
GetDBIndexKey(utils.SupplierProfilePrefix, "cgrates.org", false),
splPrf1); err != nil {
t.Errorf("Error: %+v", err)
} else {
if !reflect.DeepEqual(utils.ToJSON(expidx), utils.ToJSON(rcvidx)) {
t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(expidx), utils.ToJSON(rcvidx))
}
}
}
func TestSuppliersmatchingSupplierProfilesForEvent(t *testing.T) {

View File

@@ -1823,34 +1823,34 @@ func (tpr *TpReader) LoadAttributeProfilesFiltered(tag string) (err error) {
mapRsPfls[utils.TenantID{Tenant: rl.Tenant, ID: rl.ID}] = rl
}
tpr.attributeProfiles = mapRsPfls
for tntID, res := range mapRsPfls {
for tntID, attrP := range mapRsPfls {
if has, err := tpr.dm.HasData(utils.AttributeProfilePrefix, tntID.TenantID()); err != nil {
return err
} else if !has {
tpr.attrTntID = append(tpr.attrTntID, &utils.TenantID{Tenant: tntID.Tenant, ID: tntID.ID})
}
// index attribute profile for filters
for _, context := range res.Contexts {
for _, context := range attrP.Contexts {
attrKey := utils.ConcatenatedKey(tntID.Tenant, context)
if _, has := tpr.attrIndexers[attrKey]; !has {
if tpr.attrIndexers[attrKey] = NewReqFilterIndexer(tpr.dm, utils.AttributeProfilePrefix, attrKey); err != nil {
return
}
}
for _, fltrID := range res.FilterIDs {
for _, fltrID := range attrP.FilterIDs {
tpFltr, has := tpr.filters[utils.TenantID{Tenant: tntID.Tenant, ID: fltrID}]
if !has {
var fltr *Filter
if fltr, err = tpr.dm.GetFilter(tntID.Tenant, fltrID, false, utils.NonTransactional); err != nil {
if err == utils.ErrNotFound {
err = fmt.Errorf("broken reference to filter: %+v for resoruce: %+v", fltrID, res)
err = fmt.Errorf("broken reference to filter: %+v for resoruce: %+v", fltrID, attrP)
}
return
} else {
tpFltr = FilterToTPFilter(fltr)
}
} else {
tpr.attrIndexers[attrKey].IndexTPFilter(tpFltr, res.ID)
tpr.attrIndexers[attrKey].IndexTPFilter(tpFltr, attrP.ID)
}
}
}