Files
upstream-mirror 9456a4e922
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
snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:08:34 +05:00

150 lines
4.6 KiB
Go

// 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
}