diff --git a/cache/cache.go b/cache/cache.go index 596751b..691c7ce 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,8 +1,9 @@ package cache import ( - "sync" + "sync/atomic" + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" "github.com/aserto-dev/azm/model/diff" stts "github.com/aserto-dev/azm/stats" @@ -17,47 +18,39 @@ type ( ) type Cache struct { - model *model.Model - mtx sync.RWMutex + model atomic.Pointer[model.Model] + relsPool *mempool.RelationsPool } // New, create new model cache instance. func New(m *model.Model) *Cache { - return &Cache{ - model: m, - mtx: sync.RWMutex{}, + cache := &Cache{ + relsPool: mempool.NewRelationsPool(), } + + cache.model.Store(m) + return cache } // UpdateModel, swaps the cache model instance. func (c *Cache) UpdateModel(m *model.Model) error { - c.mtx.Lock() - defer c.mtx.Unlock() - c.model = m + c.model.Store(m) return nil } func (c *Cache) CanUpdate(other *model.Model, stats *stts.Stats) error { - c.mtx.Lock() - defer c.mtx.Unlock() - return diff.CanUpdateModel(c.model, other, stats) + return diff.CanUpdateModel(c.model.Load(), other, stats) } // ObjectExists, checks if given object type name exists in the model cache. func (c *Cache) ObjectExists(on ObjectName) bool { - c.mtx.RLock() - defer c.mtx.RUnlock() - - _, ok := c.model.Objects[on] + _, ok := c.model.Load().Objects[on] return ok } // RelationExists, checks if given relation type, for the given object type, exists in the model cache. func (c *Cache) RelationExists(on ObjectName, rn RelationName) bool { - c.mtx.RLock() - defer c.mtx.RUnlock() - - if obj, ok := c.model.Objects[on]; ok { + if obj, ok := c.model.Load().Objects[on]; ok { _, ok := obj.Relations[rn] return ok } @@ -66,10 +59,7 @@ func (c *Cache) RelationExists(on ObjectName, rn RelationName) bool { // PermissionExists, checks if given permission, for the given object type, exists in the model cache. func (c *Cache) PermissionExists(on ObjectName, pn RelationName) bool { - c.mtx.RLock() - defer c.mtx.RUnlock() - - if obj, ok := c.model.Objects[on]; ok { + if obj, ok := c.model.Load().Objects[on]; ok { _, ok := obj.Permissions[pn] return ok } @@ -77,16 +67,11 @@ func (c *Cache) PermissionExists(on ObjectName, pn RelationName) bool { } func (c *Cache) Metadata() *model.Metadata { - c.mtx.RLock() - defer c.mtx.RUnlock() - return c.model.Metadata + return c.model.Load().Metadata } -func (c *Cache) ValidateRelation(relation *dsc.Relation) error { - c.mtx.RLock() - defer c.mtx.RUnlock() - - return c.model.ValidateRelation( +func (c *Cache) ValidateRelation(relation *dsc.RelationIdentifier) error { + return c.model.Load().ValidateRelation( ObjectName(relation.ObjectType), model.ObjectID(relation.ObjectId), RelationName(relation.Relation), @@ -123,7 +108,7 @@ func (c *Cache) AssignableRelations(on, sn ObjectName, sr ...RelationName) ([]Re } } - matches := lo.PickBy(c.model.Objects[on].Relations, func(rn RelationName, r *model.Relation) bool { + matches := lo.PickBy(c.model.Load().Objects[on].Relations, func(rn RelationName, r *model.Relation) bool { for _, ref := range r.Union { if ref.Object != sn { // type mismatch diff --git a/cache/check.go b/cache/check.go index cb800fe..f2d7e85 100644 --- a/cache/check.go +++ b/cache/check.go @@ -2,14 +2,19 @@ package cache import ( "github.com/aserto-dev/azm/graph" + "github.com/aserto-dev/azm/mempool" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" "github.com/aserto-dev/go-directory/pkg/pb" "github.com/aserto-dev/go-directory/pkg/prop" "google.golang.org/protobuf/types/known/structpb" ) +// If true, use a shared memory pool for all requests. +// Othersise, each call gets its own pool. +const sharedPool = true + func (c *Cache) Check(req *dsr.CheckRequest, relReader graph.RelationReader) (*dsr.CheckResponse, error) { - checker := graph.NewCheck(c.model, req, relReader) + checker := graph.NewCheck(c.model.Load(), req, relReader, c.relationsPool()) ctx := pb.NewStruct() @@ -32,9 +37,9 @@ func (c *Cache) GetGraph(req *dsr.GetGraphRequest, relReader graph.RelationReade ) if req.ObjectId == "" { - search, err = graph.NewObjectSearch(c.model, req, relReader) + search, err = graph.NewObjectSearch(c.model.Load(), req, relReader, c.relationsPool()) } else { - search, err = graph.NewSubjectSearch(c.model, req, relReader) + search, err = graph.NewSubjectSearch(c.model.Load(), req, relReader, c.relationsPool()) } if err != nil { @@ -43,3 +48,11 @@ func (c *Cache) GetGraph(req *dsr.GetGraphRequest, relReader graph.RelationReade return search.Search() } + +func (c *Cache) relationsPool() *mempool.RelationsPool { + if sharedPool { + return c.relsPool + } + + return mempool.NewRelationsPool() +} diff --git a/go.mod b/go.mod index 2fef087..6a7980a 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,16 @@ module github.com/aserto-dev/azm -go 1.22.9 +go 1.22.10 -toolchain go1.23.3 +toolchain go1.23.4 + +// replace github.com/aserto-dev/go-directory => ../go-directory require ( github.com/antlr4-go/antlr/v4 v4.13.1 github.com/aserto-dev/errors v0.0.11 - github.com/aserto-dev/go-directory v0.33.1 - github.com/deckarep/golang-set/v2 v2.6.0 + github.com/aserto-dev/go-directory v0.33.2 + github.com/deckarep/golang-set/v2 v2.7.0 github.com/hashicorp/go-multierror v1.1.1 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 @@ -16,23 +18,24 @@ require ( github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.47.0 github.com/stretchr/testify v1.10.0 - google.golang.org/grpc v1.68.0 + google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v3 v3.0.1 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20240920164238-5a7b106cbb87.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect ) diff --git a/go.sum b/go.sum index cafda7f..27253bb 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,16 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20240920164238-5a7b106cbb87.1 h1:7QIeAuTdLp173vC/9JojRMDFcpmqtoYrxPmvdHAOynw= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20240920164238-5a7b106cbb87.1/go.mod h1:mnHCFccv4HwuIAOHNGdiIc5ZYbBCvbTWZcodLN5wITI= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 h1:jLd96rDDNJ+zIJxvV/L855VEtrjR0G4aePVDlCpf6kw= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1/go.mod h1:mnHCFccv4HwuIAOHNGdiIc5ZYbBCvbTWZcodLN5wITI= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/aserto-dev/errors v0.0.11 h1:CXo+Uwmh09doG2HvL1SC8Fnne8f9VPrGyEQPtogAfyY= github.com/aserto-dev/errors v0.0.11/go.mod h1:T1YQOtcxpgBriPTn5HXJkD/QukYz5YojYOIzGMo0ybM= -github.com/aserto-dev/go-directory v0.33.1-0.20241125175915-5cffa131b985 h1:Pjpoa4n8Kx1KBaQ8GyW9Y9bVnbzTwWkx7gDhL5huhmI= -github.com/aserto-dev/go-directory v0.33.1-0.20241125175915-5cffa131b985/go.mod h1:eEvIFY+t6WclhJPHbu+Y17ghF4qRYc36UkJL5UNPVPY= -github.com/aserto-dev/go-directory v0.33.1 h1:jLyzMRu5Omw5KJ2cmqnGZZbrF091HFHnt9awhqZhSq8= -github.com/aserto-dev/go-directory v0.33.1/go.mod h1:0Ng9lPTahslSN/ONoWCceG6Q1ukpSfxYEu589eCReRM= +github.com/aserto-dev/go-directory v0.33.2 h1:QJwzSmfxJ7EG0RzWsgu7In5cAeGtZURZklSsHhMOFh8= +github.com/aserto-dev/go-directory v0.33.2/go.mod h1:gK239V0htJtp0/BwvbTrYv/XIphoK/AugP8sw3m8B0s= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= -github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k= +github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -41,6 +39,8 @@ github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8X github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -52,23 +52,23 @@ github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/graph/check.go b/graph/check.go index 7ca3f85..c943d69 100644 --- a/graph/check.go +++ b/graph/check.go @@ -1,6 +1,7 @@ package graph import ( + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" @@ -15,9 +16,10 @@ type Checker struct { getRels RelationReader memo *checkMemo + pool *mempool.RelationsPool } -func NewCheck(m *model.Model, req *dsr.CheckRequest, reader RelationReader) *Checker { +func NewCheck(m *model.Model, req *dsr.CheckRequest, reader RelationReader, pool *mempool.RelationsPool) *Checker { return &Checker{ m: m, params: &relation{ @@ -29,6 +31,7 @@ func NewCheck(m *model.Model, req *dsr.CheckRequest, reader RelationReader) *Che }, getRels: reader, memo: newCheckMemo(req.Trace), + pool: pool, } } @@ -88,8 +91,14 @@ func (c *Checker) checkRelation(params *relation) (checkStatus, error) { r := c.m.Objects[params.ot].Relations[params.rel] steps := c.m.StepRelation(r, params.st) + // Reuse the same slice in all steps. + relsPtr := c.pool.GetSlice() + defer c.pool.PutSlice(relsPtr) + for _, step := range steps { - req := &dsc.Relation{ + *relsPtr = (*relsPtr)[:0] + + req := &dsc.RelationIdentifier{ ObjectType: params.ot.String(), ObjectId: params.oid.String(), Relation: params.rel.String(), @@ -97,33 +106,34 @@ func (c *Checker) checkRelation(params *relation) (checkStatus, error) { } switch { + case step.IsDirect(): + req.SubjectId = params.sid.String() case step.IsWildcard(): req.SubjectId = "*" case step.IsSubject(): req.SubjectRelation = step.Relation.String() } - rels, err := c.getRels(req) - if err != nil { + if err := c.getRels(req, c.pool, relsPtr); err != nil { return checkStatusFalse, err } switch { case step.IsDirect(): - for _, rel := range rels { + for _, rel := range *relsPtr { if rel.SubjectId == params.sid.String() { return checkStatusTrue, nil } } case step.IsWildcard(): - if len(rels) > 0 { + if len(*relsPtr) > 0 { // We have a wildcard match. return checkStatusTrue, nil } case step.IsSubject(): - for _, rel := range rels { + for _, rel := range *relsPtr { if status, err := c.check(&relation{ ot: step.Object, oid: ObjectID(rel.SubjectId), @@ -190,17 +200,21 @@ func (c *Checker) checkPermission(params *relation) (checkStatus, error) { func (c *Checker) expandTerm(pt *model.PermissionTerm, params *relation) (relations, error) { if pt.IsArrow() { - // Resolve the base of the arrow. - rels, err := c.getRels(&dsc.Relation{ + query := &dsc.RelationIdentifier{ ObjectType: params.ot.String(), ObjectId: params.oid.String(), Relation: pt.Base.String(), - }) + } + + relsPtr := c.pool.GetSlice() + + // Resolve the base of the arrow. + err := c.getRels(query, c.pool, relsPtr) if err != nil { return relations{}, err } - expanded := lo.Map(rels, func(rel *dsc.Relation, _ int) *relation { + expanded := lo.Map(*relsPtr, func(rel *dsc.RelationIdentifier, _ int) *relation { return &relation{ ot: model.ObjectName(rel.SubjectType), oid: ObjectID(rel.SubjectId), @@ -210,6 +224,8 @@ func (c *Checker) expandTerm(pt *model.PermissionTerm, params *relation) (relati } }) + c.pool.PutSlice(relsPtr) + return expanded, nil } diff --git a/graph/check_test.go b/graph/check_test.go index abe0f4d..115fa03 100644 --- a/graph/check_test.go +++ b/graph/check_test.go @@ -5,73 +5,77 @@ import ( "testing" azmgraph "github.com/aserto-dev/azm/graph" + "github.com/aserto-dev/azm/mempool" v3 "github.com/aserto-dev/azm/v3" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) -func TestCheck(t *testing.T) { - tests := []struct { - check string - expected bool - }{ - // Relations - {"doc:doc1#owner@user:user1", false}, - {"doc:doc1#viewer@user:user1", true}, - {"doc:doc2#viewer@user:user1", true}, - {"doc:doc2#viewer@user:userX", true}, - {"doc:doc1#viewer@user:user2", true}, - {"doc:doc1#viewer@user:user3", true}, - {"doc:doc1#viewer@group:d1_viewers", false}, - - {"group:yin#member@user:yin_user", true}, - {"group:yin#member@user:yang_user", true}, - {"group:yang#member@user:yin_user", true}, - {"group:yang#member@user:yang_user", true}, - - {"group:alpha#member@user:user1", false}, - - // Permissions - {"doc:doc1#can_change_owner@user:d1_owner", true}, - {"doc:doc1#can_change_owner@user:user1", false}, - {"doc:doc1#can_change_owner@user:userX", false}, - - {"doc:doc1#can_read@user:d1_owner", true}, - {"doc:doc1#can_read@user:f1_owner", true}, - {"doc:doc1#can_read@user:user1", true}, - {"doc:doc1#can_read@user:f1_viewer", true}, - {"doc:doc1#can_read@user:userX", false}, - - {"doc:doc1#can_write@user:d1_owner", true}, - {"doc:doc1#can_write@user:f1_owner", true}, - {"doc:doc1#can_write@user:user2", false}, - - {"folder:folder1#owner@user:f1_owner", true}, - {"folder:folder1#can_create_file@user:f1_owner", true}, - {"folder:folder1#can_share@user:f1_owner", true}, - - // intersection - {"doc:doc1#can_share@user:d1_owner", false}, - {"doc:doc1#can_share@user:f1_owner", true}, - - // negation - {"folder:folder1#can_read@user:f1_owner", true}, - {"doc:doc1#viewer@user:f1_owner", false}, - {"doc:doc1#can_invite@user:f1_owner", true}, - - // cycles - {"cycle:loop#can_delete@user:loop_owner", true}, - {"cycle:loop#can_delete@user:user1", false}, - } +var tests = []struct { + check string + expected bool +}{ + // Relations + {"doc:doc1#owner@user:user1", false}, + {"doc:doc1#viewer@user:user1", true}, + {"doc:doc2#viewer@user:user1", true}, + {"doc:doc2#viewer@user:userX", true}, + {"doc:doc1#viewer@user:user2", true}, + {"doc:doc1#viewer@user:user3", true}, + {"doc:doc1#viewer@group:d1_viewers", false}, + + {"group:yin#member@user:yin_user", true}, + {"group:yin#member@user:yang_user", true}, + {"group:yang#member@user:yin_user", true}, + {"group:yang#member@user:yang_user", true}, + + {"group:alpha#member@user:user1", false}, + + // Permissions + {"doc:doc1#can_change_owner@user:d1_owner", true}, + {"doc:doc1#can_change_owner@user:user1", false}, + {"doc:doc1#can_change_owner@user:userX", false}, + + {"doc:doc1#can_read@user:d1_owner", true}, + {"doc:doc1#can_read@user:f1_owner", true}, + {"doc:doc1#can_read@user:user1", true}, + {"doc:doc1#can_read@user:f1_viewer", true}, + {"doc:doc1#can_read@user:userX", false}, + + {"doc:doc1#can_write@user:d1_owner", true}, + {"doc:doc1#can_write@user:f1_owner", true}, + {"doc:doc1#can_write@user:user2", false}, + + {"folder:folder1#owner@user:f1_owner", true}, + {"folder:folder1#can_create_file@user:f1_owner", true}, + {"folder:folder1#can_share@user:f1_owner", true}, + + // intersection + {"doc:doc1#can_share@user:d1_owner", false}, + {"doc:doc1#can_share@user:f1_owner", true}, + + // negation + {"folder:folder1#can_read@user:f1_owner", true}, + {"doc:doc1#viewer@user:f1_owner", false}, + {"doc:doc1#can_invite@user:f1_owner", true}, + + // cycles + {"cycle:loop#can_delete@user:loop_owner", true}, + {"cycle:loop#can_delete@user:user1", false}, +} +func TestCheck(t *testing.T) { m, err := v3.LoadFile("./check_test.yaml") assert.NoError(t, err) assert.NotNil(t, m) + pool := mempool.NewRelationsPool() + for _, test := range tests { t.Run(test.check, func(tt *testing.T) { assert := assert.New(tt) - checker := azmgraph.NewCheck(m, checkReq(test.check), rels.GetRelations) + checker := azmgraph.NewCheck(m, checkReq(test.check, true), rels.GetRelations, pool) res, err := checker.Check() assert.NoError(err) @@ -79,6 +83,30 @@ func TestCheck(t *testing.T) { assert.Equal(test.expected, res) }) } +} + +func BenchmarkCheck(b *testing.B) { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + m, err := v3.LoadFile("./check_test.yaml") + if err != nil { + b.Fatalf("failed to load model: %s", err) + } + + pool := mempool.NewRelationsPool() + + b.ResetTimer() + for _, test := range tests { + assert := assert.New(b) + + b.StopTimer() + checker := azmgraph.NewCheck(m, checkReq(test.check, false), rels.GetRelations, pool) + b.StartTimer() + + res, err := checker.Check() + assert.NoError(err) + assert.Equal(test.expected, res) + } } diff --git a/graph/objects.go b/graph/objects.go index 8da33e1..d90f004 100644 --- a/graph/objects.go +++ b/graph/objects.go @@ -3,6 +3,7 @@ package graph import ( "strings" + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" @@ -16,7 +17,7 @@ type ObjectSearch struct { wildcardSearch *SubjectSearch } -func NewObjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationReader) (*ObjectSearch, error) { +func NewObjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationReader, pool *mempool.RelationsPool) (*ObjectSearch, error) { params := searchParams(req) if err := validate(m, params); err != nil { return nil, err @@ -40,6 +41,7 @@ func NewObjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationRe getRels: invertedRelationReader(im, reader), memo: newSearchMemo(req.Trace), explain: req.Explain, + pool: pool, }}, wildcardSearch: &SubjectSearch{graphSearch{ m: im, @@ -47,6 +49,7 @@ func NewObjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationRe getRels: invertedRelationReader(im, reader), memo: newSearchMemo(req.Trace), explain: req.Explain, + pool: pool, }}, }, nil } @@ -125,22 +128,24 @@ func wildcardParams(params *relation) *relation { } func invertedRelationReader(m *model.Model, reader RelationReader) RelationReader { - return func(r *dsc.Relation) ([]*dsc.Relation, error) { + return func(r *dsc.RelationIdentifier, relPool MessagePool[*dsc.RelationIdentifier], out *Relations) error { ir := uninvertRelation(m, relationFromProto(r)) - res, err := reader(ir.asProto()) - if err != nil { - return nil, err + if err := reader(ir.asProto(), relPool, out); err != nil { + return err } - return lo.Map(res, func(r *dsc.Relation, _ int) *dsc.Relation { - return &dsc.Relation{ + res := *out + for i, r := range res { + res[i] = &dsc.RelationIdentifier{ ObjectType: r.SubjectType, ObjectId: r.SubjectId, Relation: r.Relation, SubjectType: r.ObjectType, SubjectId: r.ObjectId, } - }), nil + } + + return nil } } diff --git a/graph/objects_test.go b/graph/objects_test.go index 3d9bf0e..989e10d 100644 --- a/graph/objects_test.go +++ b/graph/objects_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/aserto-dev/azm/graph" + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" v3 "github.com/aserto-dev/azm/v3" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" @@ -35,11 +36,13 @@ func TestSearchObjects(t *testing.T) { im.Validate(model.SkipNameValidation, model.AllowPermissionInArrowBase), ) + pool := mempool.NewRelationsPool() + for _, test := range searchObjectsTests { t.Run(test.search, func(tt *testing.T) { assert := assert.New(tt) - objSearch, err := graph.NewObjectSearch(m, graphReq(test.search), rels.GetRelations) + objSearch, err := graph.NewObjectSearch(m, graphReq(test.search), rels.GetRelations, pool) assert.NoError(err) res, err := objSearch.Search() diff --git a/graph/relation.go b/graph/relation.go index bf05d67..e3def96 100644 --- a/graph/relation.go +++ b/graph/relation.go @@ -19,8 +19,8 @@ type relation struct { type relations []*relation -// converts a dsc.Relation to a relation. -func relationFromProto(rel *dsc.Relation) *relation { +// converts a dsc.RelationIdentifier to a relation. +func relationFromProto(rel *dsc.RelationIdentifier) *relation { return &relation{ ot: model.ObjectName(rel.ObjectType), oid: ObjectID(rel.ObjectId), @@ -31,8 +31,8 @@ func relationFromProto(rel *dsc.Relation) *relation { } } -func (r *relation) asProto() *dsc.Relation { - return &dsc.Relation{ +func (r *relation) asProto() *dsc.RelationIdentifier { + return &dsc.RelationIdentifier{ ObjectType: string(r.ot), ObjectId: string(r.oid), Relation: string(r.rel), diff --git a/graph/search.go b/graph/search.go index 49cb3a8..319e8e6 100644 --- a/graph/search.go +++ b/graph/search.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" @@ -12,21 +13,31 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -type ObjectID = model.ObjectID +type ( + ObjectID = model.ObjectID + Relations = []*dsc.RelationIdentifier -// RelationReader retrieves relations that match the given filter. -type RelationReader func(*dsc.Relation) ([]*dsc.Relation, error) + searchPath relations + + object struct { + Type model.ObjectName + ID ObjectID + } -type searchPath relations + // The results of a search is a map where the key is a matching relations + // and the value is a list of paths that connect the search object and subject. + searchResults map[object][]searchPath +) -type object struct { - Type model.ObjectName - ID ObjectID +type MessagePool[T any] interface { + Get() T + Put(T) } -// The results of a search is a map where the key is a matching relations -// and the value is a list of paths that connect the search object and subject. -type searchResults map[object][]searchPath +type RelationPool = MessagePool[*dsc.RelationIdentifier] + +// RelationReader retrieves relations that match the given filter. +type RelationReader func(*dsc.RelationIdentifier, RelationPool, *Relations) error // Objects returns the objects from the search results. func (r searchResults) Objects() []*dsc.ObjectIdentifier { @@ -92,6 +103,7 @@ type graphSearch struct { memo *searchMemo explain bool + pool *mempool.RelationsPool } func validate(m *model.Model, params *relation) error { @@ -120,7 +132,6 @@ func searchParams(req *dsr.GetGraphRequest) *relation { sid: ObjectID(req.SubjectId), srel: model.RelationName(req.SubjectRelation), } - } type searchCall struct { @@ -157,7 +168,6 @@ func (m *searchMemo) MarkVisited(params *relation) searchStatus { func (m *searchMemo) MarkComplete(params *relation, results searchResults) { m.visited[*params] = results m.trace(params, searchStatusComplete) - } func (m *searchMemo) Status(params *relation) searchStatus { diff --git a/graph/subjects.go b/graph/subjects.go index 71d02cc..cd2c0dd 100644 --- a/graph/subjects.go +++ b/graph/subjects.go @@ -1,6 +1,7 @@ package graph import ( + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" @@ -12,7 +13,12 @@ type SubjectSearch struct { graphSearch } -func NewSubjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationReader) (*SubjectSearch, error) { +func NewSubjectSearch( + m *model.Model, + req *dsr.GetGraphRequest, + reader RelationReader, + pool *mempool.RelationsPool, +) (*SubjectSearch, error) { params := searchParams(req) if err := validate(m, params); err != nil { return nil, err @@ -24,6 +30,7 @@ func NewSubjectSearch(m *model.Model, req *dsr.GetGraphRequest, reader RelationR getRels: reader, memo: newSearchMemo(req.Trace), explain: req.Explain, + pool: pool, }}, nil } @@ -108,7 +115,7 @@ func (s *SubjectSearch) findNeighbor(step *model.RelationRef, params *relation) sid = "*" } - req := &dsc.Relation{ + req := &dsc.RelationIdentifier{ ObjectType: params.ot.String(), ObjectId: params.oid.String(), Relation: params.rel.String(), @@ -118,12 +125,12 @@ func (s *SubjectSearch) findNeighbor(step *model.RelationRef, params *relation) results := searchResults{} - rels, err := s.getRels(req) - if err != nil { + relsPtr := s.pool.GetSlice() + if err := s.getRels(req, s.pool, relsPtr); err != nil { return results, err } - for _, rel := range rels { + for _, rel := range *relsPtr { if rel.SubjectId != "*" && params.oid != ObjectID(rel.ObjectId) { continue } @@ -146,25 +153,29 @@ func (s *SubjectSearch) findNeighbor(step *model.RelationRef, params *relation) results[*subj] = path } + s.pool.PutSlice(relsPtr) + return results, nil } func (s *SubjectSearch) searchSubjectRelation(step *model.RelationRef, params *relation) (searchResults, error) { results := searchResults{} - req := &dsc.Relation{ + req := &dsc.RelationIdentifier{ ObjectType: params.ot.String(), ObjectId: params.oid.String(), Relation: params.rel.String(), SubjectType: step.Object.String(), SubjectRelation: step.Relation.String(), } - rels, err := s.getRels(req) - if err != nil { + + relsPtr := s.pool.GetSlice() + if err := s.getRels(req, s.pool, relsPtr); err != nil { return results, err } + defer s.pool.PutSlice(relsPtr) - for _, rel := range rels { + for _, rel := range *relsPtr { current := relationFromProto(rel) if params.srel == model.RelationName(rel.SubjectRelation) && params.st == model.ObjectName(rel.SubjectType) { @@ -281,17 +292,20 @@ func (s *SubjectSearch) expandTerm(o *model.Object, pt *model.PermissionTerm, pa } func (s *SubjectSearch) expandRelationArrow(pt *model.PermissionTerm, params *relation) ([]*relation, error) { - // Resolve the base of the arrow. - rels, err := s.getRels(&dsc.Relation{ + relsPtr := s.pool.GetSlice() + + req := &dsc.RelationIdentifier{ ObjectType: params.ot.String(), ObjectId: params.oid.String(), Relation: pt.Base.String(), - }) - if err != nil { + } + + // Resolve the base of the arrow. + if err := s.getRels(req, s.pool, relsPtr); err != nil { return []*relation{}, err } - expanded := lo.Map(rels, func(rel *dsc.Relation, _ int) *relation { + expanded := lo.Map(*relsPtr, func(rel *dsc.RelationIdentifier, _ int) *relation { return &relation{ ot: model.ObjectName(rel.SubjectType), oid: ObjectID(rel.SubjectId), @@ -302,6 +316,8 @@ func (s *SubjectSearch) expandRelationArrow(pt *model.PermissionTerm, params *re } }) + s.pool.PutSlice(relsPtr) + return expanded, nil } diff --git a/graph/subjects_test.go b/graph/subjects_test.go index 8e38164..91dedd4 100644 --- a/graph/subjects_test.go +++ b/graph/subjects_test.go @@ -4,6 +4,7 @@ import ( "testing" azmgraph "github.com/aserto-dev/azm/graph" + "github.com/aserto-dev/azm/mempool" "github.com/aserto-dev/azm/model" v3 "github.com/aserto-dev/azm/v3" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" @@ -18,11 +19,13 @@ func TestSearchSubjects(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, m) + pool := mempool.NewRelationsPool() + for _, test := range searchSubjectsTests { t.Run(test.search, func(tt *testing.T) { assert := assert.New(tt) - subjSearch, err := azmgraph.NewSubjectSearch(m, graphReq(test.search), rels.GetRelations) + subjSearch, err := azmgraph.NewSubjectSearch(m, graphReq(test.search), rels.GetRelations, pool) assert.NoError(err) res, err := subjSearch.Search() diff --git a/graph/utils_test.go b/graph/utils_test.go index 0b68bd7..49e59f9 100644 --- a/graph/utils_test.go +++ b/graph/utils_test.go @@ -4,6 +4,7 @@ import ( "regexp" "testing" + "github.com/aserto-dev/azm/graph" "github.com/aserto-dev/azm/model" dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" @@ -11,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" ) -func checkReq(expr string) *dsr.CheckRequest { - return parseRelation(expr).checkReq() +func checkReq(expr string, trace bool) *dsr.CheckRequest { + return parseRelation(expr).checkReq(trace) } func graphReq(expr string) *dsr.GetGraphRequest { @@ -49,25 +50,25 @@ func parseRelation(r string) *relation { return rel } -func (r *relation) proto() *dsc.Relation { - return &dsc.Relation{ - ObjectType: r.ObjectType.String(), - ObjectId: r.ObjectID.String(), - Relation: r.Relation.String(), - SubjectType: r.SubjectType.String(), - SubjectId: r.SubjectID.String(), - SubjectRelation: r.SubjectRelation.String(), - } +func (r *relation) proto(pool graph.MessagePool[*dsc.RelationIdentifier]) *dsc.RelationIdentifier { + rel := pool.Get() + rel.ObjectType = r.ObjectType.String() + rel.ObjectId = r.ObjectID.String() + rel.Relation = r.Relation.String() + rel.SubjectType = r.SubjectType.String() + rel.SubjectId = r.SubjectID.String() + rel.SubjectRelation = r.SubjectRelation.String() + return rel } -func (r *relation) checkReq() *dsr.CheckRequest { +func (r *relation) checkReq(trace bool) *dsr.CheckRequest { return &dsr.CheckRequest{ ObjectType: r.ObjectType.String(), ObjectId: r.ObjectID.String(), Relation: r.Relation.String(), SubjectType: r.SubjectType.String(), SubjectId: r.SubjectID.String(), - Trace: true, + Trace: trace, } } @@ -96,7 +97,7 @@ func NewRelationsReader(rels ...string) RelationsReader { }) } -func (r RelationsReader) GetRelations(req *dsc.Relation) ([]*dsc.Relation, error) { +func (r RelationsReader) GetRelations(req *dsc.RelationIdentifier, pool graph.MessagePool[*dsc.RelationIdentifier], out *graph.Relations) error { ot := model.ObjectName(req.ObjectType) oid := model.ObjectID(req.ObjectId) rn := model.RelationName(req.Relation) @@ -113,9 +114,11 @@ func (r RelationsReader) GetRelations(req *dsc.Relation) ([]*dsc.Relation, error (sr == "" || rel.SubjectRelation == sr) }) - return lo.Map(matches, func(r *relation, _ int) *dsc.Relation { - return r.proto() - }), nil + for _, rel := range matches { + *out = append(*out, rel.proto(pool)) + } + + return nil } type parseTest struct { diff --git a/mempool/mempool.go b/mempool/mempool.go new file mode 100644 index 0000000..578f115 --- /dev/null +++ b/mempool/mempool.go @@ -0,0 +1,81 @@ +package mempool + +import ( + "sync" +) + +const defaultSliceCapacity = 2048 + +type Pool[T any] struct { + sync.Pool +} + +func (p *Pool[T]) Get() T { + return p.Pool.Get().(T) +} + +func (p *Pool[T]) Put(x T) { + p.Pool.Put(x) +} + +func NewPool[T any](newF func() T) *Pool[T] { + return &Pool[T]{ + Pool: sync.Pool{ + New: func() interface{} { + return newF() + }, + }, + } +} + +func NewSlicePool[T any](capacity int) *Pool[*[]T] { + return NewPool(func() *[]T { + s := make([]T, 0, capacity) + return &s + }) +} + +type Allocator[T any] interface { + New() T + Reset(T) +} + +type CollectionPool[T any] struct { + slicePool *Pool[*[]T] + msgPool *Pool[T] + alloc Allocator[T] +} + +func NewCollectionPool[T any](alloc Allocator[T]) *CollectionPool[T] { + return &CollectionPool[T]{ + slicePool: NewSlicePool[T](defaultSliceCapacity), + alloc: alloc, + msgPool: NewPool(func() T { + return alloc.New() + }), + } +} + +func (p CollectionPool[T]) GetSlice() *[]T { + return p.slicePool.Get() +} + +func (p *CollectionPool[T]) PutSlice(s *[]T) { + for _, item := range *s { + p.alloc.Reset(item) + p.msgPool.Put(item) + } + + *s = (*s)[:0] + p.slicePool.Put(s) +} + +// nolint: gocritic // commentedOutCode +func (p *CollectionPool[T]) Get() T { + return p.msgPool.New().(T) + // return p.msgPool.Get() +} + +func (p *CollectionPool[T]) Put(t T) { + p.msgPool.Put(t) +} diff --git a/mempool/relation.go b/mempool/relation.go new file mode 100644 index 0000000..1a24fd4 --- /dev/null +++ b/mempool/relation.go @@ -0,0 +1,34 @@ +package mempool + +import ( + dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type RelationsPool = CollectionPool[*dsc.RelationIdentifier] + +func NewRelationsPool() *RelationsPool { + return NewCollectionPool[*dsc.RelationIdentifier](NewRelationAllocator()) +} + +type RelationAllocator struct { + tsPool *Pool[*timestamppb.Timestamp] +} + +func NewRelationAllocator() *RelationAllocator { + return &RelationAllocator{ + tsPool: NewPool[*timestamppb.Timestamp]( + func() *timestamppb.Timestamp { + return new(timestamppb.Timestamp) + }), + } +} + +func (ra *RelationAllocator) New() *dsc.RelationIdentifier { + rel := dsc.RelationIdentifierFromVTPool() + return rel +} + +func (ra *RelationAllocator) Reset(rel *dsc.RelationIdentifier) { + rel.ReturnToVTPool() +} diff --git a/model/inverse.go b/model/inverse.go index cf773dc..b1334db 100644 --- a/model/inverse.go +++ b/model/inverse.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "os" set "github.com/deckarep/golang-set/v2" "github.com/samber/lo" @@ -14,7 +15,11 @@ const ( ) func (m *Model) Invert() *Model { - return newInverter(m).invert() + if m.inverted == nil { + fmt.Fprintln(os.Stderr, "load inverted model") + m.inverted = newInverter(m).invert() + } + return m.inverted } type inverter struct { diff --git a/model/model.go b/model/model.go index 5d5ab32..3a61bfd 100644 --- a/model/model.go +++ b/model/model.go @@ -19,6 +19,7 @@ type Model struct { Version int `json:"version"` Objects map[ObjectName]*Object `json:"types"` Metadata *Metadata `json:"metadata"` + inverted *Model `json:"-"` } type Metadata struct { @@ -33,6 +34,7 @@ func New(r io.Reader) (*Model, error) { if err := dec.Decode(&m); err != nil { return nil, err } + m.inverted = newInverter(&m).invert() return &m, nil } @@ -64,8 +66,10 @@ func (r *relation) String() string { return fmt.Sprintf("%s:%s#%s@%s:%s%s", r.on, r.oid, r.rn, r.sn, r.sid, srn) } -type objSet set.Set[ObjectName] -type relSet set.Set[RelationRef] +type ( + objSet set.Set[ObjectName] + relSet set.Set[RelationRef] +) func (m *Model) Reader() (io.Reader, error) { b := bytes.Buffer{} diff --git a/safe/relation.go b/safe/relation.go index dbd218a..54214c1 100644 --- a/safe/relation.go +++ b/safe/relation.go @@ -13,11 +13,11 @@ import ( // SafeRelation identifier. type SafeRelation struct { - *dsc3.Relation + *dsc3.RelationIdentifier HasSubjectRelation bool } -func Relation(i *dsc3.Relation) *SafeRelation { return &SafeRelation{i, true} } +func Relation(i *dsc3.RelationIdentifier) *SafeRelation { return &SafeRelation{i, true} } type SafeRelationIdentifier struct { *model.RelationRef @@ -35,7 +35,7 @@ type SafeRelations struct { func GetRelation(i *dsr3.GetRelationRequest) *SafeRelations { return &SafeRelations{ &SafeRelation{ - Relation: &dsc3.Relation{ + RelationIdentifier: &dsc3.RelationIdentifier{ ObjectType: i.ObjectType, ObjectId: i.ObjectId, Relation: i.Relation, @@ -51,7 +51,7 @@ func GetRelation(i *dsr3.GetRelationRequest) *SafeRelations { func GetRelations(i *dsr3.GetRelationsRequest) *SafeRelations { return &SafeRelations{ &SafeRelation{ - Relation: &dsc3.Relation{ + RelationIdentifier: &dsc3.RelationIdentifier{ ObjectType: i.ObjectType, ObjectId: i.ObjectId, Relation: i.Relation, @@ -79,7 +79,7 @@ func (i *SafeRelation) Subject() *dsc3.ObjectIdentifier { } func (i *SafeRelation) Validate(mc *cache.Cache) error { - if i == nil || i.Relation == nil { + if i == nil || i.RelationIdentifier == nil { return derr.ErrInvalidRelation.Msg("relation not set (nil)") } @@ -109,11 +109,11 @@ func (i *SafeRelation) Validate(mc *cache.Cache) error { } } - return mc.ValidateRelation(i.Relation) + return mc.ValidateRelation(i.RelationIdentifier) } func (i *SafeRelations) Validate(mc *cache.Cache) error { - if i == nil || i.SafeRelation.Relation == nil { + if i == nil || i.SafeRelation.RelationIdentifier == nil { return derr.ErrInvalidRelation.Msg("relation not set (nil)") } @@ -152,7 +152,7 @@ func (i *SafeRelation) Hash() string { h := fnv.New64a() h.Reset() - if i != nil && i.Relation != nil { + if i != nil && i.RelationIdentifier != nil { if _, err := h.Write([]byte(i.GetObjectId())); err != nil { return DefaultHash }