From fb7af042c1081944f773158173697133ebab4974 Mon Sep 17 00:00:00 2001 From: Henning Perl Date: Thu, 20 Jan 2022 18:03:54 +0100 Subject: [PATCH] feat: Add migration for UUID mapping --- .../sql/migrations/single_table_test.go | 2 +- .../sql/migrations/uuid_mapping.go | 56 ++++++++++++ .../sql/migrations/uuid_mapping_test.go | 85 +++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 internal/persistence/sql/migrations/uuid_mapping.go create mode 100644 internal/persistence/sql/migrations/uuid_mapping_test.go diff --git a/internal/persistence/sql/migrations/single_table_test.go b/internal/persistence/sql/migrations/single_table_test.go index 13e3f8cf2..f41d3e010 100644 --- a/internal/persistence/sql/migrations/single_table_test.go +++ b/internal/persistence/sql/migrations/single_table_test.go @@ -226,7 +226,7 @@ func TestToSingleTableMigrator_HasLegacyTable(t *testing.T) { ln, err := m.LegacyNamespaces(ctx) require.NoError(t, err) - assert.Equal(t, nspaces, ln) + assert.ElementsMatch(t, nspaces, ln) }) }) } diff --git a/internal/persistence/sql/migrations/uuid_mapping.go b/internal/persistence/sql/migrations/uuid_mapping.go new file mode 100644 index 000000000..3472017d9 --- /dev/null +++ b/internal/persistence/sql/migrations/uuid_mapping.go @@ -0,0 +1,56 @@ +package migrations + +import ( + "context" + + "github.com/gobuffalo/pop/v6" + "github.com/gofrs/uuid" + + "github.com/ory/keto/internal/persistence/sql" +) + +type ( + toUUIDMappingMigrator struct { + d dependencies + } +) + +// NewToUUIDMappingMigrator creates a new UUID mapping migrator. +func NewToUUIDMappingMigrator(d dependencies) *toUUIDMappingMigrator { + return &toUUIDMappingMigrator{d: d} +} + +// MigrateUUIDMappings migrates to UUID-mapped subject IDs for all relation +// tuples in the database. +func (m *toUUIDMappingMigrator) MigrateUUIDMappings(ctx context.Context) error { + p, ok := m.d.Persister().(*sql.Persister) + if !ok { + panic("got unexpected persister") + } + + return p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { + var relationTuples []*sql.RelationTuple + + if err := p.Connection(ctx).All(&relationTuples); err != nil { + return err + } + + for _, rt := range relationTuples { + _, err := uuid.FromString(rt.SubjectID.String) + if err == nil || !rt.SubjectID.Valid || rt.SubjectID.String == "" { + continue + } + + id := uuid.Must(uuid.NewV4()) + if err := p.AddUUIDMapping(ctx, id, rt.SubjectID.String); err != nil { + return err + } + + rt.SubjectID.String = id.String() + if err := c.Update(rt); err != nil { + return err + } + } + return nil + }) +} diff --git a/internal/persistence/sql/migrations/uuid_mapping_test.go b/internal/persistence/sql/migrations/uuid_mapping_test.go new file mode 100644 index 000000000..1f469b9ba --- /dev/null +++ b/internal/persistence/sql/migrations/uuid_mapping_test.go @@ -0,0 +1,85 @@ +package migrations + +import ( + "context" + dbsql "database/sql" + "testing" + "time" + + "github.com/gofrs/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ory/keto/internal/driver" + "github.com/ory/keto/internal/persistence/sql" + "github.com/ory/keto/internal/x/dbx" +) + +func TestToUUIDMappingMigrator(t *testing.T) { + const debugOnDisk = false + + for _, dsn := range dbx.GetDSNs(t, debugOnDisk) { + t.Run("db="+dsn.Name, func(t *testing.T) { + ctx := context.Background() + r := driver.NewTestRegistry(t, dsn) + m := NewToUUIDMappingMigrator(r) + p := m.d.Persister().(*sql.Persister) + conn := p.Connection(ctx) + + testCases := []struct { + name string + rt *sql.RelationTuple + expectMapping bool + }{{ + name: "with string subject", + rt: &sql.RelationTuple{ + ID: uuid.Must(uuid.NewV4()), + SubjectID: dbsql.NullString{String: "a", Valid: true}, + CommitTime: time.Now(), + }, + expectMapping: true, + }, { + name: "with null subject", + rt: &sql.RelationTuple{ + ID: uuid.Must(uuid.NewV4()), + SubjectID: dbsql.NullString{String: "", Valid: false}, + SubjectSetNamespaceID: dbsql.NullInt32{Int32: 0, Valid: true}, + SubjectSetObject: dbsql.NullString{String: "obj", Valid: true}, + SubjectSetRelation: dbsql.NullString{String: "rel", Valid: true}, + CommitTime: time.Now(), + }, + expectMapping: false, + }, { + name: "with UUID subject", + rt: &sql.RelationTuple{ + ID: uuid.Must(uuid.NewV4()), + SubjectID: dbsql.NullString{String: uuid.Must(uuid.NewV4()).String(), Valid: true}, + CommitTime: time.Now(), + }, + expectMapping: false, + }} + + for _, tc := range testCases { + t.Run("case="+tc.name, func(t *testing.T) { + require.NoError(t, conn.Create(tc.rt)) + require.NoError(t, m.MigrateUUIDMappings(ctx)) + + newRt := &sql.RelationTuple{} + require.NoError(t, conn.Find(newRt, tc.rt.ID)) + + if tc.expectMapping { + // Check that a mapping was created + mapping := &sql.UUIDMapping{} + require.NoError(t, conn.Find(mapping, newRt.SubjectID)) + assert.NotEqual(t, tc.rt.SubjectID, newRt.SubjectID) + assert.Equal(t, tc.rt.SubjectID.String, mapping.StringRepresentation) + } else { + // Nothing should have changed (ignoring commit time) + newRt.CommitTime = tc.rt.CommitTime + assert.Equal(t, tc.rt, newRt) + } + }) + } + }) + } +}