Skip to content

Commit

Permalink
chore: use common function for field, label and tag selectors (#1337)
Browse files Browse the repository at this point in the history
* chore: use common function for field, label and tag selectors

* chore: update tests
  • Loading branch information
yashmehrotra authored Feb 24, 2025
1 parent 1ec3cce commit c60f0a8
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 100 deletions.
2 changes: 1 addition & 1 deletion query/config_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
95 changes: 13 additions & 82 deletions query/resource_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -208,7 +207,7 @@ func SetResourceSelectorClause(
}
requirements, _ := parsedTagSelector.Requirements()
for _, r := range requirements {
query = tagSelectorRequirementsToSQLClause(query, r)
query = jsonColumnRequirementsToSQLClause(query, "tags", r)
}
}
}
Expand All @@ -224,7 +223,7 @@ func SetResourceSelectorClause(
}
requirements, _ := parsedLabelSelector.Requirements()
for _, r := range requirements {
query = labelSelectorRequirementToSQLClause(query, r)
query = jsonColumnRequirementsToSQLClause(query, "labels", r)
}
}

Expand All @@ -236,7 +235,7 @@ func SetResourceSelectorClause(

requirements, _ := parsedFieldSelector.Requirements()
for _, r := range requirements {
query = fieldSelectorRequirementToSQLClause(query, r)
query = jsonColumnRequirementsToSQLClause(query, "properties", r)
}
}

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/getters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
},
}
Expand Down
18 changes: 2 additions & 16 deletions tests/query_resource_selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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"}},
})
Expand Down

0 comments on commit c60f0a8

Please sign in to comment.