Skip to content

Commit

Permalink
Merge branch 'main' into is-empty-rs-check
Browse files Browse the repository at this point in the history
  • Loading branch information
yashmehrotra authored Mar 14, 2024
2 parents d4d22a5 + ef65311 commit 8853087
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 39 deletions.
6 changes: 3 additions & 3 deletions query/config_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
const (
CatalogChangeRecursiveUpstream = "upstream"
CatalogChangeRecursiveDownstream = "downstream"
CatalogChangeRecursiveBoth = "both"
CatalogChangeRecursiveAll = "all"
)

var allowedConfigChangesSortColumns = []string{"catalog_name", "change_type", "summary", "source", "created_at"}
Expand Down Expand Up @@ -65,8 +65,8 @@ func (t *CatalogChangesSearchRequest) Validate() error {
return fmt.Errorf("catalog id is required")
}

if t.Recursive != "" && !lo.Contains([]string{CatalogChangeRecursiveUpstream, CatalogChangeRecursiveDownstream, CatalogChangeRecursiveBoth}, t.Recursive) {
return fmt.Errorf("recursive must be one of 'upstream', 'downstream' or 'both'")
if t.Recursive != "" && !lo.Contains([]string{CatalogChangeRecursiveUpstream, CatalogChangeRecursiveDownstream, CatalogChangeRecursiveAll}, t.Recursive) {
return fmt.Errorf("recursive must be one of 'upstream', 'downstream' or 'all'")
}

if t.From != "" {
Expand Down
2 changes: 1 addition & 1 deletion schema/openapi/playbook-spec.schema.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion schema/openapi/playbook.schema.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions tests/config_changes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ var _ = ginkgo.Describe("Config changes recursive", ginkgo.Ordered, func() {
ginkgo.Context("Both ways", func() {
ginkgo.It("should return changes upstream and downstream", func() {
var relatedChanges []models.ConfigChange
err := DefaultContext.DB().Raw("SELECT * FROM related_changes_recursive(?, 'both')", X.ID).Find(&relatedChanges).Error
err := DefaultContext.DB().Raw("SELECT * FROM related_changes_recursive(?, 'all')", X.ID).Find(&relatedChanges).Error
Expect(err).To(BeNil())

Expect(len(relatedChanges)).To(Equal(4))
Expand Down Expand Up @@ -199,7 +199,7 @@ var _ = ginkgo.Describe("Config changes recursive", ginkgo.Ordered, func() {
ginkgo.It("IN", func() {
response, err := query.FindCatalogChanges(DefaultContext, query.CatalogChangesSearchRequest{
CatalogID: X.ID,
Recursive: query.CatalogChangeRecursiveBoth,
Recursive: query.CatalogChangeRecursiveAll,
ChangeType: "diff",
})
Expect(err).To(BeNil())
Expand Down Expand Up @@ -290,10 +290,10 @@ var _ = ginkgo.Describe("Config changes recursive", ginkgo.Ordered, func() {
Expect(response.Summary["Pulled"]).To(Equal(1))
})

ginkgo.It(query.CatalogChangeRecursiveBoth, func() {
ginkgo.It(query.CatalogChangeRecursiveAll, func() {
response, err := query.FindCatalogChanges(DefaultContext, query.CatalogChangesSearchRequest{
CatalogID: V.ID,
Recursive: query.CatalogChangeRecursiveBoth,
Recursive: query.CatalogChangeRecursiveAll,
})
Expect(err).To(BeNil())
Expect(len(response.Changes)).To(Equal(5))
Expand Down
78 changes: 72 additions & 6 deletions tests/config_relationship_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
// /
// Z

// Graph #3 (multiple parent)
// L
// /|\
// M N O
// \|/
// p

// Create a list of ConfigItems
var (
A = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("A")}
Expand All @@ -43,14 +50,24 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
G = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("G")}
H = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("H")}

L = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("L")}
M = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("M")}
N = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("N")}
O = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("O")}
P = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("p")}

U = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("U")}
V = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("V")}
W = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("W")}
X = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("X")}
Y = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("Y")}
Z = models.ConfigItem{ID: uuid.New(), Namespace: lo.ToPtr("test-relationship"), Name: lo.ToPtr("Z")}
)
configItems := []models.ConfigItem{A, B, C, D, E, F, G, H, U, V, W, X, Y, Z}
configItems := []models.ConfigItem{
A, B, C, D, E, F, G, H,
L, M, N, O, P,
U, V, W, X, Y, Z,
}

// Create relationships between ConfigItems
relationships := []models.ConfigRelationship{
Expand All @@ -63,6 +80,13 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
{ConfigID: D.ID.String(), RelatedID: H.ID.String(), Relation: "test-relationship-DH"},
{ConfigID: H.ID.String(), RelatedID: A.ID.String(), Relation: "test-relationship-HA"},

{ConfigID: L.ID.String(), RelatedID: M.ID.String(), Relation: "test-relationship-LM"},
{ConfigID: L.ID.String(), RelatedID: N.ID.String(), Relation: "test-relationship-LN"},
{ConfigID: L.ID.String(), RelatedID: O.ID.String(), Relation: "test-relationship-LO"},
{ConfigID: M.ID.String(), RelatedID: P.ID.String(), Relation: "test-relationship-MP"},
{ConfigID: N.ID.String(), RelatedID: P.ID.String(), Relation: "test-relationship-NP"},
{ConfigID: O.ID.String(), RelatedID: P.ID.String(), Relation: "test-relationship-OP"},

{ConfigID: U.ID.String(), RelatedID: V.ID.String(), Relation: "test-relationship-UV"},
{ConfigID: U.ID.String(), RelatedID: W.ID.String(), Relation: "test-relationship-UW"},
{ConfigID: V.ID.String(), RelatedID: X.ID.String(), Relation: "test-relationship-VX"},
Expand Down Expand Up @@ -96,6 +120,37 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
Expect(err).To(BeNil())
})

ginkgo.Context("Multiple parent graph", func() {
ginkgo.It("should not return duplicate parents", func() {
var relatedConfigs []models.RelatedConfig
err := DefaultContext.DB().Raw("SELECT * FROM related_configs_recursive(?, 'incoming', false)", P.ID).Find(&relatedConfigs).Error
Expect(err).To(BeNil())

Expect(len(relatedConfigs)).To(Equal(4))
relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{L.ID, M.ID, N.ID, O.ID}))
})

ginkgo.It("should not return duplicate children", func() {
var relatedConfigs []models.RelatedConfig
err := DefaultContext.DB().Raw("SELECT * FROM related_configs_recursive(?, 'outgoing', false)", L.ID).Find(&relatedConfigs).Error
Expect(err).To(BeNil())

Expect(len(relatedConfigs)).To(Equal(4))
relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{P.ID, M.ID, N.ID, O.ID}))
})

ginkgo.It("recursive both ways", func() {
var relatedConfigs []models.RelatedConfig
err := DefaultContext.DB().Raw("SELECT * FROM related_configs_recursive(?, 'all')", G.ID).Find(&relatedConfigs).Error
Expect(err).To(BeNil())

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) string { return rc.Name })
Expect(relatedIDs).To(ConsistOf([]string{*D.Name, *B.Name, *H.Name, *A.Name}))
})
})

ginkgo.Context("Cyclic Graph", func() {
ginkgo.Context("Outgoing", func() {
ginkgo.It("should correctly return children in an acyclic path", func() {
Expand All @@ -121,7 +176,7 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
Expect(len(relatedConfigs)).To(Equal(7))

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(HaveExactElements([]uuid.UUID{B.ID, C.ID, D.ID, E.ID, F.ID, G.ID, H.ID}))
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{B.ID, C.ID, D.ID, E.ID, F.ID, G.ID, H.ID}))
})
})

Expand All @@ -133,7 +188,7 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()

Expect(len(relatedConfigs)).To(Equal(5))
relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(HaveExactElements([]uuid.UUID{C.ID, A.ID, H.ID, D.ID, B.ID}))
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{C.ID, A.ID, H.ID, D.ID, B.ID}))
})

ginkgo.It("should return parents of a non-leaf node in a cyclic path", func() {
Expand All @@ -142,7 +197,18 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
Expect(err).To(BeNil())

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(HaveExactElements([]uuid.UUID{D.ID, B.ID, A.ID, H.ID}))
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{D.ID, B.ID, A.ID, H.ID}))
})
})

ginkgo.Context("Both", func() {
ginkgo.It("should return parents of a leaf node in a cyclic path", func() {
var relatedConfigs []models.RelatedConfig
err := DefaultContext.DB().Raw("SELECT * FROM related_configs_recursive(?, 'all')", F.ID).Find(&relatedConfigs).Error
Expect(err).To(BeNil())

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) string { return rc.Name })
Expect(relatedIDs).To(ConsistOf([]string{*A.Name, *C.Name, *H.Name, *D.Name, *B.Name}))
})
})
})
Expand All @@ -156,7 +222,7 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
Expect(len(relatedConfigs)).To(Equal(5))

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(HaveExactElements([]uuid.UUID{V.ID, W.ID, X.ID, Y.ID, Z.ID}))
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{V.ID, W.ID, X.ID, Y.ID, Z.ID}))
})
})

Expand All @@ -175,7 +241,7 @@ var _ = ginkgo.Describe("Config relationship recursive", ginkgo.Ordered, func()
Expect(len(relatedConfigs)).To(Equal(3))

relatedIDs := lo.Map(relatedConfigs, func(rc models.RelatedConfig, _ int) uuid.UUID { return rc.ID })
Expect(relatedIDs).To(HaveExactElements([]uuid.UUID{X.ID, V.ID, U.ID}))
Expect(relatedIDs).To(ConsistOf([]uuid.UUID{X.ID, V.ID, U.ID}))
})
})
})
Expand Down
53 changes: 29 additions & 24 deletions views/006_config_views.sql
Original file line number Diff line number Diff line change
Expand Up @@ -539,27 +539,34 @@ CREATE OR REPLACE FUNCTION related_config_ids_recursive (
config_id UUID,
type_filter TEXT DEFAULT 'outgoing',
include_deleted_configs BOOLEAN DEFAULT FALSE
) RETURNS TABLE (relation TEXT, relation_type TEXT, id UUID) AS $$
) RETURNS TABLE (relation_type TEXT, id UUID) AS $$
BEGIN
RETURN query
WITH RECURSIVE all_related_configs AS (
SELECT
rci.relation,
type_filter as relation_type,
rci.id,
ARRAY[config_id] as visited
FROM related_config_ids(config_id, type_filter, include_deleted_configs) rci
IF type_filter = 'incoming' OR type_filter = 'outgoing' THEN
RETURN query
WITH RECURSIVE all_related_configs AS (
SELECT
rci.relation,
type_filter as relation_type,
rci.id,
ARRAY[config_id] as visited
FROM related_config_ids(config_id, type_filter, include_deleted_configs) rci
UNION
SELECT
rc.relation,
type_filter as relation_type,
rc.id,
arc.visited || ARRAY[arc.id, rc.id] as visited
FROM all_related_configs arc
INNER JOIN related_config_ids(arc.id, type_filter, include_deleted_configs) rc
ON rc.id != ALL(arc.visited)
)
SELECT DISTINCT result.relation_type, result.id FROM all_related_configs result;
ELSE
RETURN query
SELECT * FROM related_config_ids_recursive(config_id, 'outgoing', include_deleted_configs)
UNION
SELECT
rc.relation,
type_filter as relation_type,
rc.id,
arc.visited || ARRAY[arc.id, rc.id] as visited
FROM all_related_configs arc
INNER JOIN related_config_ids(arc.id, type_filter, include_deleted_configs) rc
ON rc.id != ALL(arc.visited)
)
SELECT result.relation, result.relation_type, result.id FROM all_related_configs result;
SELECT * FROM related_config_ids_recursive(config_id, 'incoming', include_deleted_configs);
END IF;
END;
$$ LANGUAGE plpgsql;

Expand All @@ -571,7 +578,6 @@ CREATE FUNCTION related_configs_recursive (
type_filter TEXT DEFAULT 'outgoing',
include_deleted_configs BOOLEAN DEFAULT FALSE
) RETURNS TABLE (
relation TEXT,
relation_type TEXT,
id uuid,
name TEXT,
Expand All @@ -590,7 +596,6 @@ CREATE FUNCTION related_configs_recursive (
BEGIN
RETURN query
SELECT
r.relation,
r.relation_type,
configs.id,
configs.name,
Expand All @@ -615,14 +620,14 @@ DROP FUNCTION IF EXISTS related_changes_recursive(UUID, TEXT, BOOLEAN);

CREATE FUNCTION related_changes_recursive (
config_id UUID,
type_filter TEXT DEFAULT 'downstream', -- 'downstream', 'upstream', or 'both'
type_filter TEXT DEFAULT 'downstream', -- 'downstream', 'upstream', or 'all'
include_deleted_configs BOOLEAN DEFAULT FALSE
) RETURNS SETOF config_changes AS $$
BEGIN
RETURN query
SELECT * FROM config_changes cc WHERE
cc.config_id = related_changes_recursive.config_id
OR (($2 = 'downstream' OR $2 = 'both') AND cc.config_id IN (SELECT id FROM related_config_ids_recursive($1, 'outgoing', $3)))
OR (($2 = 'upstream' OR $2 = 'both') AND cc.config_id IN (SELECT id FROM related_config_ids_recursive($1, 'incoming', $3)));
OR (($2 = 'downstream' OR $2 = 'all') AND cc.config_id IN (SELECT id FROM related_config_ids_recursive($1, 'outgoing', $3)))
OR (($2 = 'upstream' OR $2 = 'all') AND cc.config_id IN (SELECT id FROM related_config_ids_recursive($1, 'incoming', $3)));
END;
$$ LANGUAGE plpgsql;

0 comments on commit 8853087

Please sign in to comment.