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
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:
62
internal/persistence/sql/full_test.go
Normal file
62
internal/persistence/sql/full_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sql_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/x/networkx"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/keto/internal/driver"
|
||||
"github.com/ory/keto/internal/persistence"
|
||||
"github.com/ory/keto/internal/persistence/sql"
|
||||
relationtupletest "github.com/ory/keto/internal/relationtuple/test"
|
||||
"github.com/ory/keto/internal/x/dbx"
|
||||
)
|
||||
|
||||
func TestPersister(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setup := func(t *testing.T, dsn *dbx.DsnT) (p persistence.Persister, r *driver.RegistryDefault, hook *test.Hook) {
|
||||
r = driver.NewTestRegistry(t, dsn)
|
||||
p = r.Persister()
|
||||
require.NotNil(t, p)
|
||||
require.NoError(t, r.MigrateUp(context.Background()))
|
||||
return
|
||||
}
|
||||
|
||||
for _, dsn := range dbx.GetDSNs(t) {
|
||||
t.Run(fmt.Sprintf("dsn=%s", dsn.Name), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("relationtuple.ManagerTest", func(t *testing.T) {
|
||||
p, _, _ := setup(t, dsn)
|
||||
|
||||
relationtupletest.ManagerTest(t, p)
|
||||
})
|
||||
|
||||
t.Run("relationtuple.IsolationTest", func(t *testing.T) {
|
||||
p0, r, _ := setup(t, dsn)
|
||||
n1 := networkx.NewNetwork()
|
||||
conn, err := r.PopConnection(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, conn.Create(n1))
|
||||
p1, err := sql.NewPersister(context.Background(), r, n1.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// same registry, but different persisters only differing in the network ID
|
||||
relationtupletest.IsolationTest(t, p0, p1)
|
||||
})
|
||||
|
||||
t.Run("relationtuple.UUIDMappingManagerTest", func(t *testing.T) {
|
||||
p, _, _ := setup(t, dsn)
|
||||
relationtupletest.MappingManagerTest(t, p)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
275
internal/persistence/sql/migrations/migratest/migration_test.go
Normal file
275
internal/persistence/sql/migrations/migratest/migration_test.go
Normal 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"
|
||||
}
|
||||
1
internal/persistence/sql/migrations/migratest/testdata/20150100000001_testdata.sql
vendored
Normal file
1
internal/persistence/sql/migrations/migratest/testdata/20150100000001_testdata.sql
vendored
Normal 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');
|
||||
1
internal/persistence/sql/migrations/migratest/testdata/20201110175414_testdata.sql
vendored
Normal file
1
internal/persistence/sql/migrations/migratest/testdata/20201110175414_testdata.sql
vendored
Normal file
@@ -0,0 +1 @@
|
||||
INSERT INTO keto_namespace (id, schema_version) VALUES (1, 1);
|
||||
14
internal/persistence/sql/migrations/migratest/testdata/20210623162417_testdata.sql
vendored
Normal file
14
internal/persistence/sql/migrations/migratest/testdata/20210623162417_testdata.sql
vendored
Normal 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');
|
||||
1
internal/persistence/sql/migrations/migratest/testdata/20210914134624_testdata.sql
vendored
Normal file
1
internal/persistence/sql/migrations/migratest/testdata/20210914134624_testdata.sql
vendored
Normal file
@@ -0,0 +1 @@
|
||||
-- DROP TABLE statement only, nothing to insert
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP INDEX {{ identifier .Parameters.tableName }}_user_set_idx;
|
||||
|
||||
DROP INDEX {{ identifier .Parameters.tableName }}_object_idx;
|
||||
|
||||
DROP TABLE {{ identifier .Parameters.tableName }};
|
||||
@@ -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 }};
|
||||
@@ -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);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE keto_namespace
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
schema_version INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE keto_namespace
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
schema_version INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE keto_namespace
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
schema_version INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE keto_namespace
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
schema_version INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_namespace;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_namespace;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_namespace;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_namespace;
|
||||
@@ -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))
|
||||
);
|
||||
@@ -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))
|
||||
);
|
||||
@@ -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))
|
||||
);
|
||||
@@ -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))
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples;
|
||||
@@ -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);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE "keto_namespace" (
|
||||
"id" SERIAL NOT NULL,
|
||||
PRIMARY KEY("id"),
|
||||
"schema_version" int NOT NULL
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "keto_namespace";
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE `keto_namespace` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY(`id`),
|
||||
`schema_version` INTEGER NOT NULL
|
||||
) ENGINE=InnoDB;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE `keto_namespace`;
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE "keto_namespace" (
|
||||
"id" SERIAL NOT NULL,
|
||||
PRIMARY KEY("id"),
|
||||
"schema_version" int NOT NULL
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "keto_namespace";
|
||||
@@ -0,0 +1,4 @@
|
||||
CREATE TABLE "keto_namespace" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"schema_version" INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "keto_namespace";
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_relation_tuples DROP CONSTRAINT keto_relation_tuples_nid_fk;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP INDEX keto_relation_tuples_reverse_subject_ids_idx;
|
||||
DROP INDEX keto_relation_tuples_reverse_subject_sets_idx;
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX keto_relation_tuples_reverse_subject_idx ON keto_relation_tuples;
|
||||
@@ -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
|
||||
);
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples_uuid;
|
||||
@@ -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)))
|
||||
);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)))
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_uuid_mappings;
|
||||
@@ -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)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE keto_uuid_mappings
|
||||
(
|
||||
id UUID NOT NULL PRIMARY KEY,
|
||||
string_representation TEXT NOT NULL CHECK (string_representation <> '')
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT check_string_representation CHECK (string_representation <> '');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT IF EXISTS check_string_representation;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT keto_uuid_mappings_chk_1 CHECK (string_representation <> '');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT keto_uuid_mappings_chk_1;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings ADD CONSTRAINT keto_uuid_mappings_string_representation_check CHECK (string_representation <> '');
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_uuid_mappings DROP CONSTRAINT IF EXISTS keto_uuid_mappings_string_representation_check;
|
||||
@@ -0,0 +1 @@
|
||||
-- do nothing.
|
||||
@@ -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;
|
||||
@@ -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)))
|
||||
);
|
||||
@@ -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)))
|
||||
);
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE keto_relation_tuples;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_relation_tuples RENAME TO keto_relation_tuples_uuid;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE keto_relation_tuples_uuid RENAME TO keto_relation_tuples;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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, "?"))
|
||||
})
|
||||
}
|
||||
}
|
||||
149
internal/persistence/sql/persister.go
Normal file
149
internal/persistence/sql/persister.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/ory/pop/v6"
|
||||
"github.com/ory/x/contextx"
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/otelx"
|
||||
"github.com/ory/x/popx"
|
||||
|
||||
keysetpagination "github.com/ory/x/pagination/keysetpagination_v2"
|
||||
"github.com/ory/x/pagination/paginationplanner"
|
||||
|
||||
"github.com/ory/keto/internal/driver/config"
|
||||
"github.com/ory/keto/internal/persistence"
|
||||
)
|
||||
|
||||
type (
|
||||
Persister struct {
|
||||
conn *pop.Connection
|
||||
d dependencies
|
||||
nid uuid.UUID
|
||||
|
||||
planner *paginationplanner.PaginationPlanner
|
||||
}
|
||||
dependencies interface {
|
||||
logrusx.Provider
|
||||
otelx.Provider
|
||||
contextx.Provider
|
||||
config.Provider
|
||||
|
||||
PopConnection(ctx context.Context) (*pop.Connection, error)
|
||||
MapperNamespace(ctx context.Context) uuid.UUID
|
||||
NewShardID() uuid.UUID
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed migrations/sql/*.sql
|
||||
Migrations embed.FS
|
||||
|
||||
_ persistence.Persister = &Persister{}
|
||||
)
|
||||
|
||||
// Pagination Planner for relation_tuple table
|
||||
var (
|
||||
t = paginationplanner.NewTable()
|
||||
TupleColNamespace = t.NewColumn("namespace")
|
||||
TupleColObject = t.NewColumn("object")
|
||||
TupleColRel = t.NewColumn("relation")
|
||||
TupleColSubjectId = t.NewColumn("subject_id")
|
||||
TupleColSubjectSetNamespace = t.NewColumn("subject_set_namespace")
|
||||
TupleColSubjectSetObject = t.NewColumn("subject_set_object")
|
||||
TupleColSubjectSetRel = t.NewColumn("subject_set_relation")
|
||||
)
|
||||
|
||||
var (
|
||||
fullIdxPlan = paginationplanner.PaginationPlan{
|
||||
DefaultPageToken: keysetpagination.NewPageToken([]keysetpagination.Column{
|
||||
{Name: TupleColNamespace.Name(), Order: keysetpagination.OrderAscending, Value: ""},
|
||||
{Name: TupleColObject.Name(), Order: keysetpagination.OrderAscending, Value: uuid.Nil},
|
||||
{Name: TupleColRel.Name(), Order: keysetpagination.OrderAscending, Value: ""},
|
||||
{Name: TupleColSubjectId.Name(), Order: keysetpagination.OrderAscending, Nullable: true},
|
||||
{Name: TupleColSubjectSetNamespace.Name(), Order: keysetpagination.OrderAscending, Nullable: true},
|
||||
{Name: TupleColSubjectSetObject.Name(), Order: keysetpagination.OrderAscending, Nullable: true},
|
||||
{Name: TupleColSubjectSetRel.Name(), Order: keysetpagination.OrderAscending, Nullable: true},
|
||||
{Name: "commit_time", Order: keysetpagination.OrderAscending, Value: "1970-01-01"},
|
||||
{Name: "shard_id", Order: keysetpagination.OrderAscending, Value: uuid.Nil},
|
||||
}...),
|
||||
ApplicableQueries: [][]paginationplanner.Column{
|
||||
{},
|
||||
{TupleColNamespace},
|
||||
{TupleColNamespace, TupleColObject},
|
||||
{TupleColNamespace, TupleColObject, TupleColRel},
|
||||
},
|
||||
}
|
||||
|
||||
fallbackPlan = paginationplanner.PaginationPlan{
|
||||
DefaultPageToken: keysetpagination.NewPageToken([]keysetpagination.Column{
|
||||
{Name: "shard_id", Order: keysetpagination.OrderAscending, Value: uuid.Nil},
|
||||
}...),
|
||||
}
|
||||
)
|
||||
|
||||
func NewPersister(ctx context.Context, reg dependencies, nid uuid.UUID) (*Persister, error) {
|
||||
conn, err := reg.PopConnection(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planner, err := paginationplanner.NewPaginationPlanner(fallbackPlan, []paginationplanner.PaginationPlan{fullIdxPlan})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &Persister{
|
||||
d: reg,
|
||||
nid: nid,
|
||||
conn: conn,
|
||||
planner: planner,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Persister) Connection(ctx context.Context) *pop.Connection {
|
||||
return popx.GetConnection(ctx, p.conn.WithContext(ctx))
|
||||
}
|
||||
|
||||
func (p *Persister) queryWithNetwork(ctx context.Context) *pop.Query {
|
||||
return p.Connection(ctx).Where("nid = ?", p.NetworkID(ctx))
|
||||
}
|
||||
|
||||
func (p *Persister) Transaction(ctx context.Context, f func(ctx context.Context) error) error {
|
||||
return popx.Transaction(ctx, p.conn.WithContext(ctx), func(ctx context.Context, _ *pop.Connection) error { return f(ctx) })
|
||||
}
|
||||
|
||||
func (p *Persister) WithLatestSnapshot(ctx context.Context, f func(ctx context.Context) error) error {
|
||||
conn := p.conn.WithContext(ctx)
|
||||
|
||||
// Reuse an existing transaction rather than nesting.
|
||||
if popx.GetConnection(ctx, conn).TX != nil {
|
||||
return f(ctx)
|
||||
}
|
||||
|
||||
// REPEATABLE READ pins a consistent snapshot so all queries in the check see
|
||||
// the same database state. ReadOnly enforces read-only at the DB level.
|
||||
// CockroachDB promotes that level to SERIALIZABLE; SQLite ignores
|
||||
// the isolation level entirely.
|
||||
return popx.TransactionWithOptions(ctx, conn, &sql.TxOptions{
|
||||
Isolation: sql.LevelRepeatableRead,
|
||||
ReadOnly: true,
|
||||
}, func(ctx context.Context, _ *pop.Connection) error {
|
||||
return f(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Persister) NetworkID(ctx context.Context) uuid.UUID {
|
||||
return p.d.Contextualizer().Network(ctx, p.nid)
|
||||
}
|
||||
|
||||
func (p *Persister) SetNetwork(nid uuid.UUID) {
|
||||
p.nid = nid
|
||||
}
|
||||
128
internal/persistence/sql/query_test.go
Normal file
128
internal/persistence/sql/query_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/ory/x/uuidx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/keto/internal/relationtuple"
|
||||
)
|
||||
|
||||
func TestBuildDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
nid := uuidx.NewV4()
|
||||
|
||||
q, args, err := buildDelete(nid, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, q)
|
||||
assert.Empty(t, args)
|
||||
|
||||
obj1, obj2, sub1, obj3 := uuidx.NewV4(), uuidx.NewV4(), uuidx.NewV4(), uuidx.NewV4()
|
||||
|
||||
q, args, err = buildDelete(nid, []*relationtuple.RelationTuple{
|
||||
{
|
||||
Namespace: "ns1",
|
||||
Object: obj1,
|
||||
Relation: "rel1",
|
||||
Subject: &relationtuple.SubjectID{
|
||||
ID: sub1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Namespace: "ns2",
|
||||
Object: obj2,
|
||||
Relation: "rel2",
|
||||
Subject: &relationtuple.SubjectSet{
|
||||
Namespace: "ns3",
|
||||
Object: obj3,
|
||||
Relation: "rel3",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// parentheses are important here
|
||||
assert.Equal(t, q, "DELETE FROM keto_relation_tuples WHERE ((namespace = ? AND object = ? AND relation = ? AND subject_id = ? AND subject_set_namespace IS NULL AND subject_set_object IS NULL AND subject_set_relation IS NULL) OR (namespace = ? AND object = ? AND relation = ? AND subject_id IS NULL AND subject_set_namespace = ? AND subject_set_object = ? AND subject_set_relation = ?)) AND nid = ?")
|
||||
assert.Equal(t, []any{"ns1", obj1, "rel1", sub1, "ns2", obj2, "rel2", "ns3", obj3, "rel3", nid}, args)
|
||||
}
|
||||
|
||||
func TestBuildInsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
nid := uuidx.NewV4()
|
||||
|
||||
q, args, err := buildInsert(uuidx.NewV4, time.Now(), nid, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, q)
|
||||
assert.Empty(t, args)
|
||||
|
||||
obj1, obj2, sub1, obj3 := uuidx.NewV4(), uuidx.NewV4(), uuidx.NewV4(), uuidx.NewV4()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
q, args, err = buildInsert(uuidx.NewV4, now, nid, []*relationtuple.RelationTuple{
|
||||
{
|
||||
Namespace: "ns1",
|
||||
Object: obj1,
|
||||
Relation: "rel1",
|
||||
Subject: &relationtuple.SubjectID{
|
||||
ID: sub1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Namespace: "ns2",
|
||||
Object: obj2,
|
||||
Relation: "rel2",
|
||||
Subject: &relationtuple.SubjectSet{
|
||||
Namespace: "ns3",
|
||||
Object: obj3,
|
||||
Relation: "rel3",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, q, "INSERT INTO keto_relation_tuples (shard_id, nid, namespace, object, relation, subject_id, subject_set_namespace, subject_set_object, subject_set_relation, commit_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
assert.Equal(t, []any{
|
||||
args[0], // this is kind of cheating but we generate the shard id in the buildInsert function
|
||||
nid,
|
||||
"ns1",
|
||||
obj1,
|
||||
"rel1",
|
||||
uuid.NullUUID{UUID: sub1, Valid: true},
|
||||
sql.NullString{}, uuid.NullUUID{}, sql.NullString{},
|
||||
now.UTC(),
|
||||
|
||||
args[10], // again, cheating
|
||||
nid,
|
||||
"ns2",
|
||||
obj2,
|
||||
"rel2",
|
||||
uuid.NullUUID{},
|
||||
sql.NullString{String: "ns3", Valid: true}, uuid.NullUUID{UUID: obj3, Valid: true}, sql.NullString{String: "rel3", Valid: true},
|
||||
now.UTC(),
|
||||
}, args)
|
||||
}
|
||||
|
||||
func TestBuildInsertUUIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
foo, bar, baz := uuidx.NewV4(), uuidx.NewV4(), uuidx.NewV4()
|
||||
uuids := []UUIDMapping{
|
||||
{foo, "foo"},
|
||||
{bar, "bar"},
|
||||
{baz, "baz"},
|
||||
}
|
||||
|
||||
q, args := buildInsertUUIDs(uuids, "mysql")
|
||||
assert.Equal(t, "INSERT IGNORE INTO keto_uuid_mappings (id, string_representation) VALUES (?,?),(?,?),(?,?)", q)
|
||||
assert.Equal(t, []any{foo, "foo", bar, "bar", baz, "baz"}, args)
|
||||
|
||||
q, args = buildInsertUUIDs(uuids, "anything else")
|
||||
assert.Equal(t, "INSERT INTO keto_uuid_mappings (id, string_representation) VALUES (?,?),(?,?),(?,?) ON CONFLICT (id) DO NOTHING", q)
|
||||
assert.Equal(t, []any{foo, "foo", bar, "bar", baz, "baz"}, args)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user