snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 01:08:34 +05:00
commit 9456a4e922
2560 changed files with 120911 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package migratest
import (
"context"
stdSql "database/sql"
"os"
"strconv"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/pop/v6"
"github.com/ory/x/fsx"
"github.com/ory/x/logrusx"
"github.com/ory/x/networkx"
keysetpagination "github.com/ory/x/pagination/keysetpagination_v2"
"github.com/ory/x/popx"
"github.com/ory/x/sqlcon"
"github.com/ory/keto/internal/driver"
"github.com/ory/keto/internal/driver/config"
"github.com/ory/keto/internal/namespace"
"github.com/ory/keto/internal/persistence/sql"
"github.com/ory/keto/internal/persistence/sql/migrations/uuidmapping"
"github.com/ory/keto/internal/relationtuple"
"github.com/ory/keto/internal/x/dbx"
"github.com/ory/keto/ketoapi"
)
func TestMigrations(t *testing.T) {
t.Parallel()
const debugOnDisk = false
for _, db := range dbx.GetDSNs(t) {
t.Run("dsn="+db.Name, func(t *testing.T) {
t.Parallel()
db.MigrateUp, db.MigrateDown = false, false
var conn *pop.Connection
var err error
conn, err = pop.NewConnection(&pop.ConnectionDetails{URL: db.Conn})
require.NoError(t, err)
for range 120 {
require.NoError(t, conn.Open())
if err := dbx.Ping(conn); err == nil {
break
}
time.Sleep(time.Second)
}
require.NoError(t, dbx.Ping(conn))
t.Cleanup(func() { require.NoError(t, conn.Close()) })
namespaces := []*namespace.Namespace{
{ID: 1, Name: "foo"},
{ID: 2, Name: "uuid_test"},
}
nm := config.NewMemoryNamespaceManager(namespaces...)
tm, err := popx.NewMigrationBox(
fsx.Merge(sql.Migrations, networkx.Migrations),
conn, logrusx.New("", "", logrusx.ForceLevel(logrus.DebugLevel)),
popx.WithGoMigrations(uuidmapping.Migrations(nm)),
popx.WithTestdata(t, os.DirFS("./testdata")),
popx.WithoutGoldenDatabase(),
)
require.NoError(t, err)
// cleanup first
require.NoError(t, tm.Down(t.Context(), -1))
t.Run("suite=up", func(t *testing.T) {
if err := tm.Up(t.Context()); err != nil {
t.Log("migrations failed:", err)
logMigrationStatus(t, tm)
t.FailNow()
}
})
reg := driver.NewTestRegistry(t, db, driver.WithNamespaces(namespaces))
p, err := sql.NewPersister(t.Context(), reg, uuid.Must(uuid.FromString("77fdc5e0-2260-49da-8aae-c36ba255d05b")))
require.NoError(t, err)
t.Run("suite=fixtures", func(t *testing.T) {
t.Run("table=legacy namespaces", func(t *testing.T) {
// as they are legacy, we expect them to be actually dropped
assert.ErrorIs(t, sqlcon.HandleError(conn.RawQuery(
"SELECT * FROM keto_namespace",
).Exec()), sqlcon.ErrNoSuchTable())
})
t.Run("table=relationships", func(t *testing.T) {
actualRts, next, err := p.GetRelationTuples(t.Context(), &relationtuple.RelationQuery{Namespace: &namespaces[0].Name})
require.NoError(t, err)
assert.True(t, next.IsLast())
t.Log("actual rts:", actualRts)
expectedRts := []*ketoapi.RelationTuple{
{
Namespace: "foo",
Object: "object",
Relation: "relation",
SubjectID: new("user"),
},
{
Namespace: "foo",
Object: "object",
Relation: "relation",
SubjectSet: &ketoapi.SubjectSet{
Namespace: "foo",
Object: "s_object",
Relation: "s_relation",
},
},
}
// The relationship tuples in the db have a UUID mapping, so
// we need to convert our expectations to that.
expectedUUID, err := reg.Mapper().FromTuple(t.Context(), expectedRts...)
require.NoError(t, err)
assert.ElementsMatch(t, expectedUUID, actualRts)
logMigrationStatus(t, tm)
})
})
t.Run("suite=uuid_migrations", func(t *testing.T) {
t.Run("correct types", func(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), time.Minute)
defer cancel()
require.NoError(t, tm.Down(ctx, -1))
// Migrate up to (including) "drop old non-uuid table"
migrateUpTo(t, tm, "20220513200600000001")
t.Log("status after up migration")
logMigrationStatus(t, tm)
// Assert that relationtuples have UUIDs
tuples, _, err := p.GetRelationTuples(ctx, &relationtuple.RelationQuery{Namespace: &namespaces[1].Name})
require.NoError(t, err)
assert.NotZero(t, tuples[0].Subject.(*relationtuple.SubjectID).ID)
assert.NotZero(t, tuples[0].Object)
// Migrate down to before "migrate-strings-to-uuids"
migrateDownTo(t, tm, "20220513200300000000")
t.Log("status after down migration")
logMigrationStatus(t, tm)
// Assert that relationtuples have strings
var oldRTs []*tuplesBeforeUUID
require.NoError(t, p.Connection(ctx).
Select("subject_id", "object").
//lint:ignore SA1019 backwards compatibility
//nolint:staticcheck
Where("namespace_id = ?", namespaces[1].ID).
All(&oldRTs))
assert.Equalf(t, "user", oldRTs[0].SubjectID.String, "%+v", oldRTs[0])
assert.Equal(t, "object", oldRTs[0].Object)
})
t.Run("paginates", func(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute)
defer cancel()
require.NoError(t, tm.Down(ctx, -1))
// migrate up to before all UUID migrations
migrateUpTo(t, tm, "20220512151000000000")
t.Log("status after up migration")
logMigrationStatus(t, tm)
oldRTs := make([]tuplesBeforeUUID, 2000)
expected := make([]*ketoapi.RelationTuple, len(oldRTs))
for i := range oldRTs {
oldRTs[i] = tuplesBeforeUUID{
NetworkID: p.NetworkID(ctx),
//lint:ignore SA1019 backwards compatibility
//nolint:staticcheck
NamespaceID: namespaces[1].ID,
Object: "object-" + strconv.Itoa(i),
Relation: "pagination-works",
SubjectID: stdSql.NullString{String: "subject-" + strconv.Itoa(i), Valid: true},
CommitTime: time.Now(),
}
expected[i] = &ketoapi.RelationTuple{
Namespace: namespaces[1].Name,
Object: oldRTs[i].Object,
Relation: "pagination-works",
SubjectID: new(oldRTs[i].SubjectID.String),
}
}
require.NoError(t, p.Connection(ctx).Create(oldRTs))
require.NoError(t, tm.Up(ctx))
newRTs := make([]*relationtuple.RelationTuple, 0, len(oldRTs))
for nextPage, _ := keysetpagination.NewPaginator(); !nextPage.IsLast(); {
var rts []*relationtuple.RelationTuple
rts, nextPage, err = p.GetRelationTuples(ctx, &relationtuple.RelationQuery{Relation: new("pagination-works")}, nextPage.ToOptions()...)
require.NoError(t, err)
newRTs = append(newRTs, rts...)
}
assert.Len(t, newRTs, len(oldRTs))
actual, err := reg.Mapper().ToTuple(ctx, newRTs...)
require.NoError(t, err)
assert.ElementsMatch(t, expected, actual)
})
})
t.Run("suite=down", func(t *testing.T) {
if debugOnDisk && db.Name == "sqlite" {
t.SkipNow()
}
require.NoError(t, tm.Down(t.Context(), -1))
})
})
}
}
// migrateUpTo migrates up to the specified version (inclusive)
func migrateUpTo(t *testing.T, tm *popx.MigrationBox, version string) {
statuses, err := tm.Status(context.Background())
require.NoError(t, err)
for i, status := range statuses {
if status.Version == version {
_, err = tm.UpTo(context.Background(), i+1)
require.NoError(t, err)
return
}
}
t.Fatal("could not find ", version)
}
// migrateDownTo migrates down to the specified version (exclusive)
func migrateDownTo(t *testing.T, tm *popx.MigrationBox, version string) {
statuses, err := tm.Status(context.Background())
require.NoError(t, err)
for i, status := range statuses {
if status.Version == version {
require.NoError(t, tm.Down(context.Background(), len(statuses)-i))
return
}
}
t.Fatal("could not find ", version)
}
func logMigrationStatus(t *testing.T, m *popx.MigrationBox) {
t.Helper()
status, err := m.Status(context.Background())
require.NoError(t, err)
t.Logf("status: %+v", status)
}
type tuplesBeforeUUID struct {
ID uuid.UUID `db:"shard_id"`
NetworkID uuid.UUID `db:"nid"`
NamespaceID int32 `db:"namespace_id"`
Object string `db:"object"`
Relation string `db:"relation"`
SubjectID stdSql.NullString `db:"subject_id"`
SubjectSetNamespaceID stdSql.NullInt32 `db:"subject_set_namespace_id"`
SubjectSetObject stdSql.NullString `db:"subject_set_object"`
SubjectSetRelation stdSql.NullString `db:"subject_set_relation"`
CommitTime time.Time `db:"commit_time"`
}
func (tuplesBeforeUUID) TableName(_ context.Context) string {
return "keto_relation_tuples"
}

View File

@@ -0,0 +1 @@
INSERT INTO networks (id, created_at, updated_at) VALUES ('77fdc5e0-2260-49da-8aae-c36ba255d05b', '2015-01-01 00:00:00', '2015-01-01 00:00:00');

View File

@@ -0,0 +1 @@
INSERT INTO keto_namespace (id, schema_version) VALUES (1, 1);

View File

@@ -0,0 +1,14 @@
INSERT INTO keto_relation_tuples (shard_id, nid, namespace_id, object, relation, subject_id, subject_set_namespace_id,
subject_set_object, subject_set_relation, commit_time)
VALUES ('7a9d6df4-3b60-4cae-996e-d15d78b0fc36', '77fdc5e0-2260-49da-8aae-c36ba255d05b', 1, 'object', 'relation', 'user',
NULL, NULL, NULL, '2021-09-30 15:50:52');
INSERT INTO keto_relation_tuples (shard_id, nid, namespace_id, object, relation, subject_id, subject_set_namespace_id,
subject_set_object, subject_set_relation, commit_time)
VALUES ('1457d171-4473-4c77-bd88-0809529c599f', '77fdc5e0-2260-49da-8aae-c36ba255d05b', 1, 'object', 'relation', NULL,
1, 's_object', 's_relation', '2021-09-30 15:50:52');
INSERT INTO keto_relation_tuples (shard_id, nid, namespace_id, object, relation, subject_id, subject_set_namespace_id,
subject_set_object, subject_set_relation, commit_time)
VALUES ('7a9d6df4-3b60-4cae-996e-d15d78b0fc35', '77fdc5e0-2260-49da-8aae-c36ba255d05b', 2, 'object', 'relation', 'user',
NULL, NULL, NULL, '2021-09-30 15:50:52');

View File

@@ -0,0 +1 @@
-- DROP TABLE statement only, nothing to insert

View File

@@ -0,0 +1,5 @@
DROP INDEX {{ identifier .Parameters.tableName }}_user_set_idx;
DROP INDEX {{ identifier .Parameters.tableName }}_object_idx;
DROP TABLE {{ identifier .Parameters.tableName }};

View File

@@ -0,0 +1,5 @@
DROP INDEX {{ identifier .Parameters.tableName }}_user_set_idx ON {{ identifier .Parameters.tableName }};
DROP INDEX {{ identifier .Parameters.tableName }}_object_idx ON {{ identifier .Parameters.tableName }};
DROP TABLE {{ identifier .Parameters.tableName }};

View File

@@ -0,0 +1,14 @@
CREATE TABLE {{ identifier .Parameters.tableName }}
(
shard_id varchar(64),
object varchar(64),
relation varchar(64),
subject varchar(256), /* can be <namespace:object#relation> or <user_id> */
commit_time timestamp,
PRIMARY KEY (shard_id, object, relation, subject, commit_time)
);
CREATE INDEX {{ identifier .Parameters.tableName }}_object_idx ON {{ identifier .Parameters.tableName }} (object);
CREATE INDEX {{ identifier .Parameters.tableName }}_user_set_idx ON {{ identifier .Parameters.tableName }} (object, relation);

View File

@@ -0,0 +1,5 @@
CREATE TABLE keto_namespace
(
id INTEGER PRIMARY KEY,
schema_version INTEGER NOT NULL
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE keto_namespace
(
id INTEGER PRIMARY KEY,
schema_version INTEGER NOT NULL
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE keto_namespace
(
id INTEGER PRIMARY KEY,
schema_version INTEGER NOT NULL
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE keto_namespace
(
id INTEGER PRIMARY KEY,
schema_version INTEGER NOT NULL
);

View File

@@ -0,0 +1 @@
DROP TABLE keto_namespace;

View File

@@ -0,0 +1 @@
DROP TABLE keto_namespace;

View File

@@ -0,0 +1 @@
DROP TABLE keto_namespace;

View File

@@ -0,0 +1 @@
DROP TABLE keto_namespace;

View File

@@ -0,0 +1,25 @@
-- we can not fizz this migration because there is no check constraint support in fizz
-- and it gets cumbersome with sqlite; having a working SQL version is actually way easier
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);

View File

@@ -0,0 +1,25 @@
-- we can not fizz this migration because there is no check constraint support in fizz
-- and it gets cumbersome with sqlite; having a working SQL version is actually way easier
CREATE TABLE keto_relation_tuples
(
shard_id char(36) NOT NULL,
nid char(36) NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);

View File

@@ -0,0 +1,25 @@
-- we can not fizz this migration because there is no check constraint support in fizz
-- and it gets cumbersome with sqlite; having a working SQL version is actually way easier
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);

View File

@@ -0,0 +1,25 @@
-- we can not fizz this migration because there is no check constraint support in fizz
-- and it gets cumbersome with sqlite; having a working SQL version is actually way easier
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples;

View File

@@ -0,0 +1,6 @@
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id) WHERE subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL;

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples;

View File

@@ -0,0 +1,11 @@
-- mysql has no partial indexes so we can only use the full one
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
commit_time);

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples;

View File

@@ -0,0 +1,6 @@
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id) WHERE subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL;

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples;

View File

@@ -0,0 +1,6 @@
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id) WHERE subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL;

View File

@@ -0,0 +1,8 @@
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_set_namespace_id,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,8 @@
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_set_namespace_id,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,8 @@
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_set_namespace_id,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,10 @@
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
commit_time);

View File

@@ -0,0 +1,10 @@
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
commit_time);

View File

@@ -0,0 +1,10 @@
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
commit_time);

View File

@@ -0,0 +1,5 @@
CREATE TABLE "keto_namespace" (
"id" SERIAL NOT NULL,
PRIMARY KEY("id"),
"schema_version" int NOT NULL
);

View File

@@ -0,0 +1 @@
DROP TABLE "keto_namespace";

View File

@@ -0,0 +1,5 @@
CREATE TABLE `keto_namespace` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
PRIMARY KEY(`id`),
`schema_version` INTEGER NOT NULL
) ENGINE=InnoDB;

View File

@@ -0,0 +1 @@
DROP TABLE `keto_namespace`;

View File

@@ -0,0 +1,5 @@
CREATE TABLE "keto_namespace" (
"id" SERIAL NOT NULL,
PRIMARY KEY("id"),
"schema_version" int NOT NULL
);

View File

@@ -0,0 +1 @@
DROP TABLE "keto_namespace";

View File

@@ -0,0 +1,4 @@
CREATE TABLE "keto_namespace" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"schema_version" INTEGER NOT NULL
);

View File

@@ -0,0 +1 @@
DROP TABLE "keto_namespace";

View File

@@ -0,0 +1 @@
ALTER TABLE keto_relation_tuples DROP CONSTRAINT keto_relation_tuples_nid_fk;

View File

@@ -0,0 +1,29 @@
CREATE TABLE keto_relation_tuples_no_fk
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);
INSERT INTO keto_relation_tuples_no_fk (shard_id, nid, namespace_id, object, relation, subject_id, subject_set_namespace_id, subject_set_object, subject_set_relation, commit_time) SELECT * FROM keto_relation_tuples;
DROP TABLE keto_relation_tuples;
ALTER TABLE keto_relation_tuples_no_fk RENAME TO keto_relation_tuples;

View File

@@ -0,0 +1,59 @@
-- These tuples can't be retrieved over any API (and also not inserted by any API) because we always add a `WHERE nid = ?` clause to the queries.
-- This DELETE statement is here to ensure that we don't run into problems where the `nid` column is not properly set. Should not happen if only the official API is used.
DELETE FROM keto_relation_tuples WHERE nid NOT IN (SELECT nid FROM networks);
CREATE TABLE keto_relation_tuples_with_fk
(
shard_id TEXT NOT NULL,
nid TEXT NOT NULL,
namespace_id INTEGER NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INTEGER NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
-- enforce to have exactly one of subject_id or subject_set
CONSTRAINT chk_keto_rt_subject_type CHECK
((subject_id IS NULL AND
subject_set_namespace_id IS NOT NULL AND subject_set_object IS NOT NULL AND subject_set_relation IS NOT NULL)
OR
(subject_id IS NOT NULL AND
subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL))
);
INSERT INTO keto_relation_tuples_with_fk (shard_id, nid, namespace_id, object, relation, subject_id, subject_set_namespace_id, subject_set_object, subject_set_relation, commit_time) SELECT * FROM keto_relation_tuples;
DROP TABLE keto_relation_tuples;
ALTER TABLE keto_relation_tuples_with_fk RENAME TO keto_relation_tuples;
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id) WHERE subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL;
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_set_namespace_id,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid,
namespace_id,
object,
relation,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
commit_time);

View File

@@ -0,0 +1,7 @@
-- These tuples can't be retrieved over any API (and also not inserted by any API) because we always add a `WHERE nid = ?` clause to the queries.
-- This DELETE statement is here to ensure that we don't run into problems where the `nid` column is not properly set. Should not happen if only the official API is used.
DELETE FROM keto_relation_tuples WHERE nid NOT IN (SELECT nid FROM networks);
ALTER TABLE keto_relation_tuples
ADD CONSTRAINT keto_relation_tuples_nid_fk
FOREIGN KEY (nid) REFERENCES networks (id);

View File

@@ -0,0 +1,2 @@
DROP INDEX keto_relation_tuples_reverse_subject_ids_idx;
DROP INDEX keto_relation_tuples_reverse_subject_sets_idx;

View File

@@ -0,0 +1 @@
DROP INDEX keto_relation_tuples_reverse_subject_idx ON keto_relation_tuples;

View File

@@ -0,0 +1,8 @@
CREATE INDEX keto_relation_tuples_reverse_subject_idx ON keto_relation_tuples (nid,
subject_id,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
relation,
namespace_id
);

View File

@@ -0,0 +1,13 @@
CREATE INDEX keto_relation_tuples_reverse_subject_ids_idx ON keto_relation_tuples (nid,
subject_id,
relation,
namespace_id
) WHERE subject_set_namespace_id IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL;
CREATE INDEX keto_relation_tuples_reverse_subject_sets_idx ON keto_relation_tuples (nid,
subject_set_namespace_id,
subject_set_object,
subject_set_relation,
relation,
namespace_id
) WHERE subject_id IS NULL;

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples_uuid;

View File

@@ -0,0 +1,25 @@
CREATE TABLE keto_relation_tuples_uuid
(
shard_id CHAR(36) NOT NULL,
nid CHAR(36) NOT NULL,
namespace VARCHAR(200) NOT NULL,
object CHAR(36) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id CHAR(36) NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object CHAR(36) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id ASC, nid ASC),
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
-- mysql has no partial indexes so we can only use the full one
INDEX keto_relation_tuples_uuid_full_idx (nid, namespace, object, relation, subject_id, subject_set_namespace, subject_set_object, subject_set_relation, commit_time),
INDEX keto_relation_tuples_uuid_reverse_subject_idx (nid, subject_id, subject_set_namespace, subject_set_object, subject_set_relation, relation, namespace),
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);

View File

@@ -0,0 +1,25 @@
CREATE TABLE keto_relation_tuples_uuid
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace VARCHAR(200) NOT NULL,
object UUID NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id UUID NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object UUID NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);
CREATE INDEX keto_relation_tuples_uuid_subject_ids_idx ON keto_relation_tuples_uuid (nid, namespace, object, relation, subject_id) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_subject_sets_idx ON keto_relation_tuples_uuid (nid, namespace, object, relation, subject_set_namespace, subject_set_object, subject_set_relation) WHERE subject_id IS NULL;
CREATE INDEX keto_relation_tuples_uuid_full_idx ON keto_relation_tuples_uuid (nid, namespace, object, relation, subject_id, subject_set_namespace, subject_set_object, subject_set_relation, commit_time);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx ON keto_relation_tuples_uuid (nid, subject_id, relation, namespace) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx ON keto_relation_tuples_uuid (nid, subject_set_namespace, subject_set_object, subject_set_relation, relation, namespace) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,41 @@
CREATE TABLE keto_relation_tuples_uuid
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace VARCHAR(200) NOT NULL,
object UUID NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id UUID NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object UUID NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);
CREATE INDEX keto_relation_tuples_uuid_subject_ids_idx ON keto_relation_tuples_uuid (nid, namespace, object, relation, subject_id) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_subject_sets_idx ON keto_relation_tuples_uuid (nid, namespace, object,
relation,
subject_set_namespace,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;
CREATE INDEX keto_relation_tuples_uuid_full_idx ON keto_relation_tuples_uuid (nid, namespace, object,
relation, subject_id,
subject_set_namespace,
subject_set_object,
subject_set_relation, commit_time
);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx ON keto_relation_tuples_uuid (nid, subject_id, relation, namespace) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx ON keto_relation_tuples_uuid (nid,
subject_set_namespace,
subject_set_object,
subject_set_relation,
relation,
namespace
) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,25 @@
CREATE TABLE keto_relation_tuples_uuid
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace VARCHAR(200) NOT NULL,
object UUID NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id UUID NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object UUID NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
INDEX keto_relation_tuples_uuid_subject_ids_idx (nid, namespace, object, relation, subject_id) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL),
INDEX keto_relation_tuples_uuid_subject_sets_idx (nid, namespace, object, relation, subject_set_namespace, subject_set_object, subject_set_relation) WHERE subject_id IS NULL,
INDEX keto_relation_tuples_uuid_full_idx (nid, namespace, object, relation, subject_id, subject_set_namespace, subject_set_object, subject_set_relation, commit_time),
INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx (nid, subject_id, relation, namespace) WHERE ((subject_set_namespace IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL),
INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx (nid, subject_set_namespace, subject_set_object, subject_set_relation, relation, namespace) WHERE subject_id IS NULL,
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);

View File

@@ -0,0 +1 @@
DROP TABLE keto_uuid_mappings;

View File

@@ -0,0 +1,7 @@
CREATE TABLE keto_uuid_mappings
(
id CHAR(36) NOT NULL,
string_representation TEXT NOT NULL CHECK (string_representation <> ''),
PRIMARY KEY (id)
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE keto_uuid_mappings
(
id UUID NOT NULL PRIMARY KEY,
string_representation TEXT NOT NULL CHECK (string_representation <> '')
);

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT check_string_representation CHECK (string_representation <> '');

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT IF EXISTS check_string_representation;

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT keto_uuid_mappings_chk_1 CHECK (string_representation <> '');

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT keto_uuid_mappings_chk_1;

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT keto_uuid_mappings_string_representation_check CHECK (string_representation <> '');

View File

@@ -0,0 +1 @@
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT IF EXISTS keto_uuid_mappings_string_representation_check;

View File

@@ -0,0 +1,9 @@
--ALTER TABLE keto_uuid_mappings DROP CONSTRAINT IF EXISTS check_string_representation;
ALTER TABLE keto_uuid_mappings RENAME TO keto_uuid_mapping_with_old_check_constraint;
CREATE TABLE keto_uuid_mappings
(
id UUID NOT NULL PRIMARY KEY,
string_representation TEXT NOT NULL
);
INSERT INTO keto_uuid_mappings SELECT * FROM keto_uuid_mapping_with_old_check_constraint;
DROP TABLE keto_uuid_mapping_with_old_check_constraint;

View File

@@ -0,0 +1,25 @@
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INT8 NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INT8 NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
INDEX keto_relation_tuples_subject_ids_idx (nid, namespace_id, object, relation, subject_id) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL),
INDEX keto_relation_tuples_subject_sets_idx (nid, namespace_id, object, relation, subject_set_namespace_id, subject_set_object, subject_set_relation) WHERE subject_id IS NULL,
INDEX keto_relation_tuples_full_idx (nid, namespace_id, object, relation, subject_id, subject_set_namespace_id, subject_set_object, subject_set_relation, commit_time),
INDEX keto_relation_tuples_reverse_subject_ids_idx (nid, subject_id, relation, namespace_id) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL),
INDEX keto_relation_tuples_reverse_subject_sets_idx (nid, subject_set_namespace_id, subject_set_object, subject_set_relation, relation, namespace_id) WHERE subject_id IS NULL,
CONSTRAINT chk_keto_rt_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace_id IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace_id IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);

View File

@@ -0,0 +1,25 @@
CREATE TABLE keto_relation_tuples
(
shard_id CHAR(36) NOT NULL,
nid CHAR(36) NOT NULL,
namespace_id INT8 NOT NULL,
object CHAR(36) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id CHAR(36) NULL,
subject_set_namespace_id INT8 NULL,
subject_set_object CHAR(36) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id ASC, nid ASC),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
-- mysql has no partial indexes so we can only use the full one
INDEX keto_relation_tuples_full_idx (nid, namespace_id, object, relation, subject_id, subject_set_namespace_id, subject_set_object, subject_set_relation, commit_time),
INDEX keto_relation_tuples_reverse_subject_idx (nid, subject_id, subject_set_namespace_id, subject_set_object, subject_set_relation, relation, namespace_id),
CONSTRAINT chk_keto_rt_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace_id IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace_id IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);

View File

@@ -0,0 +1,34 @@
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INT8 NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INT8 NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
CONSTRAINT chk_keto_rt_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace_id IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace_id IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid, namespace_id, object, relation, subject_id) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid, namespace_id, object, relation,
subject_set_namespace_id,
subject_set_object,
subject_set_relation) WHERE subject_id IS NULL;
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid, namespace_id, object, relation, subject_id,
subject_set_namespace_id, subject_set_object,
subject_set_relation, commit_time);
CREATE INDEX keto_relation_tuples_reverse_subject_ids_idx ON keto_relation_tuples (nid, subject_id, relation, namespace_id) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_reverse_subject_sets_idx ON keto_relation_tuples (nid, subject_set_namespace_id,
subject_set_object,
subject_set_relation, relation,
namespace_id) WHERE subject_id IS NULL;

View File

@@ -0,0 +1,36 @@
CREATE TABLE keto_relation_tuples
(
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace_id INT8 NOT NULL,
object VARCHAR(64) NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id VARCHAR(64) NULL,
subject_set_namespace_id INT8 NULL,
subject_set_object VARCHAR(64) NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
CONSTRAINT "primary" PRIMARY KEY (shard_id ASC, nid ASC),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
CONSTRAINT chk_keto_rt_subject_type CHECK (((((subject_id IS NULL) AND (subject_set_namespace_id IS NOT NULL)) AND
(subject_set_object IS NOT NULL)) AND
(subject_set_relation IS NOT NULL)) OR
((((subject_id IS NOT NULL) AND (subject_set_namespace_id IS NULL)) AND
(subject_set_object IS NULL)) AND (subject_set_relation IS NULL)))
);
CREATE INDEX keto_relation_tuples_subject_ids_idx ON keto_relation_tuples (nid ASC, namespace_id ASC, object ASC, relation ASC, subject_id ASC) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_subject_sets_idx ON keto_relation_tuples (nid ASC, namespace_id ASC, object ASC,
relation ASC, subject_set_namespace_id ASC,
subject_set_object ASC, subject_set_relation
ASC) WHERE subject_id IS NULL;
CREATE INDEX keto_relation_tuples_full_idx ON keto_relation_tuples (nid ASC, namespace_id ASC, object ASC, relation ASC,
subject_id ASC, subject_set_namespace_id ASC,
subject_set_object ASC, subject_set_relation ASC,
commit_time ASC);
CREATE INDEX keto_relation_tuples_reverse_subject_ids_idx ON keto_relation_tuples (nid ASC, subject_id ASC, relation ASC, namespace_id ASC) WHERE ((subject_set_namespace_id IS NULL) AND (subject_set_object IS NULL)) AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_reverse_subject_sets_idx ON keto_relation_tuples (nid ASC, subject_set_namespace_id
ASC, subject_set_object ASC,
subject_set_relation ASC, relation
ASC, namespace_id
ASC) WHERE subject_id IS NULL;

View File

@@ -0,0 +1 @@
DROP TABLE keto_relation_tuples;

View File

@@ -0,0 +1 @@
ALTER TABLE keto_relation_tuples RENAME TO keto_relation_tuples_uuid;

View File

@@ -0,0 +1 @@
ALTER TABLE keto_relation_tuples_uuid RENAME TO keto_relation_tuples;

View File

@@ -0,0 +1,7 @@
ALTER TABLE
keto_relation_tuples DROP CONSTRAINT keto_relation_tuples_nid_fk;
ALTER TABLE
keto_relation_tuples
ADD
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id);

View File

@@ -0,0 +1,109 @@
ALTER TABLE
keto_relation_tuples RENAME TO _keto_relation_tuples;
DROP INDEX keto_relation_tuples_uuid_subject_ids_idx;
DROP INDEX keto_relation_tuples_uuid_subject_sets_idx;
DROP INDEX keto_relation_tuples_uuid_full_idx;
DROP INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx;
DROP INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx;
CREATE TABLE keto_relation_tuples (
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace VARCHAR(200) NOT NULL,
object UUID NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id UUID NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object UUID NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_uuid_nid_fk FOREIGN KEY (nid) REFERENCES networks (id),
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (
(
(
(
(subject_id IS NULL)
AND (subject_set_namespace IS NOT NULL)
)
AND (subject_set_object IS NOT NULL)
)
AND (subject_set_relation IS NOT NULL)
)
OR (
(
(
(subject_id IS NOT NULL)
AND (subject_set_namespace IS NULL)
)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL)
)
)
);
CREATE INDEX keto_relation_tuples_uuid_subject_ids_idx ON keto_relation_tuples (nid, namespace, object, relation, subject_id)
WHERE
(
(subject_set_namespace IS NULL)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_subject_sets_idx ON keto_relation_tuples (
nid,
namespace,
object,
relation,
subject_set_namespace,
subject_set_object,
subject_set_relation
)
WHERE
subject_id IS NULL;
CREATE INDEX keto_relation_tuples_uuid_full_idx ON keto_relation_tuples (
nid,
namespace,
object,
relation,
subject_id,
subject_set_namespace,
subject_set_object,
subject_set_relation,
commit_time
);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx ON keto_relation_tuples (nid, subject_id, relation, namespace)
WHERE
(
(subject_set_namespace IS NULL)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx ON keto_relation_tuples (
nid,
subject_set_namespace,
subject_set_object,
subject_set_relation,
relation,
namespace
)
WHERE
subject_id IS NULL;
INSERT INTO
keto_relation_tuples
SELECT
*
FROM
_keto_relation_tuples;
DROP TABLE _keto_relation_tuples;

View File

@@ -0,0 +1,109 @@
ALTER TABLE
keto_relation_tuples RENAME TO _keto_relation_tuples;
DROP INDEX keto_relation_tuples_uuid_subject_ids_idx;
DROP INDEX keto_relation_tuples_uuid_subject_sets_idx;
DROP INDEX keto_relation_tuples_uuid_full_idx;
DROP INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx;
DROP INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx;
CREATE TABLE keto_relation_tuples (
shard_id UUID NOT NULL,
nid UUID NOT NULL,
namespace VARCHAR(200) NOT NULL,
object UUID NOT NULL,
relation VARCHAR(64) NOT NULL,
subject_id UUID NULL,
subject_set_namespace VARCHAR(200) NULL,
subject_set_object UUID NULL,
subject_set_relation VARCHAR(64) NULL,
commit_time TIMESTAMP NOT NULL,
PRIMARY KEY (shard_id, nid),
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE,
CONSTRAINT chk_keto_rt_uuid_subject_type CHECK (
(
(
(
(subject_id IS NULL)
AND (subject_set_namespace IS NOT NULL)
)
AND (subject_set_object IS NOT NULL)
)
AND (subject_set_relation IS NOT NULL)
)
OR (
(
(
(subject_id IS NOT NULL)
AND (subject_set_namespace IS NULL)
)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL)
)
)
);
CREATE INDEX keto_relation_tuples_uuid_subject_ids_idx ON keto_relation_tuples (nid, namespace, object, relation, subject_id)
WHERE
(
(subject_set_namespace IS NULL)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_subject_sets_idx ON keto_relation_tuples (
nid,
namespace,
object,
relation,
subject_set_namespace,
subject_set_object,
subject_set_relation
)
WHERE
subject_id IS NULL;
CREATE INDEX keto_relation_tuples_uuid_full_idx ON keto_relation_tuples (
nid,
namespace,
object,
relation,
subject_id,
subject_set_namespace,
subject_set_object,
subject_set_relation,
commit_time
);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_ids_idx ON keto_relation_tuples (nid, subject_id, relation, namespace)
WHERE
(
(subject_set_namespace IS NULL)
AND (subject_set_object IS NULL)
)
AND (subject_set_relation IS NULL);
CREATE INDEX keto_relation_tuples_uuid_reverse_subject_sets_idx ON keto_relation_tuples (
nid,
subject_set_namespace,
subject_set_object,
subject_set_relation,
relation,
namespace
)
WHERE
subject_id IS NULL;
INSERT INTO
keto_relation_tuples
SELECT
*
FROM
_keto_relation_tuples;
DROP TABLE _keto_relation_tuples;

View File

@@ -0,0 +1,4 @@
ALTER TABLE
keto_relation_tuples DROP CONSTRAINT keto_relation_tuples_uuid_nid_fk,
ADD
CONSTRAINT keto_relation_tuples_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE;

View File

@@ -0,0 +1,427 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package uuidmapping
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/gofrs/uuid"
"github.com/jmoiron/sqlx"
"github.com/ory/pop/v6"
"github.com/ory/x/popx"
"github.com/ory/x/sqlcon"
"github.com/pkg/errors"
"github.com/ory/keto/internal/namespace"
)
//lint:file-ignore SA1019 as we migrate legacy stuff
// We copy the definitions of OldRelationTuple and UUIDMapping here so that the
// migration will always work on the same definitions.
type (
OldRelationTuple struct {
// An ID field is required to make pop happy. The actual ID is a
// composite primary key.
ID uuid.UUID `db:"shard_id"`
NetworkID uuid.UUID `db:"nid"`
NamespaceID int32 `db:"namespace_id"`
Object string `db:"object"`
Relation string `db:"relation"`
SubjectID sql.NullString `db:"subject_id"`
SubjectSetNamespaceID sql.NullInt32 `db:"subject_set_namespace_id"`
SubjectSetObject sql.NullString `db:"subject_set_object"`
SubjectSetRelation sql.NullString `db:"subject_set_relation"`
CommitTime time.Time `db:"commit_time"`
}
NewRelationTuple struct {
ID uuid.UUID `db:"shard_id"`
NetworkID uuid.UUID `db:"nid"`
Namespace string `db:"namespace"`
Object uuid.UUID `db:"object"`
Relation string `db:"relation"`
SubjectID uuid.NullUUID `db:"subject_id"`
SubjectSetNamespace sql.NullString `db:"subject_set_namespace"`
SubjectSetObject uuid.NullUUID `db:"subject_set_object"`
SubjectSetRelation sql.NullString `db:"subject_set_relation"`
CommitTime time.Time `db:"commit_time"`
}
UUIDMapping struct {
ID uuid.UUID `db:"id"`
StringRepresentation string `db:"string_representation"`
}
UUIDMappings []*UUIDMapping
ColumnProvider interface {
dbFields() []any
}
ColumnNameAble interface {
columns() []string
}
)
const (
RelationTupleTableName = "keto_relation_tuples"
RelationTupleUUIDTableName = "keto_relation_tuples_uuid"
UUIDMappingTableName = "keto_uuid_mappings"
MigrationVersion = "20220513200500000000"
)
func (OldRelationTuple) TableName() string { return RelationTupleTableName }
func (NewRelationTuple) TableName() string { return RelationTupleUUIDTableName }
func (rt *OldRelationTuple) dbFields() []any {
return []any{
rt.ID, rt.NetworkID,
rt.NamespaceID, rt.Object, rt.Relation,
rt.SubjectID,
rt.SubjectSetNamespaceID, rt.SubjectSetObject, rt.SubjectSetRelation,
rt.CommitTime,
}
}
func (OldRelationTuple) columns() []string {
return []string{
"shard_id", "nid",
"namespace_id", "object", "relation",
"subject_id",
"subject_set_namespace_id", "subject_set_object", "subject_set_relation",
"commit_time",
}
}
func (rt *NewRelationTuple) dbFields() []any {
return []any{
rt.ID, rt.NetworkID,
rt.Namespace, rt.Object, rt.Relation,
rt.SubjectID,
rt.SubjectSetNamespace, rt.SubjectSetObject, rt.SubjectSetRelation,
rt.CommitTime,
}
}
func (NewRelationTuple) columns() []string {
return []string{
"shard_id", "nid",
"namespace", "object", "relation",
"subject_id",
"subject_set_namespace", "subject_set_object", "subject_set_relation",
"commit_time",
}
}
func (UUIDMappings) TableName() string { return UUIDMappingTableName }
func (UUIDMapping) TableName() string { return UUIDMappingTableName }
func (m *UUIDMapping) dbFields() []any { return []any{m.ID, m.StringRepresentation} }
func (UUIDMapping) columns() []string { return []string{"id", "string_representation"} }
func (rt *OldRelationTuple) ToUUID(s string) uuid.UUID {
return uuid.NewV5(rt.NetworkID, s)
}
func namespaceIDtoName(n namespace.Manager, id int32) (string, error) {
//nolint:staticcheck
ns, err := n.GetNamespaceByConfigID(context.Background(), id)
if err != nil {
return "", err
}
return ns.Name, nil
}
func (rt *OldRelationTuple) ToNew(n namespace.Manager) (newRT *NewRelationTuple, objectMapping *UUIDMapping, subjectMapping *UUIDMapping, err error) {
newRT = &NewRelationTuple{
ID: rt.ID,
NetworkID: rt.NetworkID,
Object: uuid.NewV5(rt.NetworkID, rt.Object),
Relation: rt.Relation,
SubjectID: uuid.NullUUID{
Valid: rt.SubjectID.Valid,
UUID: uuid.NewV5(rt.NetworkID, rt.SubjectID.String),
},
SubjectSetObject: uuid.NullUUID{
Valid: rt.SubjectSetObject.Valid,
UUID: uuid.NewV5(rt.NetworkID, rt.SubjectSetObject.String),
},
SubjectSetRelation: rt.SubjectSetRelation,
CommitTime: rt.CommitTime,
}
if newRT.Namespace, err = namespaceIDtoName(n, rt.NamespaceID); err != nil {
return
}
if rt.SubjectSetNamespaceID.Valid {
newRT.SubjectSetNamespace.Valid = true
if newRT.SubjectSetNamespace.String, err = namespaceIDtoName(n, rt.SubjectSetNamespaceID.Int32); err != nil {
return
}
}
objectMapping = &UUIDMapping{
ID: newRT.Object,
StringRepresentation: rt.Object,
}
switch {
case rt.SubjectID.Valid:
subjectMapping = &UUIDMapping{
ID: newRT.SubjectID.UUID,
StringRepresentation: rt.SubjectID.String,
}
case rt.SubjectSetObject.Valid:
subjectMapping = &UUIDMapping{
ID: newRT.SubjectSetObject.UUID,
StringRepresentation: rt.SubjectSetObject.String,
}
}
return
}
var (
name = "migrate-strings-to-uuids"
Migrations = func(namespaces namespace.Manager) popx.Migrations {
return popx.Migrations{
// The "up" migration will add the UUID mappings to the database and
// replace the strings with UUIDs.
{
Version: MigrationVersion,
Name: name,
Path: name,
Direction: "up",
DBType: "all",
Type: "go",
Runner: func(_ popx.Migration, conn *pop.Connection) error {
var (
relationTuples []OldRelationTuple
err error
lastID uuid.UUID
)
for {
relationTuples, err = GetRelationTuples[OldRelationTuple](conn, lastID)
if err != nil {
return fmt.Errorf("could not get old relationships: %w", err)
}
if len(relationTuples) == 0 {
break
}
lastID = relationTuples[len(relationTuples)-1].ID
mappings := make([]*UUIDMapping, len(relationTuples)*2)
newTuples := make([]*NewRelationTuple, len(relationTuples))
for i := range relationTuples {
newTuples[i], mappings[i*2], mappings[i*2+1], err = relationTuples[i].ToNew(namespaces)
if err != nil {
return errors.WithStack(err)
}
}
if err := BatchWriteMappings(conn, mappings); err != nil {
return fmt.Errorf("could not write mappings: %w", err)
}
if err := BatchInsertTuples(conn, newTuples); err != nil {
return fmt.Errorf("could not insert new tuples: %w", err)
}
}
return nil
},
},
// The "down" migration will replace all UUIDs with strings from the
// mapping table.
{
Version: MigrationVersion,
Name: name,
Path: name,
Direction: "down",
DBType: "all",
Type: "go",
Runner: func(_ popx.Migration, conn *pop.Connection) error {
var (
relationTuples []NewRelationTuple
err error
lastID uuid.UUID
)
for {
relationTuples, err = GetRelationTuples[NewRelationTuple](conn, lastID)
if err != nil {
return fmt.Errorf("could not get new relationships: %w", err)
}
if len(relationTuples) == 0 {
break
}
lastID = relationTuples[len(relationTuples)-1].ID
mappings := make(map[uuid.UUID][]*string, len(relationTuples)*2)
oldTuples := make([]*OldRelationTuple, len(relationTuples))
ctx := context.Background()
for i, rt := range relationTuples {
ot := &OldRelationTuple{
ID: rt.ID,
NetworkID: rt.NetworkID,
Relation: rt.Relation,
SubjectID: sql.NullString{
Valid: rt.SubjectID.Valid, // Value will be set below
},
SubjectSetObject: sql.NullString{
Valid: rt.SubjectSetObject.Valid, // Value will be set below
},
SubjectSetRelation: rt.SubjectSetRelation,
CommitTime: rt.CommitTime,
}
namespace, err := namespaces.GetNamespaceByName(ctx, rt.Namespace)
if err != nil {
return fmt.Errorf("could not get namespace: %w", err)
}
ot.NamespaceID = namespace.ID //nolint:staticcheck
if rt.SubjectSetNamespace.Valid {
subjectSetNamespace, err := namespaces.GetNamespaceByName(ctx, rt.SubjectSetNamespace.String)
if err != nil {
return fmt.Errorf("could not get subject namespace: %w", err)
}
//nolint:staticcheck
if err = ot.SubjectSetNamespaceID.Scan(subjectSetNamespace.ID); err != nil {
return err
}
}
mappings[rt.Object] = append(mappings[rt.Object], &ot.Object)
switch {
case rt.SubjectID.Valid:
mappings[rt.SubjectID.UUID] = append(mappings[rt.SubjectID.UUID], &ot.SubjectID.String)
case rt.SubjectSetObject.Valid:
mappings[rt.SubjectSetObject.UUID] = append(mappings[rt.SubjectSetObject.UUID], &ot.SubjectSetObject.String)
}
oldTuples[i] = ot
}
if err := BatchReplaceUUIDs(conn, mappings); err != nil {
return fmt.Errorf("could not replace UUIDs: %w", err)
}
if err := BatchInsertTuples(conn, oldTuples); err != nil {
return fmt.Errorf("could not insert old tuples: %w", err)
}
}
return nil
},
},
}
}
)
func ConstructArgs[T ColumnProvider](nCols int, items []T) (string, []interface{}) {
placeholderRow := "(" + strings.Repeat("?,", nCols-1) + "?)"
q := &strings.Builder{}
q.Grow(len(items) * (len(placeholderRow) + 1))
args := make([]interface{}, 0, len(items)*nCols)
q.WriteString(placeholderRow)
args = append(args, items[0].dbFields()...)
for _, item := range items[1:] {
q.WriteRune(',')
q.WriteString(placeholderRow)
args = append(args, item.dbFields()...)
}
return q.String(), args
}
func GetRelationTuples[RT interface {
ColumnNameAble
pop.TableNameAble
}](conn *pop.Connection, lastID uuid.UUID) (
res []RT, err error,
) {
const pageSize = 500
var rt RT
// Bind-placeholder syntax is a property of the driver, not the dialect
// name: sqlx maps drivers (e.g. "pgx") to $-placeholders. Use DefaultDriver
// so dialects that share a driver but report a distinct name — YugabyteDB,
// which uses "pgx" like postgres — get the correct placeholders.
q := sqlx.Rebind(sqlx.BindType(conn.Dialect.DefaultDriver()), "SELECT * FROM %s WHERE shard_id > ? ORDER BY shard_id LIMIT ?")
err = conn.Store.Select(
&res, fmt.Sprintf(q, rt.TableName()), lastID, pageSize)
if err != nil {
return nil, sqlcon.HandleError(err)
}
return res, nil
}
func BatchWriteMappings(conn *pop.Connection, mappings []*UUIDMapping) (err error) {
if len(mappings) == 0 {
// Nothing to do.
return nil
}
placeholders, args := ConstructArgs(2, mappings)
// We need to write manual SQL here because the INSERT should not fail if
// the UUID already exists, but we still want to return an error if anything
// else goes wrong.
var query string
switch d := conn.Dialect.Name(); d {
case "mysql":
query = `INSERT IGNORE INTO keto_uuid_mappings (id, string_representation) VALUES ` + placeholders
default:
query = `
INSERT INTO keto_uuid_mappings (id, string_representation)
VALUES ` + placeholders + `
ON CONFLICT (id) DO NOTHING`
}
if err = sqlcon.HandleError(conn.RawQuery(query, args...).Exec()); err != nil {
return err
}
return nil
}
func BatchReplaceUUIDs(conn *pop.Connection, uuidToTargets map[uuid.UUID][]*string) error {
if len(uuidToTargets) == 0 {
return nil
}
ids := make([]uuid.UUID, 0, len(uuidToTargets))
for id := range uuidToTargets {
ids = append(ids, id)
}
mappings := make([]UUIDMapping, 0, len(ids))
query := conn.Where("id in (?)", ids)
if err := sqlcon.HandleError(query.All(&mappings)); err != nil {
return err
}
// Write the representation to the correct pointer(s).
for _, m := range mappings {
for _, target := range uuidToTargets[m.ID] {
*target = m.StringRepresentation
}
}
return nil
}
func BatchInsertTuples[RT interface {
pop.TableNameAble
ColumnProvider
ColumnNameAble
}](conn *pop.Connection, rts []RT) error {
if len(rts) == 0 {
return nil
}
cols := rts[0].columns()
placeholders, args := ConstructArgs(len(cols), rts)
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", rts[0].TableName(), strings.Join(cols, ", "), placeholders)
return sqlcon.HandleError(conn.RawQuery(query, args...).Exec())
}

View File

@@ -0,0 +1,49 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package uuidmapping_test
import (
"strings"
"testing"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/ory/keto/internal/persistence/sql/migrations/uuidmapping"
)
func TestConstructArgs(t *testing.T) {
createItems := func(n int) []uuidmapping.ColumnProvider {
items := make([]uuidmapping.ColumnProvider, n)
for i := range items {
items[i] = &uuidmapping.UUIDMapping{
ID: uuid.Must(uuid.NewV4()),
StringRepresentation: "foo",
}
}
return items
}
for _, tc := range []struct {
desc string
items []uuidmapping.ColumnProvider
nCols int
}{
{
desc: "only one item",
items: createItems(1),
nCols: 2,
},
{
desc: "multiple items",
items: createItems(10),
nCols: 2,
},
} {
t.Run(tc.desc, func(t *testing.T) {
placeholders, args := uuidmapping.ConstructArgs(tc.nCols, tc.items)
assert.Len(t, args, tc.nCols*len(tc.items))
assert.Len(t, args, strings.Count(placeholders, "?"))
})
}
}