Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create MapSet[T] and ImmutableMapSet[T] types and change Entity.Parents to be a ImmutableMapSet[EntityUID] #40

Merged
merged 17 commits into from
Sep 26, 2024

Conversation

patjakdev
Copy link
Collaborator

@patjakdev patjakdev commented Sep 20, 2024

Issue #, if available: None

Description of changes:

This PR adds a generic MapSet[T] and ImmutableMapSet[T] for all the places we want the semantics of a set but are annoyed by the inconvenience of using the map[T]struct{} idiom in Go.

In particular, the parents of an Entity have set semantics. With the current slice-based implementation, time complexity of the evaluation of in is linear. Utilizing a ImmutableMapSet[EntityUID] instead gives us constant time complexity on average, which is better. That being said, for small sets, that constant time is actually slightly greater than the time it takes to perform a linear search. I will consider addressing that in a future PR, but for now I'm happy to just have set semantics for Entity parents.

The ImmutableMapSet[EntityUID] is projected publicly through the types package as EntityUIDSet, with appropriate constructors also projected. For now, at least, I thought it prudent not to allow external users to import and use MapSet[T] directly.

I've also replaced any existing internal usages of map[T]struct{} in the module with MapSet[T] or ImmutableMapSet[T] as appropriate.

Important notes:

  1. MapSet[T] has no ordering guarantees. This applies to MarshalJSON as well, meaning there's no predictable JSON encoding for a given Set. Instead of using comparable, we could invent an Item interface that embeds comparable and also has a Less() method, but then this set couldn't easily be used for native types like string, which feels like a pretty lame drawback.
  2. While MapSet is implemented in internal and therefore can't be directly imported by external users, the Entity.Parents is now of type MapSet[EntityUID] and therefore changes to the MapSet[T] interface should be considered breaking changes. I'm open to feedback on a better way to structure this. I considered putting MapSet in types, but thought that might be confusing...especially because I have some desire to extract an ImmutableHashSet out of types.Set in the future.

@patjakdev patjakdev marked this pull request as ready for review September 20, 2024 20:32
@patjakdev patjakdev requested review from philhassey and apg September 20, 2024 20:32
@patjakdev patjakdev force-pushed the entity-parents-as-set branch from 3740e46 to 88889b6 Compare September 20, 2024 20:33
@@ -13,13 +13,6 @@ import (
// the Entity (it must be the same as the UID within the Entity itself.)
type Entities map[EntityUID]*Entity

// An Entity defines the parents and attributes for an EntityUID.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted this because I always have trouble remembering where the heck this struct is defined.

@patjakdev patjakdev marked this pull request as draft September 20, 2024 20:34
@patjakdev patjakdev force-pushed the entity-parents-as-set branch from 88889b6 to 68fb91c Compare September 20, 2024 21:02
@@ -1,68 +1,68 @@
package cedar
package cedar_test
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good hygiene. None of the tests in this file need to interact with any private bits in the package.

UID: cuzco,
Parents: []EntityUID{types.NewEntityUID("parent", "bob")},
Parents: cedar.NewEntityUIDSetFromSlice([]cedar.EntityUID{cedar.NewEntityUID("parent", "bob")}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example of the breaking change.

I'll admit that it's a bit of a mouthful...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe receive a variadic, to cut out the []cedar.EntityUID{ ? Though TBH, may not matter too much since it's going to be usually handled via JSON marshalling.

Maybe the words FromSlice aren't really needed.

UID: cuzco,
Parents: []EntityUID{types.NewEntityUID("team", "osiris")},
Parents: cedar.NewEntityUIDSetFromSlice([]cedar.EntityUID{cedar.NewEntityUID("team", "osiris")}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another example.

UID: dropTable,
Parents: []EntityUID{types.NewEntityUID("scary", "stuff")},
Parents: cedar.NewEntityUIDSetFromSlice([]cedar.EntityUID{cedar.NewEntityUID("scary", "stuff")}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple more examples here and on line 212.

Entities: cedar.Entities{
cedar.NewEntityUID("Resource", "table"): &cedar.Entity{
UID: cedar.NewEntityUID("Resource", "table"),
Parents: cedar.NewEntityUIDSetFromSlice([]cedar.EntityUID{cedar.NewEntityUID("Parent", "id")}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last example in this file

@patjakdev patjakdev marked this pull request as ready for review September 20, 2024 21:13
@patjakdev patjakdev force-pushed the entity-parents-as-set branch 2 times, most recently from 88d6d96 to 4cf812d Compare September 23, 2024 17:43
…nvenience functions around a map[T]struct{}

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
…g]struct{} for annotation names

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
…} for record keys

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
…} for detecting unbound or unused variables

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
@patjakdev patjakdev force-pushed the entity-parents-as-set branch from 4cf812d to 32380ba Compare September 23, 2024 21:54
internal/sets/mapset.go Outdated Show resolved Hide resolved
internal/sets/mapset.go Outdated Show resolved Hide resolved
internal/sets/mapset.go Outdated Show resolved Hide resolved
internal/sets/mapset.go Outdated Show resolved Hide resolved
internal/sets/mapset.go Outdated Show resolved Hide resolved
internal/sets/mapset.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@philhassey philhassey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of little nitpicks here, glad to discuss. Great improvement to a lot of stuff!

…ropriately, and return pointers to underscore risk of copy by value

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
…oolean Intersects

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
@patjakdev patjakdev force-pushed the entity-parents-as-set branch from 3db1c9a to c0c3981 Compare September 24, 2024 22:58
@patjakdev patjakdev changed the title Create a MapSet[T] type and change Entity.Parents to be a MapSet[EntityUID] Create MapSet[T] and ImmutableMapSet[T] types and change Entity.Parents to be a ImmutableMapSet[EntityUID] Sep 25, 2024
@philhassey
Copy link
Collaborator

Set has no ordering. This applies to MarshalJSON as well, meaning there's no predictable JSON encoding for a given Set. Instead of using comparable, we could invent an Item interface that embeds comparable and also has a Less() method, but then this set couldn't easily be used for native types like string, which feels like a pretty lame drawback.

I think having a consistent JSON marshaller is important because having things change randomly is really annoying and can cause unnecessary updates happen when a diff is detected by something somewhere.

In the case of JSON marshaling, I don't think performance is the key metric. So I think what you could do is get a Marshal of the bytes of all the values. Then sort those slices of bytes, then emit the "[a,b,c]" in that order.

While MapSet is implemented in internal and therefore can't be directly imported by external users, the Entity.Parents is now of type MapSet[EntityUID] and therefore changes to the MapSet[T] interface should be considered breaking changes. I'm open to feedback on a better way to structure this. I considered putting MapSet in types, but thought that might be confusing...especially because I have some desire to extract an ImmutableHashSet out of types.Set in the future.

I thought the Entity.Parents was going to be immutable?

I agree that having type X internal.Something isn't great, but I think we can defer fixing that for a while. We have similar problems in our public ast package.

}
for _, k := range fe.Parents {
fe.Parents.Iterate(func(k types.EntityUID) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but as a point of interest, I wonder if using a closure causes an allocation.

policy.go Outdated Show resolved Hide resolved
types/set.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@philhassey philhassey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a few minor tweaks would be nice.

Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
Signed-off-by: Patrick Jakubowski <patrick.jakubowski@strongdm.com>
@patjakdev patjakdev force-pushed the entity-parents-as-set branch from 038608e to e796ce2 Compare September 26, 2024 18:25
@patjakdev patjakdev merged commit 609f098 into cedar-policy:main Sep 26, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants