Skip to content

Commit 5fc65b0

Browse files
authored
Further operations and improvements to Schema V2 library (#2643)
1 parent f8379c3 commit 5fc65b0

File tree

12 files changed

+1831
-131
lines changed

12 files changed

+1831
-131
lines changed

pkg/query/build_tree.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,20 +194,20 @@ func (b *iteratorBuilder) buildIteratorFromOperation(p *schema.Permission, op sc
194194

195195
return NewExclusion(mainIt, excludedIt), nil
196196

197-
case *schema.FunctionedTuplesetOperation:
198-
rel, ok := p.Parent().Relations()[perm.TuplesetRelation()]
197+
case *schema.FunctionedArrowReference:
198+
rel, ok := p.Parent().Relations()[perm.Left()]
199199
if !ok {
200-
return nil, fmt.Errorf("BuildIteratorFromSchema: couldn't find tupleset relation `%s` for functioned tupleset `%s.%s(%s)` for permission `%s` in definition `%s`", perm.TuplesetRelation(), perm.TuplesetRelation(), functionTypeString(perm.Function()), perm.ComputedRelation(), p.Name(), p.Parent().Name())
200+
return nil, fmt.Errorf("BuildIteratorFromSchema: couldn't find arrow relation `%s` for functioned arrow `%s.%s(%s)` for permission `%s` in definition `%s`", perm.Left(), perm.Left(), functionTypeString(perm.Function()), perm.Right(), p.Name(), p.Parent().Name())
201201
}
202202

203203
switch perm.Function() {
204204
case schema.FunctionTypeAny:
205205
// any() functions just like an arrow
206-
return b.buildArrowIterators(rel, perm.ComputedRelation())
206+
return b.buildArrowIterators(rel, perm.Right())
207207

208208
case schema.FunctionTypeAll:
209209
// all() requires intersection arrow - user must have permission on ALL left subjects
210-
return b.buildIntersectionArrowIterators(rel, perm.ComputedRelation())
210+
return b.buildIntersectionArrowIterators(rel, perm.Right())
211211

212212
default:
213213
return nil, fmt.Errorf("unknown function type: %v", perm.Function())

pkg/schema/v2/clone_test.go

Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/require"
7+
8+
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
9+
"github.com/authzed/spicedb/pkg/schemadsl/input"
710
)
811

912
func TestCloneSchema_Nil(t *testing.T) {
@@ -390,18 +393,18 @@ func TestCloneSchema_Mutation(t *testing.T) {
390393
require.NotContains(t, original.definitions, "document")
391394
}
392395

393-
func TestCloneSchema_WithFunctionedTuplesetOperation(t *testing.T) {
394-
// Test cloning with FunctionedTuplesetOperation
395-
anyOp := &FunctionedTuplesetOperation{
396-
tuplesetRelation: "parent",
397-
function: FunctionTypeAny,
398-
computedRelation: "viewer",
396+
func TestCloneSchema_WithFunctionedArrowReference(t *testing.T) {
397+
// Test cloning with FunctionedArrowReference
398+
anyOp := &FunctionedArrowReference{
399+
left: "parent",
400+
function: FunctionTypeAny,
401+
right: "viewer",
399402
}
400403

401-
allOp := &FunctionedTuplesetOperation{
402-
tuplesetRelation: "group",
403-
function: FunctionTypeAll,
404-
computedRelation: "member",
404+
allOp := &FunctionedArrowReference{
405+
left: "group",
406+
function: FunctionTypeAll,
407+
right: "member",
405408
}
406409

407410
// Test with both any() and all() functions in a union
@@ -454,46 +457,46 @@ func TestCloneSchema_WithFunctionedTuplesetOperation(t *testing.T) {
454457
clonedUnion := clonedPerm.operation.(*UnionOperation)
455458
require.Len(t, clonedUnion.children, 3)
456459

457-
// Check FunctionedTuplesetOperation with any()
458-
clonedAnyOp := clonedUnion.children[0].(*FunctionedTuplesetOperation)
460+
// Check FunctionedArrowReference with any()
461+
clonedAnyOp := clonedUnion.children[0].(*FunctionedArrowReference)
459462
require.NotSame(t, anyOp, clonedAnyOp)
460-
require.Equal(t, "parent", clonedAnyOp.tuplesetRelation)
463+
require.Equal(t, "parent", clonedAnyOp.left)
461464
require.Equal(t, FunctionTypeAny, clonedAnyOp.function)
462-
require.Equal(t, "viewer", clonedAnyOp.computedRelation)
465+
require.Equal(t, "viewer", clonedAnyOp.right)
463466

464-
// Check FunctionedTuplesetOperation with all()
465-
clonedAllOp := clonedUnion.children[1].(*FunctionedTuplesetOperation)
467+
// Check FunctionedArrowReference with all()
468+
clonedAllOp := clonedUnion.children[1].(*FunctionedArrowReference)
466469
require.NotSame(t, allOp, clonedAllOp)
467-
require.Equal(t, "group", clonedAllOp.tuplesetRelation)
470+
require.Equal(t, "group", clonedAllOp.left)
468471
require.Equal(t, FunctionTypeAll, clonedAllOp.function)
469-
require.Equal(t, "member", clonedAllOp.computedRelation)
472+
require.Equal(t, "member", clonedAllOp.right)
470473

471474
// Check RelationReference
472475
clonedRelRef := clonedUnion.children[2].(*RelationReference)
473476
require.NotSame(t, op.children[2], clonedRelRef)
474477
require.Equal(t, "direct_viewer", clonedRelRef.relationName)
475478
}
476479

477-
func TestCloneOperation_FunctionedTuplesetOperation(t *testing.T) {
478-
// Test direct cloning of FunctionedTuplesetOperation
480+
func TestCloneOperation_FunctionedArrowReference(t *testing.T) {
481+
// Test direct cloning of FunctionedArrowReference
479482
tests := []struct {
480483
name string
481-
op *FunctionedTuplesetOperation
484+
op *FunctionedArrowReference
482485
}{
483486
{
484487
name: "any function",
485-
op: &FunctionedTuplesetOperation{
486-
tuplesetRelation: "parent",
487-
function: FunctionTypeAny,
488-
computedRelation: "viewer",
488+
op: &FunctionedArrowReference{
489+
left: "parent",
490+
function: FunctionTypeAny,
491+
right: "viewer",
489492
},
490493
},
491494
{
492495
name: "all function",
493-
op: &FunctionedTuplesetOperation{
494-
tuplesetRelation: "group",
495-
function: FunctionTypeAll,
496-
computedRelation: "admin",
496+
op: &FunctionedArrowReference{
497+
left: "group",
498+
function: FunctionTypeAll,
499+
right: "admin",
497500
},
498501
},
499502
{
@@ -514,38 +517,38 @@ func TestCloneOperation_FunctionedTuplesetOperation(t *testing.T) {
514517
require.NotNil(t, cloned)
515518
require.NotSame(t, tt.op, cloned)
516519

517-
clonedOp := cloned.(*FunctionedTuplesetOperation)
518-
require.Equal(t, tt.op.tuplesetRelation, clonedOp.tuplesetRelation)
520+
clonedOp := cloned.(*FunctionedArrowReference)
521+
require.Equal(t, tt.op.left, clonedOp.left)
519522
require.Equal(t, tt.op.function, clonedOp.function)
520-
require.Equal(t, tt.op.computedRelation, clonedOp.computedRelation)
523+
require.Equal(t, tt.op.right, clonedOp.right)
521524

522525
// Test that mutation doesn't affect original
523-
clonedOp.tuplesetRelation = "modified"
524-
require.NotEqual(t, tt.op.tuplesetRelation, clonedOp.tuplesetRelation)
526+
clonedOp.left = "modified"
527+
require.NotEqual(t, tt.op.left, clonedOp.left)
525528
})
526529
}
527530
}
528531

529532
func TestCloneSchema_ComplexOperationWithFunctionedTupleset(t *testing.T) {
530-
// Test a complex nested operation tree that includes FunctionedTuplesetOperation
533+
// Test a complex nested operation tree that includes FunctionedArrowReference
531534
op := &UnionOperation{
532535
children: []Operation{
533536
&RelationReference{relationName: "direct"},
534537
&IntersectionOperation{
535538
children: []Operation{
536-
&FunctionedTuplesetOperation{
537-
tuplesetRelation: "parent",
538-
function: FunctionTypeAny,
539-
computedRelation: "viewer",
539+
&FunctionedArrowReference{
540+
left: "parent",
541+
function: FunctionTypeAny,
542+
right: "viewer",
540543
},
541544
&RelationReference{relationName: "approved"},
542545
},
543546
},
544547
&ExclusionOperation{
545-
left: &FunctionedTuplesetOperation{
546-
tuplesetRelation: "organization",
547-
function: FunctionTypeAll,
548-
computedRelation: "member",
548+
left: &FunctionedArrowReference{
549+
left: "organization",
550+
function: FunctionTypeAll,
551+
right: "member",
549552
},
550553
right: &RelationReference{relationName: "suspended"},
551554
},
@@ -585,19 +588,64 @@ func TestCloneSchema_ComplexOperationWithFunctionedTupleset(t *testing.T) {
585588
require.NotSame(t, op, clonedUnion)
586589
require.Len(t, clonedUnion.children, 3)
587590

588-
// Check intersection with FunctionedTuplesetOperation
591+
// Check intersection with FunctionedArrowReference
589592
clonedIntersection := clonedUnion.children[1].(*IntersectionOperation)
590593
require.NotSame(t, op.children[1], clonedIntersection)
591-
clonedFuncOp1 := clonedIntersection.children[0].(*FunctionedTuplesetOperation)
592-
require.Equal(t, "parent", clonedFuncOp1.tuplesetRelation)
594+
clonedFuncOp1 := clonedIntersection.children[0].(*FunctionedArrowReference)
595+
require.Equal(t, "parent", clonedFuncOp1.left)
593596
require.Equal(t, FunctionTypeAny, clonedFuncOp1.function)
594-
require.Equal(t, "viewer", clonedFuncOp1.computedRelation)
597+
require.Equal(t, "viewer", clonedFuncOp1.right)
595598

596-
// Check exclusion with FunctionedTuplesetOperation
599+
// Check exclusion with FunctionedArrowReference
597600
clonedExclusion := clonedUnion.children[2].(*ExclusionOperation)
598601
require.NotSame(t, op.children[2], clonedExclusion)
599-
clonedFuncOp2 := clonedExclusion.left.(*FunctionedTuplesetOperation)
600-
require.Equal(t, "organization", clonedFuncOp2.tuplesetRelation)
602+
clonedFuncOp2 := clonedExclusion.left.(*FunctionedArrowReference)
603+
require.Equal(t, "organization", clonedFuncOp2.left)
601604
require.Equal(t, FunctionTypeAll, clonedFuncOp2.function)
602-
require.Equal(t, "member", clonedFuncOp2.computedRelation)
605+
require.Equal(t, "member", clonedFuncOp2.right)
606+
}
607+
608+
func TestClone_ResolvedFunctionedArrowReference(t *testing.T) {
609+
// Create a test schema with a functioned arrow
610+
schemaString := `definition document {
611+
relation parent: folder
612+
permission view = parent.any(viewer)
613+
}
614+
615+
definition folder {
616+
relation viewer: user
617+
}
618+
619+
definition user {}`
620+
621+
// Step 1: Compile the schema
622+
compiled, err := compiler.Compile(compiler.InputSchema{
623+
Source: input.Source("test"),
624+
SchemaString: schemaString,
625+
}, compiler.AllowUnprefixedObjectType())
626+
require.NoError(t, err)
627+
628+
// Step 2: Convert to *Schema
629+
schema, err := BuildSchemaFromCompiledSchema(*compiled)
630+
require.NoError(t, err)
631+
require.NotNil(t, schema)
632+
633+
// Step 3: Resolve the schema
634+
resolved, err := ResolveSchema(schema)
635+
require.NoError(t, err)
636+
require.NotNil(t, resolved)
637+
638+
// Step 4: Clone the resolved schema
639+
cloned := resolved.schema.clone()
640+
require.NotNil(t, cloned)
641+
642+
// Verify that cloning worked correctly
643+
clonedDef := cloned.definitions["document"]
644+
require.NotNil(t, clonedDef)
645+
clonedPerm := clonedDef.permissions["view"]
646+
require.NotNil(t, clonedPerm)
647+
648+
// The operation should be a ResolvedFunctionedArrowReference
649+
_, ok := clonedPerm.operation.(*ResolvedFunctionedArrowReference)
650+
require.True(t, ok, "expected ResolvedFunctionedArrowReference after clone")
603651
}

pkg/schema/v2/convert.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,10 @@ func convertChild(child *corev1.SetOperation_Child) (Operation, error) {
234234
if err != nil {
235235
return nil, err
236236
}
237-
return &FunctionedTuplesetOperation{
238-
tuplesetRelation: childType.FunctionedTupleToUserset.GetTupleset().GetRelation(),
239-
function: functionType,
240-
computedRelation: childType.FunctionedTupleToUserset.GetComputedUserset().GetRelation(),
237+
return &FunctionedArrowReference{
238+
left: childType.FunctionedTupleToUserset.GetTupleset().GetRelation(),
239+
right: childType.FunctionedTupleToUserset.GetComputedUserset().GetRelation(),
240+
function: functionType,
241241
}, nil
242242
case *corev1.SetOperation_Child_XNil:
243243
return &RelationReference{

pkg/schema/v2/convert_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -638,11 +638,11 @@ func TestConvertFunctionedTupleToUserset(t *testing.T) {
638638
result, err := convertChild(child)
639639
require.NoError(t, err)
640640

641-
funcOp, ok := result.(*FunctionedTuplesetOperation)
642-
require.True(t, ok, "Should convert to FunctionedTuplesetOperation")
643-
require.Equal(t, "team", funcOp.TuplesetRelation())
641+
funcOp, ok := result.(*FunctionedArrowReference)
642+
require.True(t, ok, "Should convert to FunctionedArrowReference")
643+
require.Equal(t, "team", funcOp.Left())
644+
require.Equal(t, "member", funcOp.Right())
644645
require.Equal(t, FunctionTypeAny, funcOp.Function())
645-
require.Equal(t, "member", funcOp.ComputedRelation())
646646
})
647647

648648
t.Run("convert FUNCTION_ALL", func(t *testing.T) {
@@ -665,11 +665,11 @@ func TestConvertFunctionedTupleToUserset(t *testing.T) {
665665
result, err := convertChild(child)
666666
require.NoError(t, err)
667667

668-
funcOp, ok := result.(*FunctionedTuplesetOperation)
669-
require.True(t, ok, "Should convert to FunctionedTuplesetOperation")
670-
require.Equal(t, "group", funcOp.TuplesetRelation())
668+
funcOp, ok := result.(*FunctionedArrowReference)
669+
require.True(t, ok, "Should convert to FunctionedArrowReference")
670+
require.Equal(t, "group", funcOp.Left())
671+
require.Equal(t, "viewer", funcOp.Right())
671672
require.Equal(t, FunctionTypeAll, funcOp.Function())
672-
require.Equal(t, "viewer", funcOp.ComputedRelation())
673673
})
674674

675675
t.Run("convert unknown function type", func(t *testing.T) {
@@ -760,23 +760,23 @@ func TestConvertFunctionedTupleToUserset(t *testing.T) {
760760
require.True(t, ok)
761761
require.Equal(t, "view", viewPerm.Name())
762762

763-
// The permission should have a FunctionedTuplesetOperation (might be unwrapped from union if single child)
764-
// Check if it's directly a FunctionedTuplesetOperation or wrapped in a union
765-
if funcOp, ok := viewPerm.Operation().(*FunctionedTuplesetOperation); ok {
763+
// The permission should have a FunctionedArrowReference (might be unwrapped from union if single child)
764+
// Check if it's directly a FunctionedArrowReference or wrapped in a union
765+
if funcOp, ok := viewPerm.Operation().(*FunctionedArrowReference); ok {
766766
// Direct operation (unwrapped by optimization)
767-
require.Equal(t, "team", funcOp.TuplesetRelation())
767+
require.Equal(t, "team", funcOp.Left())
768+
require.Equal(t, "member", funcOp.Right())
768769
require.Equal(t, FunctionTypeAll, funcOp.Function())
769-
require.Equal(t, "member", funcOp.ComputedRelation())
770770
} else if union, ok := viewPerm.Operation().(*UnionOperation); ok {
771771
// Wrapped in union
772772
require.Len(t, union.Children(), 1)
773-
funcOp, ok := union.Children()[0].(*FunctionedTuplesetOperation)
773+
funcOp, ok := union.Children()[0].(*FunctionedArrowReference)
774774
require.True(t, ok)
775-
require.Equal(t, "team", funcOp.TuplesetRelation())
775+
require.Equal(t, "team", funcOp.Left())
776+
require.Equal(t, "member", funcOp.Right())
776777
require.Equal(t, FunctionTypeAll, funcOp.Function())
777-
require.Equal(t, "member", funcOp.ComputedRelation())
778778
} else {
779-
require.Fail(t, "Expected FunctionedTuplesetOperation, got %T", viewPerm.Operation())
779+
require.Fail(t, "Expected FunctionedArrowReference, got %T", viewPerm.Operation())
780780
}
781781
})
782782
}

0 commit comments

Comments
 (0)