diff --git a/internal/e2e/transaction_cases_test.go b/internal/e2e/transaction_cases_test.go index 9f925a902..08cebd458 100644 --- a/internal/e2e/transaction_cases_test.go +++ b/internal/e2e/transaction_cases_test.go @@ -62,6 +62,25 @@ func runTransactionCases(c transactClient, m *namespaceTestManager) func(*testin assert.Len(t, resp.RelationTuples, 0) }) + t.Run("case=duplicate string representations", func(t *testing.T) { + n := &namespace.Namespace{Name: t.Name()} + m.add(t, n) + c.transactTuples(t, []*ketoapi.RelationTuple{ + { + Namespace: n.Name, + Object: "o", + Relation: "rel", + SubjectID: pointerx.Ptr("sid"), + }, + { + Namespace: n.Name, + Object: "o", + Relation: "rel", + SubjectID: pointerx.Ptr("sid"), + }, + }, nil) + }) + t.Run("case=large inserts and deletes", func(t *testing.T) { if !testing.Short() { t.Skip("This test is fairly expensive, especially the deletion.") diff --git a/internal/persistence/sql/relationtuples.go b/internal/persistence/sql/relationtuples.go index b6f40f781..4daf6ecfc 100644 --- a/internal/persistence/sql/relationtuples.go +++ b/internal/persistence/sql/relationtuples.go @@ -175,14 +175,14 @@ func buildDelete(nid uuid.UUID, rs []*relationtuple.RelationTuple) (query string } func (p *Persister) DeleteRelationTuples(ctx context.Context, rs ...*relationtuple.RelationTuple) (err error) { - ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteRelationTuples", - trace.WithAttributes(attribute.Int("count", len(rs)))) - defer otelx.End(span, &err) - if len(rs) == 0 { return nil } + ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteRelationTuples", + trace.WithAttributes(attribute.Int("count", len(rs)))) + defer otelx.End(span, &err) + return p.Transaction(ctx, func(ctx context.Context) error { for chunk := range slices.Chunk(rs, chunkSizeDeleteTuple) { q, args, err := buildDelete(p.NetworkID(ctx), chunk) @@ -310,14 +310,14 @@ func buildInsert(commitTime time.Time, nid uuid.UUID, rs []*relationtuple.Relati } func (p *Persister) WriteRelationTuples(ctx context.Context, rs ...*relationtuple.RelationTuple) (err error) { - ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.WriteRelationTuples", - trace.WithAttributes(attribute.Int("count", len(rs)))) - defer otelx.End(span, &err) - if len(rs) == 0 { return nil } + ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.WriteRelationTuples", + trace.WithAttributes(attribute.Int("count", len(rs)))) + defer otelx.End(span, &err) + commitTime := time.Now() return p.Transaction(ctx, func(ctx context.Context) error { diff --git a/internal/persistence/sql/uuid_mapping.go b/internal/persistence/sql/uuid_mapping.go index 4cd429a41..633b8db19 100644 --- a/internal/persistence/sql/uuid_mapping.go +++ b/internal/persistence/sql/uuid_mapping.go @@ -4,6 +4,7 @@ package sql import ( + "bytes" "context" "iter" "maps" @@ -13,6 +14,8 @@ import ( "github.com/gofrs/uuid" "github.com/ory/x/otelx" "github.com/ory/x/sqlcon" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/ory/keto/internal/x" ) @@ -89,13 +92,14 @@ func (p *Persister) batchFromUUIDs(ctx context.Context, ids []uuid.UUID, opts .. } func (p *Persister) MapStringsToUUIDs(ctx context.Context, values ...string) (uuids []uuid.UUID, err error) { - ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.MapStringsToUUIDs") - defer otelx.End(span, &err) - if len(values) == 0 { return } + ctx, span := p.d.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.MapStringsToUUIDs", + trace.WithAttributes(attribute.Int("num_values", len(values)))) + defer otelx.End(span, &err) + uuids, err = p.MapStringsToUUIDsReadOnly(ctx, values...) if err != nil { return nil, err @@ -110,6 +114,14 @@ func (p *Persister) MapStringsToUUIDs(ctx context.Context, values ...string) (uu StringRepresentation: values[i], } } + slices.SortFunc(mappings, func(a, b UUIDMapping) int { + return bytes.Compare(a.ID[:], b.ID[:]) + }) + mappings = slices.CompactFunc(mappings, func(a, b UUIDMapping) bool { + return a.ID == b.ID + }) + + span.SetAttributes(attribute.Int("num_mappings", len(mappings))) err = p.Transaction(ctx, func(ctx context.Context) error { for chunk := range slices.Chunk(mappings, chunkSizeInsertUUIDMappings) {