From 775621dddefcbe1d531f1acec5be652ceffc6a1c Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Mon, 24 Feb 2025 13:10:32 +0530 Subject: [PATCH 1/2] chore: use common function for field, label and tag selectors --- query/config_changes.go | 2 +- query/resource_selector.go | 95 ++++++-------------------------------- 2 files changed, 14 insertions(+), 83 deletions(-) diff --git a/query/config_changes.go b/query/config_changes.go index b8133ba3..32caf988 100644 --- a/query/config_changes.go +++ b/query/config_changes.go @@ -329,7 +329,7 @@ func FindCatalogChanges(ctx context.Context, req CatalogChangesSearchRequest) (* } requirements, _ := parsedLabelSelector.Requirements() for _, r := range requirements { - query = tagSelectorRequirementsToSQLClause(query, r) + query = jsonColumnRequirementsToSQLClause(query, "tags", r) } } diff --git a/query/resource_selector.go b/query/resource_selector.go index 021c9172..b39cb88d 100644 --- a/query/resource_selector.go +++ b/query/resource_selector.go @@ -8,7 +8,6 @@ import ( "github.com/flanksource/commons/collections" "github.com/flanksource/commons/duration" - "github.com/flanksource/commons/logger" "github.com/google/uuid" "github.com/patrickmn/go-cache" @@ -208,7 +207,7 @@ func SetResourceSelectorClause( } requirements, _ := parsedTagSelector.Requirements() for _, r := range requirements { - query = tagSelectorRequirementsToSQLClause(query, r) + query = jsonColumnRequirementsToSQLClause(query, "tags", r) } } } @@ -224,7 +223,7 @@ func SetResourceSelectorClause( } requirements, _ := parsedLabelSelector.Requirements() for _, r := range requirements { - query = labelSelectorRequirementToSQLClause(query, r) + query = jsonColumnRequirementsToSQLClause(query, "labels", r) } } @@ -236,7 +235,7 @@ func SetResourceSelectorClause( requirements, _ := parsedFieldSelector.Requirements() for _, r := range requirements { - query = fieldSelectorRequirementToSQLClause(query, r) + query = jsonColumnRequirementsToSQLClause(query, "properties", r) } } @@ -319,108 +318,40 @@ func queryResourceSelector( return output, nil } -// tagSelectorRequirementsToSQLClause to converts each selector requirement into a gorm SQL clause -func tagSelectorRequirementsToSQLClause(q *gorm.DB, r labels.Requirement) *gorm.DB { +// jsonColumnRequirementsToSQLClause to converts each selector requirement into a gorm SQL clause for a column +func jsonColumnRequirementsToSQLClause(q *gorm.DB, column string, r labels.Requirement) *gorm.DB { switch r.Operator() { case selection.Equals, selection.DoubleEquals: for val := range r.Values() { - q = q.Where("tags @> ?", types.JSONStringMap{r.Key(): val}) + q = q.Where(fmt.Sprintf("%s @> ?", column), types.JSONStringMap{r.Key(): val}) } case selection.NotEquals: for val := range r.Values() { - q = q.Where(fmt.Sprintf("tags->>'%s' != ?", r.Key()), lo.Ternary[any](val == "nil", nil, val)) + q = q.Where(fmt.Sprintf("%s->>'%s' != ?", column, r.Key()), lo.Ternary[any](val == "nil", nil, val)) } case selection.In: - q = q.Where(fmt.Sprintf("tags->>'%s' IN ?", r.Key()), collections.MapKeys(r.Values())) + q = q.Where(fmt.Sprintf("%s->>'%s' IN ?", column, r.Key()), collections.MapKeys(r.Values())) case selection.NotIn: - q = q.Where(fmt.Sprintf("tags->>'%s' NOT IN ?", r.Key()), collections.MapKeys(r.Values())) + q = q.Where(fmt.Sprintf("%s->>'%s' NOT IN ?", column, r.Key()), collections.MapKeys(r.Values())) case selection.DoesNotExist: for val := range r.Values() { - q = q.Where(fmt.Sprintf("tags->>'%s' IS NULL", val)) + q = q.Where(fmt.Sprintf("%s->>'%s' IS NULL", column, val)) } case selection.Exists: - q = q.Where("tags ? ?", gorm.Expr("?"), r.Key()) + q = q.Where(fmt.Sprintf("%s ? ?", column), gorm.Expr("?"), r.Key()) case selection.GreaterThan: for val := range r.Values() { - q = q.Where(fmt.Sprintf("tags->>'%s' > ?", r.Key()), val) + q = q.Where(fmt.Sprintf("%s->>'%s' > ?", column, r.Key()), val) } case selection.LessThan: for val := range r.Values() { - q = q.Where(fmt.Sprintf("tags->>'%s' < ?", r.Key()), val) + q = q.Where(fmt.Sprintf("%s->>'%s' < ?", column, r.Key()), val) } } return q } -// labelSelectorRequirementToSQLClause to converts each selector requirement into a gorm SQL clause -func labelSelectorRequirementToSQLClause(q *gorm.DB, r labels.Requirement) *gorm.DB { - switch r.Operator() { - case selection.Equals, selection.DoubleEquals: - for val := range r.Values() { - q = q.Where("labels @> ?", types.JSONStringMap{r.Key(): val}) - } - case selection.NotEquals: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("labels->>'%s' != ?", r.Key()), lo.Ternary[any](val == "nil", nil, val)) - } - case selection.In: - q = q.Where(fmt.Sprintf("labels->>'%s' IN ?", r.Key()), collections.MapKeys(r.Values())) - case selection.NotIn: - q = q.Where(fmt.Sprintf("labels->>'%s' NOT IN ?", r.Key()), collections.MapKeys(r.Values())) - case selection.DoesNotExist: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("labels->>'%s' IS NULL", val)) - } - case selection.Exists: - q = q.Where("labels ? ?", gorm.Expr("?"), r.Key()) - case selection.GreaterThan: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("labels->>'%s' > ?", r.Key()), val) - } - case selection.LessThan: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("labels->>'%s' < ?", r.Key()), val) - } - } - - return q -} - -// fieldSelectorRequirementToSQLClause to converts each selector requirement into a gorm SQL clause -func fieldSelectorRequirementToSQLClause(q *gorm.DB, r labels.Requirement) *gorm.DB { - switch r.Operator() { - case selection.Equals, selection.DoubleEquals: - for val := range r.Values() { - if r.Key() == "external_id" { - q = q.Where(fmt.Sprintf("? = ANY(%s)", r.Key()), lo.Ternary[any](val == "nil", nil, val)) - } else { - q = q.Where(fmt.Sprintf("%s = ?", r.Key()), lo.Ternary[any](val == "nil", nil, val)) - } - } - case selection.NotEquals: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("%s <> ?", r.Key()), lo.Ternary[any](val == "nil", nil, val)) - } - case selection.In: - q = q.Where(fmt.Sprintf("%s IN ?", r.Key()), collections.MapKeys(r.Values())) - case selection.NotIn: - q = q.Where(fmt.Sprintf("%s NOT IN ?", r.Key()), collections.MapKeys(r.Values())) - case selection.GreaterThan: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("%s > ?", r.Key()), val) - } - case selection.LessThan: - for val := range r.Values() { - q = q.Where(fmt.Sprintf("%s < ?", r.Key()), val) - } - case selection.Exists, selection.DoesNotExist: - logger.Warnf("Operators %s is not supported for property lookup", r.Operator()) - } - - return q -} - // getScopeID takes either uuid or namespace/name and table to return the appropriate scope_id func getScopeID(ctx context.Context, scope string, table string, agentID *uuid.UUID) (string, error) { // If scope is a valid uuid, return as is From b02ee7227b2c596ea1f7806bd5499b0426594ef5 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Mon, 24 Feb 2025 13:54:31 +0530 Subject: [PATCH 2/2] chore: update tests --- tests/getters_test.go | 2 +- tests/query_resource_selector_test.go | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/tests/getters_test.go b/tests/getters_test.go index e97a89ab..c8d328ee 100644 --- a/tests/getters_test.go +++ b/tests/getters_test.go @@ -114,7 +114,7 @@ var _ = ginkgo.Describe("FindConfigs", func() { }, { Name: "field selector", - Selectors: []types.ResourceSelector{{FieldSelector: "config_class=Deployment"}}, + Selectors: []types.ResourceSelector{{Search: "config_class=Deployment"}}, Results: []uuid.UUID{dummy.LogisticsUIDeployment.ID, dummy.LogisticsAPIDeployment.ID}, }, } diff --git a/tests/query_resource_selector_test.go b/tests/query_resource_selector_test.go index 29b07a16..2d47a4e4 100644 --- a/tests/query_resource_selector_test.go +++ b/tests/query_resource_selector_test.go @@ -184,20 +184,6 @@ var _ = ginkgo.Describe("SearchResourceSelectors", func() { }, Configs: []models.ConfigItem{dummy.EC2InstanceA, dummy.EC2InstanceB}, }, - { - description: "field selector | IN Query", - query: query.SearchResourcesRequest{ - Configs: []types.ResourceSelector{{FieldSelector: "config_class in (Cluster)"}}, - }, - Configs: []models.ConfigItem{dummy.EKSCluster, dummy.KubernetesCluster}, - }, - { - description: "field selector | NOT IN Query", - query: query.SearchResourcesRequest{ - Configs: []types.ResourceSelector{{FieldSelector: "config_class notin (Node,Deployment,Database,Pod,Cluster)"}}, - }, - Configs: []models.ConfigItem{dummy.EC2InstanceA, dummy.EC2InstanceB}, - }, { description: "field selector | Tag selector Equals Query", query: query.SearchResourcesRequest{ @@ -302,7 +288,7 @@ var _ = ginkgo.Describe("Resoure Selector limits", ginkgo.Ordered, func() { ginkgo.It(fmt.Sprintf("should work with %d page size", limit), func() { items, err := query.SearchResources(DefaultContext, query.SearchResourcesRequest{ Limit: limit, - Configs: []types.ResourceSelector{{FieldSelector: fmt.Sprintf("config_class=%s", models.ConfigClassNode)}}, + Configs: []types.ResourceSelector{{Search: fmt.Sprintf("config_class=%s", models.ConfigClassNode)}}, }) Expect(err).To(BeNil()) @@ -344,7 +330,7 @@ var _ = ginkgo.Describe("Resoure Selector limits", ginkgo.Ordered, func() { ginkgo.It(fmt.Sprintf("should work with %d page size", pageSize), func() { items, err := query.SearchResources(DefaultContext, query.SearchResourcesRequest{ Limit: pageSize, - Configs: []types.ResourceSelector{{FieldSelector: fmt.Sprintf("config_class=%s", models.ConfigClassNode)}}, + Configs: []types.ResourceSelector{{Search: fmt.Sprintf("config_class=%s", models.ConfigClassNode)}}, Components: []types.ResourceSelector{{Types: []string{"Application"}}}, Checks: []types.ResourceSelector{{Types: []string{"http"}, Agent: "all"}}, })