feat(database): support for postgres (#3998)

* beginning with postgres statements

* try pgx

* use pgx

* database

* init works for postgres

* arrays working

* init for cockroach

* init

* start tests

* tests

* TESTS

* ch

* ch

* chore: use go 1.18

* read stmts

* fix typo

* tests

* connection string

* add missing error handler

* cleanup

* start all apis

* go mod tidy

* old update

* switch back to minute

* on conflict

* replace string slice with `database.StringArray` in db models

* fix tests and start

* update go version in dockerfile

* setup go

* clean up

* remove notification migration

* update

* docs: add deploy guide for postgres

* fix: revert sonyflake

* use `database.StringArray` for daos

* use `database.StringArray` every where

* new tables

* index naming,
metadata primary key,
project grant role key type

* docs(postgres): change to beta

* chore: correct compose

* fix(defaults): add empty postgres config

* refactor: remove unused code

* docs: add postgres to self hosted

* fix broken link

* so?

* change title

* add mdx to link

* fix stmt

* update goreleaser in test-code

* docs: improve postgres example

* update more projections

* fix: add beta log for postgres

* revert index name change

* prerelease

* fix: add sequence to v1 "reduce paniced"

* log if nil

* add logging

* fix: log output

* fix(import): check if org exists and user

* refactor: imports

* fix(user): ignore malformed events

* refactor: method naming

* fix: test

* refactor: correct errors.Is call

* ci: don't build dev binaries on main

* fix(go releaser): update version to 1.11.0

* fix(user): projection should not break

* fix(user): handle error properly

* docs: correct config example

* Update .releaserc.js

* Update .releaserc.js

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Elio Bischof <eliobischof@gmail.com>
This commit is contained in:
Silvan 2022-08-31 09:52:43 +02:00 committed by GitHub
parent d6c9815945
commit 77b4fc5487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
189 changed files with 3401 additions and 2956 deletions

View File

@ -13,6 +13,10 @@ jobs:
env:
DOCKER_BUILDKIT: 1
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Source checkout
uses: actions/checkout@v2
- name: Set up QEMU
@ -26,7 +30,7 @@ jobs:
uses: goreleaser/goreleaser-action@v3
with:
install-only: true
version: v1.8.3
version: v1.10.3
- name: Build and Unit Test
run: GOOS="linux" GOARCH="amd64" goreleaser build --id prod --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
- name: Publish go coverage

View File

@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.19
- name: Source checkout
uses: actions/checkout@v2
with:
@ -65,7 +65,7 @@ jobs:
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
with:
distribution: goreleaser
version: v1.8.3
version: v1.11.0
args: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -24,8 +24,7 @@ before:
- sh -c "cp -r .artifacts/console/* internal/api/ui/console/static/"
builds:
- id: prod
env:
- env:
- CGO_ENABLED=0
goos:
- linux
@ -36,12 +35,6 @@ builds:
- arm64
ldflags:
-s -w -X github.com/zitadel/zitadel/cmd/build.version={{.Version}} -X github.com/zitadel/zitadel/cmd/build.commit={{.Commit}} -X github.com/zitadel/zitadel/cmd/build.date={{.Date}}
- id: dev
env:
- CGO_ENABLED=0
binary: zitadel-debug
gcflags: all=-N -l
ldflags: ""
dist: .artifacts/goreleaser
@ -54,8 +47,6 @@ dockers:
dockerfile: build/Dockerfile
build_flag_templates:
- "--platform=linux/amd64"
ids:
- prod
- image_templates:
- ghcr.io/zitadel/zitadel:{{ .Tag }}-arm64
- ghcr.io/zitadel/zitadel:{{ .ShortCommit }}-arm64
@ -64,8 +55,6 @@ dockers:
dockerfile: build/Dockerfile
build_flag_templates:
- "--platform=linux/arm64"
ids:
- prod
docker_manifests:
- id: zitadel-latest
@ -84,8 +73,6 @@ docker_manifests:
archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- prod
replacements:
darwin: Darwin
linux: Linux

View File

@ -1,4 +1,4 @@
ARG GO_VERSION=1.17
ARG GO_VERSION=1.19
#######################
## Go dependencies

View File

@ -45,6 +45,7 @@ HTTP1HostHeader: "host"
WebAuthNName: ZITADEL
Database:
# CockroachDB is the default datbase of ZITADEL
cockroach:
Host: localhost
Port: 26257
@ -69,6 +70,32 @@ Database:
RootCert: ""
Cert: ""
Key: ""
# Postgres is used as soon as a value is set
# The values describe the possible fields to set values
postgres:
Host:
Port:
Database:
MaxOpenConns:
MaxConnLifetime:
MaxConnIdleTime:
Options:
User:
Username:
Password:
SSL:
Mode:
RootCert:
Cert:
Key:
Admin:
Username:
Password:
SSL:
Mode:
RootCert:
Cert:
Key:
Machine:
# Cloud hosted VMs need to specify their metadata endpoint so that the machine can be uniquely identified.

View File

@ -2,27 +2,20 @@ package initialise
import (
"database/sql"
"errors"
"github.com/jackc/pgconn"
)
func exists(query string, args ...interface{}) func(*sql.DB) (exists bool, err error) {
return func(db *sql.DB) (exists bool, err error) {
row := db.QueryRow("SELECT EXISTS("+query+")", args...)
err = row.Scan(&exists)
return exists, err
func exec(db *sql.DB, stmt string, possibleErrCodes []string, args ...interface{}) error {
_, err := db.Exec(stmt, args...)
pgErr := new(pgconn.PgError)
if errors.As(err, &pgErr) {
for _, possibleCode := range possibleErrCodes {
if possibleCode == pgErr.Code {
return nil
}
}
}
}
func exec(stmt string, args ...interface{}) func(*sql.DB) error {
return func(db *sql.DB) error {
_, err := db.Exec(stmt, args...)
return err
}
}
func verify(db *sql.DB, checkExists func(*sql.DB) (bool, error), create func(*sql.DB) error) error {
exists, err := checkExists(db)
if exists || err != nil {
return err
}
return create(db)
return err
}

View File

@ -2,7 +2,7 @@ package initialise
import (
"database/sql"
_ "embed"
"embed"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -12,6 +12,26 @@ import (
"github.com/zitadel/zitadel/internal/id"
)
var (
//go:embed sql/cockroach/*
//go:embed sql/postgres/*
stmts embed.FS
createUserStmt string
grantStmt string
databaseStmt string
createEventstoreStmt string
createProjectionsStmt string
createSystemStmt string
createEncryptionKeysStmt string
createEventsStmt string
createSystemSequenceStmt string
createUniqueConstraints string
roleAlreadyExistsCode = "42710"
dbAlreadyExistsCode = "42P04"
)
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
@ -39,6 +59,7 @@ The user provided by flags needs privileges to
func InitAll(config *Config) {
id.Configure(config.Machine)
err := initialise(config.Database,
VerifyUser(config.Database.Username(), config.Database.Password()),
VerifyDatabase(config.Database.Database()),
@ -53,22 +74,85 @@ func InitAll(config *Config) {
func initialise(config database.Config, steps ...func(*sql.DB) error) error {
logging.Info("initialization started")
err := ReadStmts(config.Type())
if err != nil {
return err
}
db, err := database.Connect(config, true)
if err != nil {
return err
}
err = Initialise(db, steps...)
if err != nil {
return err
}
return db.Close()
defer db.Close()
return Init(db, steps...)
}
func Initialise(db *sql.DB, steps ...func(*sql.DB) error) error {
func Init(db *sql.DB, steps ...func(*sql.DB) error) error {
for _, step := range steps {
if err := step(db); err != nil {
return err
}
}
return nil
}
func ReadStmts(typ string) (err error) {
createUserStmt, err = readStmt(typ, "01_user")
if err != nil {
return err
}
databaseStmt, err = readStmt(typ, "02_database")
if err != nil {
return err
}
grantStmt, err = readStmt(typ, "03_grant_user")
if err != nil {
return err
}
createEventstoreStmt, err = readStmt(typ, "04_eventstore")
if err != nil {
return err
}
createProjectionsStmt, err = readStmt(typ, "05_projections")
if err != nil {
return err
}
createSystemStmt, err = readStmt(typ, "06_system")
if err != nil {
return err
}
createEncryptionKeysStmt, err = readStmt(typ, "07_encryption_keys_table")
if err != nil {
return err
}
createEventsStmt, err = readStmt(typ, "08_events_table")
if err != nil {
return err
}
createSystemSequenceStmt, err = readStmt(typ, "09_system_sequence")
if err != nil {
return err
}
createUniqueConstraints, err = readStmt(typ, "10_unique_constraints_table")
if err != nil {
return err
}
return nil
}
func readStmt(typ, step string) (string, error) {
stmt, err := stmts.ReadFile("sql/" + typ + "/" + step + ".sql")
return string(stmt), err
}

View File

@ -31,18 +31,6 @@ func prepareDB(t *testing.T, expectations ...expectation) db {
type expectation func(m sqlmock.Sqlmock)
func expectExists(query string, value bool, args ...driver.Value) expectation {
return func(m sqlmock.Sqlmock) {
m.ExpectQuery(regexp.QuoteMeta(query)).WithArgs(args...).WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(value))
}
}
func expectQueryErr(query string, err error, args ...driver.Value) expectation {
return func(m sqlmock.Sqlmock) {
m.ExpectQuery(regexp.QuoteMeta(query)).WithArgs(args...).WillReturnError(err)
}
}
func expectExec(stmt string, err error, args ...driver.Value) expectation {
return func(m sqlmock.Sqlmock) {
query := m.ExpectExec(regexp.QuoteMeta(stmt)).WithArgs(args...)

View File

@ -1 +0,0 @@
CREATE SCHEMA eventstore

View File

@ -1 +0,0 @@
CREATE SCHEMA projections

View File

@ -1 +0,0 @@
CREATE SCHEMA system;

View File

@ -1 +0,0 @@
SET experimental_enable_hash_sharded_indexes = on

View File

@ -1 +0,0 @@
CREATE SEQUENCE eventstore.system_seq

View File

@ -1,2 +1,2 @@
-- replace %[1]s with the name of the user
CREATE USER %[1]s WITH PASSWORD $1
CREATE USER IF NOT EXISTS %[1]s

View File

@ -1,2 +1,2 @@
-- replace %[1]s with the name of the database
CREATE DATABASE %[1]s
CREATE DATABASE IF NOT EXISTS %[1]s

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS eventstore;
GRANT ALL ON ALL TABLES IN SCHEMA eventstore TO %[1]s;

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS projections;
GRANT ALL ON ALL TABLES IN SCHEMA projections TO %[1]s;

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS system;
GRANT ALL ON ALL TABLES IN SCHEMA system TO %[1]s;

View File

@ -1,4 +1,4 @@
CREATE TABLE system.encryption_keys (
CREATE TABLE IF NOT EXISTS system.encryption_keys (
id TEXT NOT NULL
, key TEXT NOT NULL

View File

@ -1,4 +1,6 @@
CREATE TABLE eventstore.events (
SET experimental_enable_hash_sharded_indexes = on;
CREATE TABLE IF NOT EXISTS eventstore.events (
id UUID DEFAULT gen_random_uuid()
, event_type TEXT NOT NULL
, aggregate_type TEXT NOT NULL
@ -22,4 +24,4 @@ CREATE TABLE eventstore.events (
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC, instance_id)
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC, instance_id)
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence, instance_id)
)
);

View File

@ -0,0 +1 @@
CREATE SEQUENCE IF NOT EXISTS eventstore.system_seq

View File

@ -1,4 +1,4 @@
CREATE TABLE eventstore.unique_constraints (
CREATE TABLE IF NOT EXISTS eventstore.unique_constraints (
instance_id TEXT,
unique_type TEXT,
unique_field TEXT,

View File

@ -0,0 +1 @@
CREATE USER %[1]s

View File

@ -0,0 +1 @@
CREATE DATABASE %[1]s

View File

@ -0,0 +1,3 @@
-- replace the first %[1]s with the database
-- replace the second \%[2]s with the user
GRANT ALL ON DATABASE %[1]s TO %[2]s;

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS eventstore;
GRANT ALL ON ALL TABLES IN SCHEMA eventstore TO %[1]s;

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS projections;
GRANT ALL ON ALL TABLES IN SCHEMA projections TO %[1]s;

View File

@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS system;
GRANT ALL ON ALL TABLES IN SCHEMA system TO %[1]s;

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS system.encryption_keys (
id TEXT NOT NULL
, key TEXT NOT NULL
, PRIMARY KEY (id)
);

View File

@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS eventstore.events (
id UUID DEFAULT gen_random_uuid()
, event_type TEXT NOT NULL
, aggregate_type TEXT NOT NULL
, aggregate_id TEXT NOT NULL
, aggregate_version TEXT NOT NULL
, event_sequence BIGINT NOT NULL
, previous_aggregate_sequence BIGINT
, previous_aggregate_type_sequence INT8
, creation_date TIMESTAMPTZ NOT NULL DEFAULT now()
, event_data JSONB
, editor_user TEXT NOT NULL
, editor_service TEXT NOT NULL
, resource_owner TEXT NOT NULL
, instance_id TEXT NOT NULL
, PRIMARY KEY (event_sequence, instance_id)
, CONSTRAINT previous_sequence_unique UNIQUE(previous_aggregate_sequence, instance_id)
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence, instance_id)
);
CREATE INDEX IF NOT EXISTS agg_type_agg_id ON eventstore.events (aggregate_type, aggregate_id, instance_id);
CREATE INDEX IF NOT EXISTS agg_type ON eventstore.events (aggregate_type, instance_id);
CREATE INDEX IF NOT EXISTS agg_type_seq ON eventstore.events (aggregate_type, event_sequence DESC, instance_id);
CREATE INDEX IF NOT EXISTS max_sequence ON eventstore.events (aggregate_type, aggregate_id, event_sequence DESC, instance_id);

View File

@ -0,0 +1 @@
CREATE SEQUENCE IF NOT EXISTS eventstore.system_seq;

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS eventstore.unique_constraints (
instance_id TEXT,
unique_type TEXT,
unique_field TEXT,
PRIMARY KEY (instance_id, unique_type, unique_field)
);

View File

@ -10,13 +10,6 @@ import (
"github.com/zitadel/logging"
)
var (
searchDatabase = "SELECT database_name FROM [show databases] WHERE database_name = $1"
//go:embed sql/02_database.sql
databaseStmt string
)
func newDatabase() *cobra.Command {
return &cobra.Command{
Use: "database",
@ -40,11 +33,10 @@ The user provided by flags needs priviledge to
}
}
func VerifyDatabase(database string) func(*sql.DB) error {
func VerifyDatabase(databaseName string) func(*sql.DB) error {
return func(db *sql.DB) error {
return verify(db,
exists(searchDatabase, database),
exec(fmt.Sprintf(databaseStmt, database)),
)
logging.WithFields("database", databaseName).Info("verify database")
return exec(db, fmt.Sprintf(string(databaseStmt), databaseName), []string{dbAlreadyExistsCode})
}
}

View File

@ -7,6 +7,12 @@ import (
)
func Test_verifyDB(t *testing.T) {
err := ReadStmts("cockroach") //TODO: check all dialects
if err != nil {
t.Errorf("unable to read stmts: %v", err)
t.FailNow()
}
type args struct {
db db
database string
@ -16,20 +22,11 @@ func Test_verifyDB(t *testing.T) {
args args
targetErr error
}{
{
name: "exists fails",
args: args{
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", sql.ErrConnDone, "zitadel")),
database: "zitadel",
},
targetErr: sql.ErrConnDone,
},
{
name: "doesn't exists, create fails",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
expectExec("CREATE DATABASE zitadel", sql.ErrTxDone),
expectExec("-- replace zitadel with the name of the database\nCREATE DATABASE IF NOT EXISTS zitadel", sql.ErrTxDone),
),
database: "zitadel",
},
@ -39,8 +36,7 @@ func Test_verifyDB(t *testing.T) {
name: "doesn't exists, create successful",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
expectExec("CREATE DATABASE zitadel", nil),
expectExec("-- replace zitadel with the name of the database\nCREATE DATABASE IF NOT EXISTS zitadel", nil),
),
database: "zitadel",
},
@ -50,7 +46,7 @@ func Test_verifyDB(t *testing.T) {
name: "already exists",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", true, "zitadel"),
expectExec("-- replace zitadel with the name of the database\nCREATE DATABASE IF NOT EXISTS zitadel", nil),
),
database: "zitadel",
},

View File

@ -10,12 +10,6 @@ import (
"github.com/zitadel/logging"
)
var (
searchGrant = "SELECT * FROM [SHOW GRANTS ON DATABASE %s] where grantee = $1 AND privilege_type = 'ALL'"
//go:embed sql/03_grant_user.sql
grantStmt string
)
func newGrant() *cobra.Command {
return &cobra.Command{
Use: "grant",
@ -34,12 +28,10 @@ Prereqesits:
}
}
func VerifyGrant(database, username string) func(*sql.DB) error {
func VerifyGrant(databaseName, username string) func(*sql.DB) error {
return func(db *sql.DB) error {
logging.WithFields("user", username, "database", database).Info("verify grant")
return verify(db,
exists(fmt.Sprintf(searchGrant, database), username),
exec(fmt.Sprintf(grantStmt, database, username)),
)
logging.WithFields("user", username, "database", databaseName).Info("verify grant")
return exec(db, fmt.Sprintf(grantStmt, databaseName, username), nil)
}
}

View File

@ -17,20 +17,10 @@ func Test_verifyGrant(t *testing.T) {
args args
targetErr error
}{
{
name: "exists fails",
args: args{
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", sql.ErrConnDone, "zitadel-user")),
database: "zitadel",
username: "zitadel-user",
},
targetErr: sql.ErrConnDone,
},
{
name: "doesn't exists, create fails",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", sql.ErrTxDone),
),
database: "zitadel",
@ -42,7 +32,6 @@ func Test_verifyGrant(t *testing.T) {
name: "correct",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", nil),
),
database: "zitadel",
@ -54,7 +43,7 @@ func Test_verifyGrant(t *testing.T) {
name: "already exists",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", true, "zitadel-user"),
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", nil),
),
database: "zitadel",
username: "zitadel-user",

View File

@ -10,12 +10,6 @@ import (
"github.com/zitadel/logging"
)
var (
searchUser = "SELECT username FROM [show roles] WHERE username = $1"
//go:embed sql/01_user.sql
createUserStmt string
)
func newUser() *cobra.Command {
return &cobra.Command{
Use: "user",
@ -42,9 +36,11 @@ The user provided by flags needs priviledge to
func VerifyUser(username, password string) func(*sql.DB) error {
return func(db *sql.DB) error {
logging.WithFields("username", username).Info("verify user")
return verify(db,
exists(searchUser, username),
exec(fmt.Sprintf(createUserStmt, username), &sql.NullString{String: password, Valid: password != ""}),
)
if password != "" {
createUserStmt += " WITH PASSWORD '" + password + "'"
}
return exec(db, fmt.Sprintf(createUserStmt, username), []string{roleAlreadyExistsCode})
}
}

View File

@ -7,6 +7,12 @@ import (
)
func Test_verifyUser(t *testing.T) {
err := ReadStmts("cockroach") //TODO: check all dialects
if err != nil {
t.Errorf("unable to read stmts: %v", err)
t.FailNow()
}
type args struct {
db db
username string
@ -17,21 +23,11 @@ func Test_verifyUser(t *testing.T) {
args args
targetErr error
}{
{
name: "exists fails",
args: args{
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", sql.ErrConnDone, "zitadel-user")),
username: "zitadel-user",
password: "",
},
targetErr: sql.ErrConnDone,
},
{
name: "doesn't exists, create fails",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
expectExec("CREATE USER zitadel-user WITH PASSWORD $1", sql.ErrTxDone, nil),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER IF NOT EXISTS zitadel-user", sql.ErrTxDone),
),
username: "zitadel-user",
password: "",
@ -42,8 +38,7 @@ func Test_verifyUser(t *testing.T) {
name: "correct without password",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
expectExec("CREATE USER zitadel-user WITH PASSWORD $1", nil, nil),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER IF NOT EXISTS zitadel-user", nil),
),
username: "zitadel-user",
password: "",
@ -54,8 +49,7 @@ func Test_verifyUser(t *testing.T) {
name: "correct with password",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
expectExec("CREATE USER zitadel-user WITH PASSWORD $1", nil, "password"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER IF NOT EXISTS zitadel-user WITH PASSWORD 'password'", nil),
),
username: "zitadel-user",
password: "password",
@ -66,7 +60,7 @@ func Test_verifyUser(t *testing.T) {
name: "already exists",
args: args{
db: prepareDB(t,
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", true, "zitadel-user"),
expectExec("-- replace zitadel-user with the name of the user\nCREATE USER IF NOT EXISTS zitadel-user WITH PASSWORD 'password'", nil),
),
username: "zitadel-user",
password: "",

View File

@ -3,11 +3,12 @@ package initialise
import (
"database/sql"
_ "embed"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
)
@ -20,29 +21,6 @@ const (
encryptionKeysTable = "encryption_keys"
)
var (
searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1"
searchTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = $1"
searchSystemSequence = "SELECT sequence_name FROM [SHOW SEQUENCES] WHERE sequence_name = 'system_seq'"
//go:embed sql/04_eventstore.sql
createEventstoreStmt string
//go:embed sql/05_projections.sql
createProjectionsStmt string
//go:embed sql/06_system.sql
createSystemStmt string
//go:embed sql/07_encryption_keys_table.sql
createEncryptionKeysStmt string
//go:embed sql/08_enable_hash_sharded_indexes.sql
enableHashShardedIdx string
//go:embed sql/09_events_table.sql
createEventsStmt string
//go:embed sql/10_system_sequence.sql
createSystemSequenceStmt string
//go:embed sql/11_unique_constraints_table.sql
createUniqueConstraints string
)
func newZitadel() *cobra.Command {
return &cobra.Command{
Use: "zitadel",
@ -62,44 +40,44 @@ Prereqesits:
}
}
func VerifyZitadel(db *sql.DB) error {
if err := verify(db, exists(searchSchema, systemSchema), exec(createSystemStmt)); err != nil {
func VerifyZitadel(db *sql.DB, config database.Config) error {
if err := exec(db, fmt.Sprintf(createSystemStmt, config.Username()), nil); err != nil {
return err
}
if err := verify(db, exists(searchTable, encryptionKeysTable), createEncryptionKeys); err != nil {
if err := createEncryptionKeys(db); err != nil {
return err
}
if err := verify(db, exists(searchSchema, projectionsSchema), exec(createProjectionsStmt)); err != nil {
if err := exec(db, fmt.Sprintf(createProjectionsStmt, config.Username()), nil); err != nil {
return err
}
if err := verify(db, exists(searchSchema, eventstoreSchema), exec(createEventstoreStmt)); err != nil {
if err := exec(db, fmt.Sprintf(createEventstoreStmt, config.Username()), nil); err != nil {
return err
}
if err := verify(db, exists(searchTable, eventsTable), createEvents); err != nil {
if err := createEvents(db); err != nil {
return err
}
if err := verify(db, exists(searchSystemSequence), exec(createSystemSequenceStmt)); err != nil {
if err := exec(db, createSystemSequenceStmt, nil); err != nil {
return err
}
if err := verify(db, exists(searchTable, uniqueConstraintsTable), exec(createUniqueConstraints)); err != nil {
if err := exec(db, createUniqueConstraints, nil); err != nil {
return err
}
return nil
}
func verifyZitadel(config database.Config) error {
logging.WithFields("database", config.Database).Info("verify zitadel")
logging.WithFields("database", config.Database()).Info("verify zitadel")
db, err := database.Connect(config, false)
if err != nil {
return err
}
if err := VerifyZitadel(db); err != nil {
if err := VerifyZitadel(db, config); err != nil {
return nil
}
@ -124,10 +102,6 @@ func createEvents(db *sql.DB) error {
if err != nil {
return err
}
if _, err = tx.Exec(enableHashShardedIdx); err != nil {
tx.Rollback()
return err
}
if _, err = tx.Exec(createEventsStmt); err != nil {
tx.Rollback()

View File

@ -7,6 +7,12 @@ import (
)
func Test_verifyEvents(t *testing.T) {
err := ReadStmts("cockroach") //TODO: check all dialects
if err != nil {
t.Errorf("unable to read stmts: %v", err)
t.FailNow()
}
type args struct {
db db
}
@ -24,23 +30,11 @@ func Test_verifyEvents(t *testing.T) {
},
targetErr: sql.ErrConnDone,
},
{
name: "hash sharded indexes fails",
args: args{
db: prepareDB(t,
expectBegin(nil),
expectExec("SET experimental_enable_hash_sharded_indexes = on", sql.ErrNoRows),
expectRollback(nil),
),
},
targetErr: sql.ErrNoRows,
},
{
name: "create table fails",
args: args{
db: prepareDB(t,
expectBegin(nil),
expectExec("SET experimental_enable_hash_sharded_indexes = on", nil),
expectExec(createEventsStmt, sql.ErrNoRows),
expectRollback(nil),
),
@ -52,7 +46,6 @@ func Test_verifyEvents(t *testing.T) {
args: args{
db: prepareDB(t,
expectBegin(nil),
expectExec("SET experimental_enable_hash_sharded_indexes = on", nil),
expectExec(createEventsStmt, nil),
expectCommit(nil),
),
@ -60,6 +53,7 @@ func Test_verifyEvents(t *testing.T) {
targetErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := createEvents(tt.args.db.db); !errors.Is(err, tt.targetErr) {

View File

@ -11,8 +11,6 @@ var (
createAdminViews string
//go:embed 01_sql/auth.sql
createAuthViews string
//go:embed 01_sql/notification.sql
createNotificationViews string
//go:embed 01_sql/projections.sql
createProjections string
)
@ -22,7 +20,7 @@ type ProjectionTable struct {
}
func (mig *ProjectionTable) Execute(ctx context.Context) error {
stmt := createAdminViews + createAuthViews + createNotificationViews + createProjections
stmt := createAdminViews + createAuthViews + createProjections
_, err := mig.dbClient.ExecContext(ctx, stmt)
return err
}

View File

@ -30,28 +30,28 @@ CREATE TABLE adminapi.failed_events (
);
CREATE TABLE adminapi.styling (
aggregate_id STRING NOT NULL,
aggregate_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
label_policy_state INT2 NOT NULL DEFAULT 0:::INT2,
label_policy_state INT2 NOT NULL DEFAULT 0::INT2,
sequence INT8 NULL,
primary_color STRING NULL,
background_color STRING NULL,
warn_color STRING NULL,
font_color STRING NULL,
primary_color_dark STRING NULL,
background_color_dark STRING NULL,
warn_color_dark STRING NULL,
font_color_dark STRING NULL,
logo_url STRING NULL,
icon_url STRING NULL,
logo_dark_url STRING NULL,
icon_dark_url STRING NULL,
font_url STRING NULL,
primary_color TEXT NULL,
background_color TEXT NULL,
warn_color TEXT NULL,
font_color TEXT NULL,
primary_color_dark TEXT NULL,
background_color_dark TEXT NULL,
warn_color_dark TEXT NULL,
font_color_dark TEXT NULL,
logo_url TEXT NULL,
icon_url TEXT NULL,
logo_dark_url TEXT NULL,
icon_dark_url TEXT NULL,
font_url TEXT NULL,
err_msg_popup BOOL NULL,
disable_watermark BOOL NULL,
hide_login_name_suffix BOOL NULL,
instance_id STRING NOT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (aggregate_id, label_policy_state, instance_id)
);

View File

@ -30,48 +30,48 @@ CREATE TABLE auth.failed_events (
);
CREATE TABLE auth.users (
id STRING NULL,
id TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner STRING NULL,
resource_owner TEXT NULL,
user_state INT2 NULL,
password_set BOOL NULL,
password_change_required BOOL NULL,
password_change TIMESTAMPTZ NULL,
last_login TIMESTAMPTZ NULL,
user_name STRING NULL,
login_names STRING[] NULL,
preferred_login_name STRING NULL,
first_name STRING NULL,
last_name STRING NULL,
nick_name STRING NULL,
display_name STRING NULL,
preferred_language STRING NULL,
user_name TEXT NULL,
login_names TEXT[] NULL,
preferred_login_name TEXT NULL,
first_name TEXT NULL,
last_name TEXT NULL,
nick_name TEXT NULL,
display_name TEXT NULL,
preferred_language TEXT NULL,
gender INT2 NULL,
email STRING NULL,
email TEXT NULL,
is_email_verified BOOL NULL,
phone STRING NULL,
phone TEXT NULL,
is_phone_verified BOOL NULL,
country STRING NULL,
locality STRING NULL,
postal_code STRING NULL,
region STRING NULL,
street_address STRING NULL,
country TEXT NULL,
locality TEXT NULL,
postal_code TEXT NULL,
region TEXT NULL,
street_address TEXT NULL,
otp_state INT2 NULL,
mfa_max_set_up INT2 NULL,
mfa_init_skipped TIMESTAMPTZ NULL,
sequence INT8 NULL,
init_required BOOL NULL,
username_change_required BOOL NULL,
machine_name STRING NULL,
machine_description STRING NULL,
user_type STRING NULL,
u2f_tokens BYTES NULL,
passwordless_tokens BYTES NULL,
avatar_key STRING NULL,
machine_name TEXT NULL,
machine_description TEXT NULL,
user_type TEXT NULL,
u2f_tokens BYTEA NULL,
passwordless_tokens BYTEA NULL,
avatar_key TEXT NULL,
passwordless_init_required BOOL NULL,
password_init_required BOOL NULL,
instance_id STRING NOT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id)
);
@ -79,148 +79,151 @@ CREATE TABLE auth.users (
CREATE TABLE auth.user_sessions (
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner STRING NULL,
resource_owner TEXT NULL,
state INT2 NULL,
user_agent_id STRING NULL,
user_id STRING NULL,
user_name STRING NULL,
user_agent_id TEXT NULL,
user_id TEXT NULL,
user_name TEXT NULL,
password_verification TIMESTAMPTZ NULL,
second_factor_verification TIMESTAMPTZ NULL,
multi_factor_verification TIMESTAMPTZ NULL,
sequence INT8 NULL,
second_factor_verification_type INT2 NULL,
multi_factor_verification_type INT2 NULL,
user_display_name STRING NULL,
login_name STRING NULL,
user_display_name TEXT NULL,
login_name TEXT NULL,
external_login_verification TIMESTAMPTZ NULL,
selected_idp_config_id STRING NULL,
selected_idp_config_id TEXT NULL,
passwordless_verification TIMESTAMPTZ NULL,
avatar_key STRING NULL,
instance_id STRING NOT NULL,
avatar_key TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (user_agent_id, user_id, instance_id)
);
CREATE TABLE auth.user_external_idps (
external_user_id STRING NOT NULL,
idp_config_id STRING NOT NULL,
user_id STRING NULL,
idp_name STRING NULL,
user_display_name STRING NULL,
external_user_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
user_id TEXT NULL,
idp_name TEXT NULL,
user_display_name TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
resource_owner STRING NULL,
instance_id STRING NOT NULL,
resource_owner TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (external_user_id, idp_config_id, instance_id)
);
CREATE TABLE auth.tokens (
id STRING NOT NULL,
id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner STRING NULL,
application_id STRING NULL,
user_agent_id STRING NULL,
user_id STRING NULL,
resource_owner TEXT NULL,
application_id TEXT NULL,
user_agent_id TEXT NULL,
user_id TEXT NULL,
expiration TIMESTAMPTZ NULL,
sequence INT8 NULL,
scopes STRING[] NULL,
audience STRING[] NULL,
preferred_language STRING NULL,
refresh_token_id STRING NULL,
scopes TEXT[] NULL,
audience TEXT[] NULL,
preferred_language TEXT NULL,
refresh_token_id TEXT NULL,
is_pat BOOL NOT NULL DEFAULT false,
instance_id STRING NOT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id),
INDEX user_user_agent_idx (user_id, user_agent_id)
PRIMARY KEY (id, instance_id)
);
CREATE INDEX user_user_agent_idx ON auth.tokens (user_id, user_agent_id);
CREATE TABLE auth.refresh_tokens (
id STRING NOT NULL,
id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner STRING NULL,
token STRING NULL,
client_id STRING NOT NULL,
user_agent_id STRING NOT NULL,
user_id STRING NOT NULL,
resource_owner TEXT NULL,
token TEXT NULL,
client_id TEXT NOT NULL,
user_agent_id TEXT NOT NULL,
user_id TEXT NOT NULL,
auth_time TIMESTAMPTZ NULL,
idle_expiration TIMESTAMPTZ NULL,
expiration TIMESTAMPTZ NULL,
sequence INT8 NULL,
scopes STRING[] NULL,
audience STRING[] NULL,
amr STRING[] NULL,
instance_id STRING NOT NULL,
scopes TEXT[] NULL,
audience TEXT[] NULL,
amr TEXT[] NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id),
UNIQUE INDEX unique_client_user_index (client_id, user_agent_id, user_id)
PRIMARY KEY (id, instance_id)
);
CREATE UNIQUE INDEX unique_client_user_index ON auth.refresh_tokens (client_id, user_agent_id, user_id);
CREATE TABLE auth.org_project_mapping (
org_id STRING NOT NULL,
project_id STRING NOT NULL,
project_grant_id STRING NULL,
instance_id STRING NOT NULL,
org_id TEXT NOT NULL,
project_id TEXT NOT NULL,
project_grant_id TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (org_id, project_id, instance_id)
);
CREATE TABLE auth.idp_providers (
aggregate_id STRING NOT NULL,
idp_config_id STRING NOT NULL,
aggregate_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
name STRING NULL,
name TEXT NULL,
idp_config_type INT2 NULL,
idp_provider_type INT2 NULL,
idp_state INT2 NULL,
styling_type INT2 NULL,
instance_id STRING NOT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (aggregate_id, idp_config_id, instance_id)
);
CREATE TABLE auth.idp_configs (
idp_config_id STRING NOT NULL,
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
aggregate_id STRING NULL,
name STRING NULL,
aggregate_id TEXT NULL,
name TEXT NULL,
idp_state INT2 NULL,
idp_provider_type INT2 NULL,
is_oidc BOOL NULL,
oidc_client_id STRING NULL,
oidc_client_id TEXT NULL,
oidc_client_secret JSONB NULL,
oidc_issuer STRING NULL,
oidc_scopes STRING[] NULL,
oidc_issuer TEXT NULL,
oidc_scopes TEXT[] NULL,
oidc_idp_display_name_mapping INT2 NULL,
oidc_idp_username_mapping INT2 NULL,
styling_type INT2 NULL,
oauth_authorization_endpoint STRING NULL,
oauth_token_endpoint STRING NULL,
oauth_authorization_endpoint TEXT NULL,
oauth_token_endpoint TEXT NULL,
auto_register BOOL NULL,
jwt_endpoint STRING NULL,
jwt_keys_endpoint STRING NULL,
jwt_header_name STRING NULL,
instance_id STRING NOT NULL,
jwt_endpoint TEXT NULL,
jwt_keys_endpoint TEXT NULL,
jwt_header_name TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (idp_config_id, instance_id)
);
CREATE TABLE auth.auth_requests (
id STRING NOT NULL,
id TEXT NOT NULL,
request JSONB NULL,
code STRING NULL,
code TEXT NULL,
request_type INT2 NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
instance_id STRING NOT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id),
INDEX auth_code_idx (code)
PRIMARY KEY (id, instance_id)
);
CREATE INDEX auth_code_idx ON auth.auth_requests (code);

View File

@ -1,55 +0,0 @@
CREATE SCHEMA notification;
CREATE TABLE notification.locks (
locker_id TEXT,
locked_until TIMESTAMPTZ(3),
view_name TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE notification.current_sequences (
view_name TEXT,
current_sequence BIGINT,
event_timestamp TIMESTAMPTZ,
last_successful_spooler_run TIMESTAMPTZ,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE notification.failed_events (
view_name TEXT,
failed_sequence BIGINT,
failure_count SMALLINT,
err_msg TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, failed_sequence, instance_id)
);
CREATE TABLE notification.notify_users (
id STRING NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner STRING NULL,
user_name STRING NULL,
first_name STRING NULL,
last_name STRING NULL,
nick_name STRING NULL,
display_name STRING NULL,
preferred_language STRING NULL,
gender INT2 NULL,
last_email STRING NULL,
verified_email STRING NULL,
last_phone STRING NULL,
verified_phone STRING NULL,
sequence INT8 NULL,
password_set BOOL NULL,
login_names STRING NULL,
preferred_login_name STRING NULL,
instance_id STRING NULL,
PRIMARY KEY (id)
);

View File

@ -13,8 +13,8 @@ CREATE TABLE system.assets (
resource_owner TEXT,
name TEXT,
content_type TEXT,
hash TEXT AS (md5(data)) STORED,
data BYTES,
hash TEXT GENERATED ALWAYS AS (md5(data)) STORED,
data BYTEA,
updated_at TIMESTAMPTZ,
PRIMARY KEY (instance_id, resource_owner, name)

View File

@ -0,0 +1,47 @@
package start
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/key"
"github.com/zitadel/zitadel/cmd/setup"
"github.com/zitadel/zitadel/cmd/tls"
)
func NewStartFromSetup() *cobra.Command {
cmd := &cobra.Command{
Use: "start-from-setup",
Short: "cold starts zitadel",
Long: `cold starts ZITADEL.
First the initial events are created.
Last ZITADEL starts.
Requirements:
- database
- database is initialized
`,
Run: func(cmd *cobra.Command, args []string) {
err := tls.ModeFromFlag(cmd)
logging.OnError(err).Fatal("invalid tlsMode")
masterKey, err := key.MasterKey(cmd)
logging.OnError(err).Panic("No master key provided")
setupConfig := setup.MustNewConfig(viper.GetViper())
setupSteps := setup.MustNewSteps(viper.New())
setup.Setup(setupConfig, setupSteps, masterKey)
startConfig := MustNewConfig(viper.GetViper())
err = startZitadel(startConfig, masterKey)
logging.OnError(err).Fatal("unable to start zitadel")
},
}
startFlags(cmd)
setup.Flags(cmd)
return cmd
}

View File

@ -51,6 +51,7 @@ func New(out io.Writer, in io.Reader, args []string) *cobra.Command {
setup.New(),
start.New(),
start.NewStartFromInit(),
start.NewStartFromSetup(),
key.New(),
)

View File

@ -144,4 +144,4 @@ The storage layer of ZITADEL is responsible for multiple things. For example:
- Backup and restore operation for disaster recovery purpose
ZITADEL currently supports CockroachDB as first choice of storage due to its perfect match for ZITADELs needs.
Postgresql support is work in progress and should be available soon as well.
Postgresql support is currently in beta.

View File

@ -10,7 +10,7 @@ Since the storage layer takes the heavy lifting of making sure that data in sync
Depending on your projects needs our general recommendation is to run ZITADEL and ZITADELs storage layer across multiple availability zones in the same region or if you need higher guarantees run the storage layer across multiple regions.
Consult the [CockroachDB documentation](https://www.cockroachlabs.com/docs/) for more details or use the [CockroachCloud Service](https://www.cockroachlabs.com/docs/cockroachcloud/create-an-account.html)
> Soon ZITADEL will also support Postgres as database.
> Postgres support of ZITADEL is currently in beta.
## Scalability

View File

@ -8,19 +8,19 @@ services:
image: 'ghcr.io/zitadel/zitadel:stable'
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled'
environment:
- 'ZITADEL_DATABASE_COCKROACH_HOST=db'
- 'ZITADEL_DATABASE_COCKROACH_HOST=crdb'
- 'ZITADEL_EXTERNALSECURE=false'
depends_on:
db:
crdb:
condition: 'service_healthy'
ports:
- '8080:8080'
db:
crdb:
restart: 'always'
networks:
- 'zitadel'
image: 'cockroachdb/cockroach:v22.1.0'
image: 'cockroachdb/cockroach:v22.1.3'
command: 'start-single-node --insecure'
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]

View File

@ -15,7 +15,7 @@ By default, it runs a highly available ZITADEL instance along with a secure and
## Prerequisits
- ZITADEL does not need many resources, 1 CPU and 512MB memory are more than enough. (With more CPU, the password hashing might be faster)
- A cockroachDB or [🚧 Postgresql coming soon](https://github.com/zitadel/zitadel/pull/3998) as only needed storage
- A cockroachDB or Postgresql (currently in beta) as only needed storage
- If you want to front ZITADEL with a reverse proxy, web application firewall or content delivery network, make sure to support [HTTP/2](../manage/self-hosted/http2)

View File

@ -0,0 +1,32 @@
## Cockroach
The default database of ZITADEL is [CockroachDB](https://www.cockroachlabs.com). The SQL database provides a bunch of features like horizontal scalability, data reginality and many more.
The default configuration of the database looks like this:
```yaml
Database:
cockroach:
Host: localhost
Port: 26257
Database: zitadel
MaxOpenConns: 20
MaxConnLifetime: 30m
MaxConnIdleTime: 30m
Options: ""
User:
Username: zitadel
Password: ""
SSL:
Mode: disable
RootCert: ""
Cert: ""
Key: ""
Admin:
Username: root
Password: ""
SSL:
Mode: disable
RootCert: ""
Cert: ""
Key: ""
```

View File

@ -0,0 +1,36 @@
## Postgres
:::caution
Postgres extension is currently in beta.
:::
If you want to use a Postgres database instead of CockroachDB you can [overwrite the default configuration](../configure/configure.mdx).
Postgres can be configured as follows:
```yaml
Database:
postgres:
Host: localhost
Port: 5432
Database: zitadel
MaxOpenConns: 25
MaxConnLifetime: 1h
MaxConnIdleTime: 5m
Options:
User:
Username: zitadel
Password: zitadel
SSL:
Mode: disable
RootCert:
Cert:
Key:
Admin:
Username: postgres
Password: postgres
SSL:
Mode: disable
RootCert:
Cert:
Key:
```

View File

@ -0,0 +1,28 @@
---
title: Database
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Cockroach from './_cockroachdb.mdx'
import Postgres from './_postgres.mdx'
# Database Configuration
<Tabs
groupId="database-vendor"
default="cockroach"
values={[
{'label': 'Cockroach', 'value': 'crdb'},
{'label': 'Postgres', 'value': 'pg'},
]}
>
<TabItem value="crdb">
<Cockroach/>
<More/>
</TabItem>
<TabItem value="pg">
<Postgres/>
<More/>
</TabItem>
</Tabs>

View File

@ -86,6 +86,7 @@ module.exports = {
"guides/manage/self-hosted/custom-domain",
"guides/manage/self-hosted/http2",
"guides/manage/self-hosted/tls_modes",
"guides/manage/self-hosted/database/database",
]
},
{
@ -95,7 +96,6 @@ module.exports = {
"guides/manage/console/organizations",
"guides/manage/console/projects",
"guides/manage/console/applications",
]
},
{

16
go.mod
View File

@ -1,6 +1,6 @@
module github.com/zitadel/zitadel
go 1.17
go 1.19
require (
cloud.google.com/go/storage v1.14.0
@ -29,6 +29,9 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.1
github.com/improbable-eng/grpc-web v0.15.0
github.com/jackc/pgconn v1.12.1
github.com/jackc/pgtype v1.11.0
github.com/jackc/pgx/v4 v4.16.1
github.com/jinzhu/gorm v1.9.16
github.com/k3a/html2text v1.0.8
github.com/kevinburke/twilio-go v0.0.0-20210327194925-1623146bcf73
@ -66,7 +69,7 @@ require (
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7
golang.org/x/tools v0.1.8
golang.org/x/tools v0.1.11
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
google.golang.org/grpc v1.43.0
google.golang.org/protobuf v1.27.1
@ -115,7 +118,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/certificate-transparency-go v1.0.21 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
@ -125,6 +128,11 @@ require (
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -169,7 +177,7 @@ require (
go.opentelemetry.io/otel/internal/metric v0.25.0 // indirect
go.opentelemetry.io/proto/otlp v0.10.0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.63.0

58
go.sum
View File

@ -69,6 +69,8 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
@ -117,7 +119,6 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
@ -125,7 +126,6 @@ github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@ -151,6 +151,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 h1:KwaoQzs/WeUxxJqiJsZ4euOly1Az/IgZXXSxlD/UBNk=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go/v2 v2.2.4 h1:VuiBJKut2Imgrzl+TNk+U5+GxLOh3hnIFxU0EzjTCnI=
github.com/cockroachdb/cockroach-go/v2 v2.2.4/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
@ -263,13 +264,10 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
@ -362,8 +360,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -391,7 +390,6 @@ github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -436,7 +434,6 @@ github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -490,6 +487,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
@ -498,8 +496,17 @@ github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
@ -508,7 +515,11 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
@ -517,6 +528,9 @@ github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
@ -524,11 +538,15 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52 h1:jny9eqYPwkG8IVy7foUoRjQmFLcArCSz+uPsL6KS0HQ=
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52/go.mod h1:RDZ+4PR3mDOtTpVbI0qBE+rdhmtIrtbssiNn38/1OWA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@ -570,7 +588,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@ -596,13 +613,13 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@ -779,13 +796,13 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -864,7 +881,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM=
github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0=
@ -953,9 +969,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
@ -999,8 +1017,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1054,8 +1072,6 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1168,17 +1184,14 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1257,8 +1270,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1446,7 +1459,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -130,7 +130,7 @@ func OIDCGrantTypesFromModel(grantTypes []domain.OIDCGrantType) []app_pb.OIDCGra
}
func OIDCGrantTypesToDomain(grantTypes []app_pb.OIDCGrantType) []domain.OIDCGrantType {
if grantTypes == nil || len(grantTypes) == 0 {
if len(grantTypes) == 0 {
return []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}
}
oidcGrantTypes := make([]domain.OIDCGrantType, len(grantTypes))

View File

@ -6,9 +6,6 @@ import (
"strings"
"time"
//sql import
_ "github.com/lib/pq"
"github.com/mitchellh/mapstructure"
"github.com/zitadel/logging"
@ -61,13 +58,15 @@ func (c *Config) Decode(configs []interface{}) (dialect.Connector, error) {
}
func (c *Config) Connect(useAdmin bool) (*sql.DB, error) {
client, err := sql.Open("postgres", c.String(useAdmin))
client, err := sql.Open("pgx", c.String(useAdmin))
if err != nil {
return nil, err
}
client.SetMaxOpenConns(int(c.MaxOpenConns))
client.SetConnMaxLifetime(c.MaxConnLifetime)
client.SetConnMaxIdleTime(c.MaxConnIdleTime)
return client, nil
}

View File

@ -6,6 +6,8 @@ import (
_ "github.com/zitadel/zitadel/internal/database/cockroach"
"github.com/zitadel/zitadel/internal/database/dialect"
_ "github.com/zitadel/zitadel/internal/database/postgres"
"github.com/zitadel/zitadel/internal/errors"
)
type Config struct {
@ -17,23 +19,6 @@ func (c *Config) SetConnector(connector dialect.Connector) {
c.connector = connector
}
type User struct {
Username string
Password string
SSL SSL
}
type SSL struct {
// type of connection security
Mode string
// RootCert Path to the CA certificate
RootCert string
// Cert Path to the client certificate
Cert string
// Key Path to the client private key
Key string
}
func Connect(config Config, useAdmin bool) (*sql.DB, error) {
client, err := config.connector.Connect(useAdmin)
if err != nil {
@ -41,7 +26,7 @@ func Connect(config Config, useAdmin bool) (*sql.DB, error) {
}
if err := client.Ping(); err != nil {
return nil, err
return nil, errors.ThrowPreconditionFailed(err, "DATAB-0pIWD", "Errors.Database.Connection.Failed")
}
return client, nil

View File

@ -0,0 +1,153 @@
package postgres
import (
"database/sql"
"strconv"
"strings"
"time"
"github.com/mitchellh/mapstructure"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database/dialect"
)
const (
sslDisabledMode = "disable"
)
type Config struct {
Host string
Port int32
Database string
MaxOpenConns uint32
MaxConnLifetime time.Duration
MaxConnIdleTime time.Duration
User User
Admin User
//Additional options to be appended as options=<Options>
//The value will be taken as is. Multiple options are space separated.
Options string
}
func (c *Config) MatchName(name string) bool {
for _, key := range []string{"pg", "postgres"} {
if strings.TrimSpace(strings.ToLower(name)) == key {
return true
}
}
return false
}
func (c *Config) Decode(configs []interface{}) (dialect.Connector, error) {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Result: c,
})
if err != nil {
return nil, err
}
for _, config := range configs {
if err = decoder.Decode(config); err != nil {
return nil, err
}
}
return c, nil
}
func (c *Config) Connect(useAdmin bool) (*sql.DB, error) {
logging.Warn("postgres is currently in beta")
db, err := sql.Open("pgx", c.String(useAdmin))
if err != nil {
return nil, err
}
db.SetMaxOpenConns(int(c.MaxOpenConns))
db.SetConnMaxLifetime(c.MaxConnLifetime)
db.SetConnMaxIdleTime(c.MaxConnIdleTime)
return db, nil
}
func (c *Config) DatabaseName() string {
return c.Database
}
func (c *Config) Username() string {
return c.User.Username
}
func (c *Config) Password() string {
return c.User.Password
}
func (c *Config) Type() string {
return "postgres"
}
type User struct {
Username string
Password string
SSL SSL
}
type SSL struct {
// type of connection security
Mode string
// RootCert Path to the CA certificate
RootCert string
// Cert Path to the client certificate
Cert string
// Key Path to the client private key
Key string
}
func (s *Config) checkSSL(user User) {
if user.SSL.Mode == sslDisabledMode || user.SSL.Mode == "" {
user.SSL = SSL{Mode: sslDisabledMode}
return
}
if user.SSL.RootCert == "" {
logging.WithFields(
"cert set", user.SSL.Cert != "",
"key set", user.SSL.Key != "",
"rootCert set", user.SSL.RootCert != "",
).Fatal("at least ssl root cert has to be set")
}
}
func (c Config) String(useAdmin bool) string {
user := c.User
if useAdmin {
user = c.Admin
}
c.checkSSL(user)
fields := []string{
"host=" + c.Host,
"port=" + strconv.Itoa(int(c.Port)),
"user=" + user.Username,
"application_name=zitadel",
"sslmode=" + user.SSL.Mode,
}
if c.Options != "" {
fields = append(fields, "options="+c.Options)
}
if user.Password != "" {
fields = append(fields, "password="+user.Password)
}
if !useAdmin {
fields = append(fields, "dbname="+c.Database)
}
if user.SSL.Mode != sslDisabledMode {
fields = append(fields, "sslrootcert="+user.SSL.RootCert)
if user.SSL.Cert != "" {
fields = append(fields, "sslcert="+user.SSL.Cert)
}
if user.SSL.Key != "" {
fields = append(fields, "sslkey="+user.SSL.Key)
}
}
return strings.Join(fields, " ")
}

View File

@ -0,0 +1,14 @@
package postgres
import (
//sql import
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/zitadel/zitadel/internal/database/dialect"
)
func init() {
config := &Config{}
dialect.Register(config, config, false)
}

72
internal/database/type.go Normal file
View File

@ -0,0 +1,72 @@
package database
import (
"database/sql/driver"
"github.com/jackc/pgtype"
)
type StringArray []string
// Scan implements the `database/sql.Scanner` interface.
func (s *StringArray) Scan(src any) error {
array := new(pgtype.TextArray)
if err := array.Scan(src); err != nil {
return err
}
if err := array.AssignTo(s); err != nil {
return err
}
return nil
}
// Value implements the `database/sql/driver.Valuer`` interface.
func (s StringArray) Value() (driver.Value, error) {
if len(s) == 0 {
return nil, nil
}
array := pgtype.TextArray{}
if err := array.Set(s); err != nil {
return nil, err
}
return array.Value()
}
type enumField interface {
~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32
}
type EnumArray[F enumField] []F
// Scan implements the `database/sql.Scanner` interface.
func (s *EnumArray[F]) Scan(src any) error {
array := new(pgtype.Int2Array)
if err := array.Scan(src); err != nil {
return err
}
ints := make([]int32, 0, len(array.Elements))
if err := array.AssignTo(&ints); err != nil {
return err
}
*s = make([]F, len(ints))
for i, a := range ints {
(*s)[i] = F(a)
}
return nil
}
// Value implements the `database/sql/driver.Valuer`` interface.
func (s EnumArray[F]) Value() (driver.Value, error) {
if len(s) == 0 {
return nil, nil
}
array := pgtype.Int2Array{}
if err := array.Set(s); err != nil {
return nil, err
}
return array.Value()
}

View File

@ -6,15 +6,15 @@ import (
"strconv"
"strings"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
)
const (
currentSequenceStmtFormat = `SELECT current_sequence, aggregate_type, instance_id FROM %s WHERE projection_name = $1 AND instance_id = ANY ($2) FOR UPDATE`
updateCurrentSequencesStmtFormat = `UPSERT INTO %s (projection_name, aggregate_type, current_sequence, instance_id, timestamp) VALUES `
currentSequenceStmtFormat = `SELECT current_sequence, aggregate_type, instance_id FROM %s WHERE projection_name = $1 AND instance_id = ANY ($2) FOR UPDATE`
updateCurrentSequencesStmtFormat = `INSERT INTO %s (projection_name, aggregate_type, current_sequence, instance_id, timestamp) VALUES `
updateCurrentSequencesConflictStmt = ` ON CONFLICT (projection_name, aggregate_type, instance_id) DO UPDATE SET current_sequence = EXCLUDED.current_sequence, timestamp = EXCLUDED.timestamp`
)
type currentSequences map[eventstore.AggregateType][]*instanceSequence
@ -24,8 +24,8 @@ type instanceSequence struct {
sequence uint64
}
func (h *StatementHandler) currentSequences(ctx context.Context, query func(context.Context, string, ...interface{}) (*sql.Rows, error), instanceIDs []string) (currentSequences, error) {
rows, err := query(ctx, h.currentSequenceStmt, h.ProjectionName, pq.StringArray(instanceIDs))
func (h *StatementHandler) currentSequences(ctx context.Context, query func(context.Context, string, ...interface{}) (*sql.Rows, error), instanceIDs database.StringArray) (currentSequences, error) {
rows, err := query(ctx, h.currentSequenceStmt, h.ProjectionName, instanceIDs)
if err != nil {
return nil, err
}
@ -74,7 +74,7 @@ func (h *StatementHandler) updateCurrentSequences(tx *sql.Tx, sequences currentS
}
}
res, err := tx.Exec(h.updateSequencesBaseStmt+strings.Join(valueQueries, ", "), values...)
res, err := tx.Exec(h.updateSequencesBaseStmt+strings.Join(valueQueries, ", ")+updateCurrentSequencesConflictStmt, values...)
if err != nil {
return errors.ThrowInternal(err, "CRDB-TrH2Z", "unable to exec update sequence")
}

View File

@ -8,8 +8,8 @@ import (
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
@ -17,7 +17,7 @@ type mockExpectation func(sqlmock.Sqlmock)
func expectFailureCount(tableName string, projectionName, instanceID string, failedSeq, failureCount uint64) func(sqlmock.Sqlmock) {
return func(m sqlmock.Sqlmock) {
m.ExpectQuery(`WITH failures AS \(SELECT failure_count FROM `+tableName+` WHERE projection_name = \$1 AND failed_sequence = \$2\ AND instance_id = \$3\) SELECT IF\(EXISTS\(SELECT failure_count FROM failures\), \(SELECT failure_count FROM failures\), 0\) AS failure_count`).
m.ExpectQuery(`WITH failures AS \(SELECT failure_count FROM `+tableName+` WHERE projection_name = \$1 AND failed_sequence = \$2 AND instance_id = \$3\) SELECT COALESCE\(\(SELECT failure_count FROM failures\), 0\) AS failure_count`).
WithArgs(projectionName, failedSeq, instanceID).
WillReturnRows(
sqlmock.NewRows([]string{"failure_count"}).
@ -28,7 +28,7 @@ func expectFailureCount(tableName string, projectionName, instanceID string, fai
func expectUpdateFailureCount(tableName string, projectionName, instanceID string, seq, failureCount uint64) func(sqlmock.Sqlmock) {
return func(m sqlmock.Sqlmock) {
m.ExpectExec(`UPSERT INTO `+tableName+` \(projection_name, failed_sequence, failure_count, error, instance_id\) VALUES \(\$1, \$2, \$3, \$4\, \$5\)`).
m.ExpectExec(`INSERT INTO `+tableName+` \(projection_name, failed_sequence, failure_count, error, instance_id\) VALUES \(\$1, \$2, \$3, \$4\, \$5\) ON CONFLICT \(projection_name, failed_sequence, instance_id\) DO UPDATE SET failure_count = EXCLUDED\.failure_count, error = EXCLUDED\.error`).
WithArgs(projectionName, seq, failureCount, sqlmock.AnyArg(), instanceID).WillReturnResult(sqlmock.NewResult(1, 1))
}
}
@ -133,7 +133,7 @@ func expectCurrentSequence(tableName, projection string, seq uint64, aggregateTy
m.ExpectQuery(`SELECT current_sequence, aggregate_type, instance_id FROM `+tableName+` WHERE projection_name = \$1 AND instance_id = ANY \(\$2\) FOR UPDATE`).
WithArgs(
projection,
pq.StringArray(instanceIDs),
database.StringArray(instanceIDs),
).
WillReturnRows(
rows,
@ -146,7 +146,7 @@ func expectCurrentSequenceErr(tableName, projection string, instanceIDs []string
m.ExpectQuery(`SELECT current_sequence, aggregate_type, instance_id FROM `+tableName+` WHERE projection_name = \$1 AND instance_id = ANY \(\$2\) FOR UPDATE`).
WithArgs(
projection,
pq.StringArray(instanceIDs),
database.StringArray(instanceIDs),
).
WillReturnError(err)
}
@ -157,7 +157,7 @@ func expectCurrentSequenceNoRows(tableName, projection string, instanceIDs []str
m.ExpectQuery(`SELECT current_sequence, aggregate_type, instance_id FROM `+tableName+` WHERE projection_name = \$1 AND instance_id = ANY \(\$2\) FOR UPDATE`).
WithArgs(
projection,
pq.StringArray(instanceIDs),
database.StringArray(instanceIDs),
).
WillReturnRows(
sqlmock.NewRows([]string{"current_sequence", "aggregate_type", "instance_id"}),
@ -170,7 +170,7 @@ func expectCurrentSequenceScanErr(tableName, projection string, instanceIDs []st
m.ExpectQuery(`SELECT current_sequence, aggregate_type, instance_id FROM `+tableName+` WHERE projection_name = \$1 AND instance_id = ANY \(\$2\) FOR UPDATE`).
WithArgs(
projection,
pq.StringArray(instanceIDs),
database.StringArray(instanceIDs),
).
WillReturnRows(
sqlmock.NewRows([]string{"current_sequence", "aggregate_type", "instance_id"}).
@ -182,7 +182,7 @@ func expectCurrentSequenceScanErr(tableName, projection string, instanceIDs []st
func expectUpdateCurrentSequence(tableName, projection string, seq uint64, aggregateType, instanceID string) func(sqlmock.Sqlmock) {
return func(m sqlmock.Sqlmock) {
m.ExpectExec("UPSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\)`).
m.ExpectExec("INSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\) ON CONFLICT \(projection_name, aggregate_type, instance_id\) DO UPDATE SET current_sequence = EXCLUDED.current_sequence, timestamp = EXCLUDED.timestamp`).
WithArgs(
projection,
aggregateType,
@ -213,7 +213,7 @@ func expectUpdateThreeCurrentSequence(t *testing.T, tableName, projection string
matchers[i] = matcher
}
return func(m sqlmock.Sqlmock) {
m.ExpectExec("UPSERT INTO " + tableName + ` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\), \(\$5, \$6, \$7, \$8, NOW\(\)\), \(\$9, \$10, \$11, \$12, NOW\(\)\)`).
m.ExpectExec("INSERT INTO " + tableName + ` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\), \(\$5, \$6, \$7, \$8, NOW\(\)\), \(\$9, \$10, \$11, \$12, NOW\(\)\) ON CONFLICT \(projection_name, aggregate_type, instance_id\) DO UPDATE SET current_sequence = EXCLUDED.current_sequence, timestamp = EXCLUDED.timestamp`).
WithArgs(
matchers...,
).
@ -262,7 +262,7 @@ func (m *currentSequenceMatcher) Match(value driver.Value) bool {
func expectUpdateCurrentSequenceErr(tableName, projection string, seq uint64, err error, aggregateType, instanceID string) func(sqlmock.Sqlmock) {
return func(m sqlmock.Sqlmock) {
m.ExpectExec("UPSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\)`).
m.ExpectExec("INSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\) ON CONFLICT \(projection_name, aggregate_type, instance_id\) DO UPDATE SET current_sequence = EXCLUDED.current_sequence, timestamp = EXCLUDED.timestamp`).
WithArgs(
projection,
aggregateType,
@ -275,7 +275,7 @@ func expectUpdateCurrentSequenceErr(tableName, projection string, seq uint64, er
func expectUpdateCurrentSequenceNoRows(tableName, projection string, seq uint64, aggregateType, instanceID string) func(sqlmock.Sqlmock) {
return func(m sqlmock.Sqlmock) {
m.ExpectExec("UPSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\)`).
m.ExpectExec("INSERT INTO "+tableName+` \(projection_name, aggregate_type, current_sequence, instance_id, timestamp\) VALUES \(\$1, \$2, \$3, \$4, NOW\(\)\) ON CONFLICT \(projection_name, aggregate_type, instance_id\) DO UPDATE SET current_sequence = EXCLUDED.current_sequence, timestamp = EXCLUDED.timestamp`).
WithArgs(
projection,
aggregateType,
@ -297,10 +297,10 @@ func expectLock(lockTable, workerName string, d time.Duration, instanceID string
` WHERE `+lockTable+`\.projection_name = \$3 AND `+lockTable+`\.instance_id = ANY \(\$5\) AND \(`+lockTable+`\.locker_id = \$1 OR `+lockTable+`\.locked_until < now\(\)\)`).
WithArgs(
workerName,
float64(d),
d,
projectionName,
instanceID,
pq.StringArray{instanceID},
database.StringArray{instanceID},
).
WillReturnResult(
sqlmock.NewResult(1, 1),
@ -317,11 +317,11 @@ func expectLockMultipleInstances(lockTable, workerName string, d time.Duration,
` WHERE `+lockTable+`\.projection_name = \$3 AND `+lockTable+`\.instance_id = ANY \(\$6\) AND \(`+lockTable+`\.locker_id = \$1 OR `+lockTable+`\.locked_until < now\(\)\)`).
WithArgs(
workerName,
float64(d),
d,
projectionName,
instanceID1,
instanceID2,
pq.StringArray{instanceID1, instanceID2},
database.StringArray{instanceID1, instanceID2},
).
WillReturnResult(
sqlmock.NewResult(1, 1),
@ -338,10 +338,10 @@ func expectLockNoRows(lockTable, workerName string, d time.Duration, instanceID
` WHERE `+lockTable+`\.projection_name = \$3 AND `+lockTable+`\.instance_id = ANY \(\$5\) AND \(`+lockTable+`\.locker_id = \$1 OR `+lockTable+`\.locked_until < now\(\)\)`).
WithArgs(
workerName,
float64(d),
d,
projectionName,
instanceID,
pq.StringArray{instanceID},
database.StringArray{instanceID},
).
WillReturnResult(driver.ResultNoRows)
}
@ -356,10 +356,10 @@ func expectLockErr(lockTable, workerName string, d time.Duration, instanceID str
` WHERE `+lockTable+`\.projection_name = \$3 AND `+lockTable+`\.instance_id = ANY \(\$5\) AND \(`+lockTable+`\.locker_id = \$1 OR `+lockTable+`\.locked_until < now\(\)\)`).
WithArgs(
workerName,
float64(d),
d,
projectionName,
instanceID,
pq.StringArray{instanceID},
database.StringArray{instanceID},
).
WillReturnError(err)
}

View File

@ -10,15 +10,12 @@ import (
)
const (
setFailureCountStmtFormat = "UPSERT INTO %s" +
setFailureCountStmtFormat = "INSERT INTO %s" +
" (projection_name, failed_sequence, failure_count, error, instance_id)" +
" VALUES ($1, $2, $3, $4, $5)"
" VALUES ($1, $2, $3, $4, $5) ON CONFLICT (projection_name, failed_sequence, instance_id)" +
" DO UPDATE SET failure_count = EXCLUDED.failure_count, error = EXCLUDED.error"
failureCountStmtFormat = "WITH failures AS (SELECT failure_count FROM %s WHERE projection_name = $1 AND failed_sequence = $2 AND instance_id = $3)" +
" SELECT IF(" +
"EXISTS(SELECT failure_count FROM failures)," +
" (SELECT failure_count FROM failures)," +
" 0" +
") AS failure_count"
" SELECT COALESCE((SELECT failure_count FROM failures), 0) AS failure_count"
)
func (h *StatementHandler) handleFailedStmt(tx *sql.Tx, stmt *handler.Statement, execErr error) (shouldContinue bool) {

View File

@ -72,12 +72,12 @@ func NewStatementHandler(
aggregates: aggregateTypes,
reduces: reduces,
bulkLimit: config.BulkLimit,
Locker: NewLocker(config.Client, config.LockTable, config.ProjectionHandlerConfig.ProjectionName),
Locker: NewLocker(config.Client, config.LockTable, config.ProjectionName),
}
h.ProjectionHandler = handler.NewProjectionHandler(ctx, config.ProjectionHandlerConfig, h.reduce, h.Update, h.SearchQuery, h.Lock, h.Unlock)
err := h.Init(ctx, config.InitCheck)
logging.OnError(err).Fatal("unable to initialize projections")
logging.OnError(err).WithField("projection", config.ProjectionName).Fatal("unable to initialize projections")
h.Subscribe(h.aggregates...)

View File

@ -6,11 +6,10 @@ import (
"fmt"
"strings"
"github.com/lib/pq"
"github.com/jackc/pgconn"
"github.com/zitadel/logging"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/handler"
)
@ -186,7 +185,7 @@ type ForeignKey struct {
RefColumns []string
}
//Init implements handler.Init
// Init implements handler.Init
func (h *StatementHandler) Init(ctx context.Context, checks ...*handler.Check) error {
for _, check := range checks {
if check == nil || check.IsNoop() {
@ -280,7 +279,7 @@ func isErrAlreadyExists(err error) bool {
if !errors.As(err, &caosErr) {
return false
}
sqlErr, ok := caosErr.GetParent().(*pq.Error)
sqlErr, ok := caosErr.GetParent().(*pgconn.PgError)
if !ok {
return false
}
@ -288,14 +287,11 @@ func isErrAlreadyExists(err error) bool {
}
func createTableStatement(table *Table, tableName string, suffix string) string {
stmt := fmt.Sprintf("CREATE TABLE %s (%s, PRIMARY KEY (%s)",
stmt := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (%s, PRIMARY KEY (%s)",
tableName+suffix,
createColumnsStatement(table.columns, tableName),
strings.Join(table.primaryKey, ", "),
)
for _, index := range table.indices {
stmt += fmt.Sprintf(", INDEX %s (%s)", index.Name, strings.Join(index.Columns, ","))
}
for _, key := range table.foreignKeys {
ref := tableName
if len(key.RefColumns) > 0 {
@ -309,7 +305,13 @@ func createTableStatement(table *Table, tableName string, suffix string) string
for _, constraint := range table.constraints {
stmt += fmt.Sprintf(", CONSTRAINT %s UNIQUE (%s)", constraint.Name, strings.Join(constraint.Columns, ","))
}
return stmt + ");"
stmt += ");"
for _, index := range table.indices {
stmt += fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s (%s);", index.Name, tableName+suffix, strings.Join(index.Columns, ","))
}
return stmt
}
func createViewStatement(viewName string, selectStmt string) string {
@ -321,7 +323,7 @@ func createViewStatement(viewName string, selectStmt string) string {
func createIndexStatement(index *Index) func(config execConfig) string {
return func(config execConfig) string {
stmt := fmt.Sprintf("CREATE INDEX %s ON %s (%s)",
stmt := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s (%s)",
index.Name,
config.tableName,
strings.Join(index.Columns, ","),
@ -380,9 +382,8 @@ func columnType(columnType ColumnType) string {
case ColumnTypeJSONB:
return "JSONB"
case ColumnTypeBytes:
return "BYTES"
return "BYTEA"
default:
panic("unknown column type")
return ""
}
}

View File

@ -8,9 +8,9 @@ import (
"strings"
"time"
"github.com/lib/pq"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/id"
)
@ -91,17 +91,17 @@ func (h *locker) Unlock(instanceIDs ...string) error {
return nil
}
func (h *locker) lockStatement(lockDuration time.Duration, instanceIDs []string) (string, []interface{}) {
func (h *locker) lockStatement(lockDuration time.Duration, instanceIDs database.StringArray) (string, []interface{}) {
valueQueries := make([]string, len(instanceIDs))
values := make([]interface{}, len(instanceIDs)+4)
values[0] = h.workerName
//the unit of crdb interval is seconds (https://www.cockroachlabs.com/docs/stable/interval.html).
values[1] = lockDuration.Seconds()
values[1] = lockDuration
values[2] = h.projectionName
for i, instanceID := range instanceIDs {
valueQueries[i] = "($1, now()+$2::INTERVAL, $3, $" + strconv.Itoa(i+4) + ")"
values[i+3] = instanceID
}
values[len(values)-1] = pq.StringArray(instanceIDs)
values[len(values)-1] = instanceIDs
return h.lockStmt(strings.Join(valueQueries, ", "), len(values)), values
}

View File

@ -10,6 +10,7 @@ import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/zitadel/zitadel/internal/database"
z_errs "github.com/zitadel/zitadel/internal/errors"
)
@ -43,9 +44,9 @@ func TestStatementHandler_handleLock(t *testing.T) {
name: "lock fails",
want: want{
expectations: []mockExpectation{
expectLock(lockTable, workerName, 2, "instanceID"),
expectLock(lockTable, workerName, 2, "instanceID"),
expectLockErr(lockTable, workerName, 2, "instanceID", errLock),
expectLock(lockTable, workerName, 2*time.Second, "instanceID"),
expectLock(lockTable, workerName, 2*time.Second, "instanceID"),
expectLockErr(lockTable, workerName, 2*time.Second, "instanceID", errLock),
},
},
args: args{
@ -63,8 +64,8 @@ func TestStatementHandler_handleLock(t *testing.T) {
name: "success",
want: want{
expectations: []mockExpectation{
expectLock(lockTable, workerName, 2, "instanceID"),
expectLock(lockTable, workerName, 2, "instanceID"),
expectLock(lockTable, workerName, 2*time.Second, "instanceID"),
expectLock(lockTable, workerName, 2*time.Second, "instanceID"),
},
},
args: args{
@ -81,8 +82,8 @@ func TestStatementHandler_handleLock(t *testing.T) {
name: "success with multiple",
want: want{
expectations: []mockExpectation{
expectLockMultipleInstances(lockTable, workerName, 2, "instanceID1", "instanceID2"),
expectLockMultipleInstances(lockTable, workerName, 2, "instanceID1", "instanceID2"),
expectLockMultipleInstances(lockTable, workerName, 2*time.Second, "instanceID1", "instanceID2"),
expectLockMultipleInstances(lockTable, workerName, 2*time.Second, "instanceID1", "instanceID2"),
},
},
args: args{
@ -149,7 +150,7 @@ func TestStatementHandler_renewLock(t *testing.T) {
name: "lock fails",
want: want{
expectations: []mockExpectation{
expectLockErr(lockTable, workerName, 1, "instanceID", sql.ErrTxDone),
expectLockErr(lockTable, workerName, 1*time.Second, "instanceID", sql.ErrTxDone),
},
isErr: func(err error) bool {
return errors.Is(err, sql.ErrTxDone)
@ -157,14 +158,14 @@ func TestStatementHandler_renewLock(t *testing.T) {
},
args: args{
lockDuration: 1 * time.Second,
instanceIDs: []string{"instanceID"},
instanceIDs: database.StringArray{"instanceID"},
},
},
{
name: "lock no rows",
want: want{
expectations: []mockExpectation{
expectLockNoRows(lockTable, workerName, 2, "instanceID"),
expectLockNoRows(lockTable, workerName, 2*time.Second, "instanceID"),
},
isErr: func(err error) bool {
return errors.Is(err, renewNoRowsAffectedErr)
@ -172,14 +173,14 @@ func TestStatementHandler_renewLock(t *testing.T) {
},
args: args{
lockDuration: 2 * time.Second,
instanceIDs: []string{"instanceID"},
instanceIDs: database.StringArray{"instanceID"},
},
},
{
name: "success",
want: want{
expectations: []mockExpectation{
expectLock(lockTable, workerName, 3, "instanceID"),
expectLock(lockTable, workerName, 3*time.Second, "instanceID"),
},
isErr: func(err error) bool {
return errors.Is(err, nil)
@ -187,14 +188,14 @@ func TestStatementHandler_renewLock(t *testing.T) {
},
args: args{
lockDuration: 3 * time.Second,
instanceIDs: []string{"instanceID"},
instanceIDs: database.StringArray{"instanceID"},
},
},
{
name: "success with multiple",
want: want{
expectations: []mockExpectation{
expectLockMultipleInstances(lockTable, workerName, 3, "instanceID1", "instanceID2"),
expectLockMultipleInstances(lockTable, workerName, 3*time.Second, "instanceID1", "instanceID2"),
},
isErr: func(err error) bool {
return errors.Is(err, nil)

View File

@ -4,8 +4,7 @@ import (
"strconv"
"strings"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/database"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler"
@ -51,10 +50,13 @@ func NewCreateStatement(event eventstore.Event, values []handler.Column, opts ..
}
}
func NewUpsertStatement(event eventstore.Event, values []handler.Column, opts ...execOption) *handler.Statement {
func NewUpsertStatement(event eventstore.Event, conflictCols []handler.Column, values []handler.Column, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values)
columnNames := strings.Join(cols, ", ")
valuesPlaceholder := strings.Join(params, ", ")
conflictTarget := make([]string, len(conflictCols))
for i, col := range conflictCols {
conflictTarget[i] = col.Name
}
config := execConfig{
args: args,
@ -64,8 +66,14 @@ func NewUpsertStatement(event eventstore.Event, values []handler.Column, opts ..
config.err = handler.ErrNoValues
}
updateCols, updateVals := getUpdateCols(cols, conflictTarget)
if len(updateCols) == 0 || len(updateVals) == 0 {
config.err = handler.ErrNoValues
}
q := func(config execConfig) string {
return "UPSERT INTO " + config.tableName + " (" + columnNames + ") VALUES (" + valuesPlaceholder + ")"
return "INSERT INTO " + config.tableName + " (" + strings.Join(cols, ", ") + ") VALUES (" + strings.Join(params, ", ") + ")" +
" ON CONFLICT (" + strings.Join(conflictTarget, ", ") + ") DO UPDATE SET (" + strings.Join(updateCols, ", ") + ") = (" + strings.Join(updateVals, ", ") + ")"
}
return &handler.Statement{
@ -77,15 +85,38 @@ func NewUpsertStatement(event eventstore.Event, values []handler.Column, opts ..
}
}
func getUpdateCols(cols, conflictTarget []string) (updateCols, updateVals []string) {
updateCols = make([]string, len(cols))
updateVals = make([]string, len(cols))
copy(updateCols, cols)
for i := len(updateCols) - 1; i >= 0; i-- {
updateVals[i] = "EXCLUDED." + updateCols[i]
for _, conflict := range conflictTarget {
if conflict == updateCols[i] {
copy(updateCols[i:], updateCols[i+1:])
updateCols[len(updateCols)-1] = ""
updateCols = updateCols[:len(updateCols)-1]
copy(updateVals[i:], updateVals[i+1:])
updateVals[len(updateVals)-1] = ""
updateVals = updateVals[:len(updateVals)-1]
break
}
}
}
return updateCols, updateVals
}
func NewUpdateStatement(event eventstore.Event, values []handler.Column, conditions []handler.Condition, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values)
wheres, whereArgs := conditionsToWhere(conditions, len(params))
args = append(args, whereArgs...)
columnNames := strings.Join(cols, ", ")
valuesPlaceholder := strings.Join(params, ", ")
wheresPlaceholders := strings.Join(wheres, " AND ")
config := execConfig{
args: args,
}
@ -99,7 +130,7 @@ func NewUpdateStatement(event eventstore.Event, values []handler.Column, conditi
}
q := func(config execConfig) string {
return "UPDATE " + config.tableName + " SET (" + columnNames + ") = (" + valuesPlaceholder + ") WHERE " + wheresPlaceholders
return "UPDATE " + config.tableName + " SET (" + strings.Join(cols, ", ") + ") = (" + strings.Join(params, ", ") + ") WHERE " + strings.Join(wheres, " AND ")
}
return &handler.Statement{
@ -171,9 +202,9 @@ func AddCreateStatement(columns []handler.Column, opts ...execOption) func(event
}
}
func AddUpsertStatement(values []handler.Column, opts ...execOption) func(eventstore.Event) Exec {
func AddUpsertStatement(indexCols []handler.Column, values []handler.Column, opts ...execOption) func(eventstore.Event) Exec {
return func(event eventstore.Event) Exec {
return NewUpsertStatement(event, values, opts...).Execute
return NewUpsertStatement(event, indexCols, values, opts...).Execute
}
}
@ -189,9 +220,9 @@ func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func
}
}
func AddCopyStatement(from, to []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.Event) Exec {
func AddCopyStatement(conflict, from, to []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.Event) Exec {
return func(event eventstore.Event) Exec {
return NewCopyStatement(event, from, to, conditions, opts...).Execute
return NewCopyStatement(event, conflict, from, to, conditions, opts...).Execute
}
}
@ -218,11 +249,9 @@ func NewArrayRemoveCol(column string, value interface{}) handler.Column {
func NewArrayIntersectCol(column string, value interface{}) handler.Column {
var arrayType string
switch value.(type) {
case pq.StringArray:
arrayType = "STRING"
case pq.Int32Array,
pq.Int64Array:
arrayType = "INT"
case []string, database.StringArray:
arrayType = "TEXT"
//TODO: handle more types if necessary
}
return handler.Column{
@ -234,25 +263,29 @@ func NewArrayIntersectCol(column string, value interface{}) handler.Column {
}
}
//NewCopyStatement creates a new upsert statement which updates a column from an existing row
// NewCopyStatement creates a new upsert statement which updates a column from an existing row
// cols represent the columns which are objective to change.
// if the value of a col is empty the data will be copied from the selected row
// if the value of a col is not empty the data will be set by the static value
// conds represent the conditions for the selection subquery
func NewCopyStatement(event eventstore.Event, from, to []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement {
func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement {
columnNames := make([]string, len(to))
selectColumns := make([]string, len(from))
updateColumns := make([]string, len(columnNames))
argCounter := 0
args := []interface{}{}
for i := range from {
for i, col := range from {
columnNames[i] = to[i].Name
selectColumns[i] = from[i].Name
if from[i].Value != nil {
updateColumns[i] = "EXCLUDED." + col.Name
if col.Value != nil {
argCounter++
selectColumns[i] = "$" + strconv.Itoa(argCounter)
args = append(args, from[i].Value)
updateColumns[i] = selectColumns[i]
args = append(args, col.Value)
}
}
wheres := make([]string, len(conds))
@ -262,6 +295,11 @@ func NewCopyStatement(event eventstore.Event, from, to []handler.Column, conds [
args = append(args, cond.Value)
}
conflictTargets := make([]string, len(conflictCols))
for i, conflictCol := range conflictCols {
conflictTargets[i] = conflictCol.Name
}
config := execConfig{
args: args,
}
@ -275,7 +313,7 @@ func NewCopyStatement(event eventstore.Event, from, to []handler.Column, conds [
}
q := func(config execConfig) string {
return "UPSERT INTO " +
return "INSERT INTO " +
config.tableName +
" (" +
strings.Join(columnNames, ", ") +
@ -283,7 +321,14 @@ func NewCopyStatement(event eventstore.Event, from, to []handler.Column, conds [
strings.Join(selectColumns, ", ") +
" FROM " +
config.tableName + " AS copy_table WHERE " +
strings.Join(wheres, " AND ")
strings.Join(wheres, " AND ") +
" ON CONFLICT (" +
strings.Join(conflictTargets, ", ") +
") DO UPDATE SET (" +
strings.Join(columnNames, ", ") +
") = (" +
strings.Join(updateColumns, ", ") +
")"
}
return &handler.Statement{

View File

@ -178,9 +178,10 @@ func TestNewCreateStatement(t *testing.T) {
func TestNewUpsertStatement(t *testing.T) {
type args struct {
table string
event *testEvent
values []handler.Column
table string
event *testEvent
conflictCols []handler.Column
values []handler.Column
}
type want struct {
aggregateType eventstore.AggregateType
@ -249,7 +250,7 @@ func TestNewUpsertStatement(t *testing.T) {
},
},
{
name: "correct",
name: "no update cols",
args: args{
table: "my_table",
event: &testEvent{
@ -257,6 +258,9 @@ func TestNewUpsertStatement(t *testing.T) {
sequence: 1,
previousSequence: 0,
},
conflictCols: []handler.Column{
handler.NewCol("col1", nil),
},
values: []handler.Column{
{
Name: "col1",
@ -264,6 +268,42 @@ func TestNewUpsertStatement(t *testing.T) {
},
},
},
want: want{
table: "my_table",
aggregateType: "agg",
sequence: 1,
previousSequence: 1,
executer: &wantExecuter{
shouldExecute: false,
},
isErr: func(err error) bool {
return errors.Is(err, handler.ErrNoValues)
},
},
},
{
name: "correct",
args: args{
table: "my_table",
event: &testEvent{
aggregateType: "agg",
sequence: 1,
previousSequence: 0,
},
conflictCols: []handler.Column{
handler.NewCol("col1", nil),
},
values: []handler.Column{
{
Name: "col1",
Value: "val",
},
{
Name: "col2",
Value: "val",
},
},
},
want: want{
table: "my_table",
aggregateType: "agg",
@ -272,8 +312,8 @@ func TestNewUpsertStatement(t *testing.T) {
executer: &wantExecuter{
params: []params{
{
query: "UPSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{"val"},
query: "INSERT INTO my_table (col1, col2) VALUES ($1, $2) ON CONFLICT (col1) DO UPDATE SET (col2) = (EXCLUDED.col2)",
args: []interface{}{"val", "val"},
},
},
shouldExecute: true,
@ -287,7 +327,7 @@ func TestNewUpsertStatement(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.want.executer.t = t
stmt := NewUpsertStatement(tt.args.event, tt.args.values)
stmt := NewUpsertStatement(tt.args.event, tt.args.conflictCols, tt.args.values)
err := stmt.Execute(tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
@ -724,11 +764,18 @@ func TestNewMultiStatement(t *testing.T) {
},
}),
AddUpsertStatement(
[]handler.Column{
handler.NewCol("col1", nil),
},
[]handler.Column{
{
Name: "col1",
Value: 1,
},
{
Name: "col2",
Value: 2,
},
}),
AddUpdateStatement(
[]handler.Column{
@ -761,8 +808,8 @@ func TestNewMultiStatement(t *testing.T) {
args: []interface{}{1},
},
{
query: "UPSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{1},
query: "INSERT INTO my_table (col1, col2) VALUES ($1, $2) ON CONFLICT (col1) DO UPDATE SET (col2) = (EXCLUDED.col2)",
args: []interface{}{1, 2},
},
{
query: "UPDATE my_table SET (col1) = ($1) WHERE (col1 = $2)",
@ -799,11 +846,12 @@ func TestNewMultiStatement(t *testing.T) {
func TestNewCopyStatement(t *testing.T) {
type args struct {
table string
event *testEvent
from []handler.Column
to []handler.Column
conds []handler.Condition
table string
event *testEvent
conflictingCols []handler.Column
from []handler.Column
to []handler.Column
conds []handler.Condition
}
type want struct {
aggregateType eventstore.AggregateType
@ -1004,7 +1052,7 @@ func TestNewCopyStatement(t *testing.T) {
executer: &wantExecuter{
params: []params{
{
query: "UPSERT INTO my_table (state, id, col_a, col_b) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3",
query: "INSERT INTO my_table (state, id, col_a, col_b) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3 ON CONFLICT () DO UPDATE SET (state, id, col_a, col_b) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
args: []interface{}{1, 2, 3},
},
},
@ -1071,7 +1119,7 @@ func TestNewCopyStatement(t *testing.T) {
executer: &wantExecuter{
params: []params{
{
query: "UPSERT INTO my_table (state, id, col_c, col_d) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3",
query: "INSERT INTO my_table (state, id, col_c, col_d) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3 ON CONFLICT () DO UPDATE SET (state, id, col_c, col_d) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
args: []interface{}{1, 2, 3},
},
},
@ -1086,7 +1134,7 @@ func TestNewCopyStatement(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.want.executer.t = t
stmt := NewCopyStatement(tt.args.event, tt.args.from, tt.args.to, tt.args.conds)
stmt := NewCopyStatement(tt.args.event, tt.args.conflictingCols, tt.args.from, tt.args.to, tt.args.conds)
err := stmt.Execute(tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {

View File

@ -9,6 +9,8 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/initialise"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/database/cockroach"
)
var (
@ -45,13 +47,20 @@ func TestMain(m *testing.M) {
}
func initDB(db *sql.DB) error {
username := "zitadel"
database := "zitadel"
err := initialise.Initialise(db, initialise.VerifyUser(username, ""),
initialise.VerifyDatabase(database),
initialise.VerifyGrant(database, username))
initialise.ReadStmts("cockroach")
config := new(database.Config)
config.SetConnector(&cockroach.Config{
User: cockroach.User{
Username: "zitadel",
},
Database: "zitadel",
})
err := initialise.Init(db,
initialise.VerifyUser(config.Username(), ""),
initialise.VerifyDatabase(config.Database()),
initialise.VerifyGrant(config.Database(), config.Username()))
if err != nil {
return err
}
return initialise.VerifyZitadel(db)
return initialise.VerifyZitadel(db, *config)
}

View File

@ -9,6 +9,7 @@ import (
"strings"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"github.com/jackc/pgconn"
"github.com/lib/pq"
"github.com/zitadel/logging"
@ -30,7 +31,7 @@ const (
" SELECT MAX(event_sequence) seq, 1 join_me" +
" FROM eventstore.events" +
" WHERE aggregate_type = $2" +
" AND (CASE WHEN $9::STRING IS NULL THEN instance_id is null else instance_id = $9::STRING END)" +
" AND (CASE WHEN $9::TEXT IS NULL THEN instance_id is null else instance_id = $9::TEXT END)" +
") AS agg_type " +
// combined with
"LEFT JOIN " +
@ -39,7 +40,7 @@ const (
" SELECT event_sequence seq, resource_owner ro, 1 join_me" +
" FROM eventstore.events" +
" WHERE aggregate_type = $2 AND aggregate_id = $3" +
" AND (CASE WHEN $9::STRING IS NULL THEN instance_id is null else instance_id = $9::STRING END)" +
" AND (CASE WHEN $9::TEXT IS NULL THEN instance_id is null else instance_id = $9::TEXT END)" +
" ORDER BY event_sequence DESC" +
" LIMIT 1" +
") AS agg USING(join_me)" +
@ -69,9 +70,9 @@ const (
" $5::JSONB AS event_data," +
" $6::VARCHAR AS editor_user," +
" $7::VARCHAR AS editor_service," +
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
" COALESCE((resource_owner), $8::VARCHAR) AS resource_owner," +
" $9::VARCHAR AS instance_id," +
" NEXTVAL(CONCAT('eventstore.', IF($9 <> '', CONCAT('i_', $9), 'system'), '_seq'))," +
" NEXTVAL(CONCAT('eventstore.', (CASE WHEN $9 <> '' THEN CONCAT('i_', $9) ELSE 'system' END), '_seq'))," +
" aggregate_sequence AS previous_aggregate_sequence," +
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
"FROM previous_data " +
@ -156,7 +157,7 @@ func (db *CRDB) Push(ctx context.Context, events []*repository.Event, uniqueCons
var instanceRegexp = regexp.MustCompile(`eventstore\.i_[0-9a-zA-Z]{1,}_seq`)
func (db *CRDB) CreateInstance(ctx context.Context, instanceID string) error {
row := db.client.QueryRowContext(ctx, "SELECT CONCAT('eventstore.i_', $1, '_seq')", instanceID)
row := db.client.QueryRowContext(ctx, "SELECT CONCAT('eventstore.i_', $1::TEXT, '_seq')", instanceID)
if row.Err() != nil {
return caos_errs.ThrowInvalidArgument(row.Err(), "SQL-7gtFA", "Errors.InvalidArgument")
}
@ -218,7 +219,7 @@ func (db *CRDB) Filter(ctx context.Context, searchQuery *repository.SearchQuery)
return events, nil
}
//LatestSequence returns the latest sequence found by the search query
// LatestSequence returns the latest sequence found by the search query
func (db *CRDB) LatestSequence(ctx context.Context, searchQuery *repository.SearchQuery) (uint64, error) {
var seq Sequence
err := query(ctx, db, searchQuery, &seq)
@ -228,7 +229,7 @@ func (db *CRDB) LatestSequence(ctx context.Context, searchQuery *repository.Sear
return uint64(seq), nil
}
//InstanceIDs returns the instance ids found by the search query
// InstanceIDs returns the instance ids found by the search query
func (db *CRDB) InstanceIDs(ctx context.Context, searchQuery *repository.SearchQuery) ([]string, error) {
var ids []string
err := query(ctx, db, searchQuery, &ids)
@ -331,7 +332,7 @@ var (
placeholder = regexp.MustCompile(`\?`)
)
//placeholder replaces all "?" with postgres placeholders ($<NUMBER>)
// placeholder replaces all "?" with postgres placeholders ($<NUMBER>)
func (db *CRDB) placeholder(query string) string {
occurances := placeholder.FindAllStringIndex(query, -1)
if len(occurances) == 0 {
@ -355,5 +356,10 @@ func (db *CRDB) isUniqueViolationError(err error) bool {
return true
}
}
if pgxErr, ok := err.(*pgconn.PgError); ok {
if pgxErr.Code == "23505" {
return true
}
}
return false
}

View File

@ -6,8 +6,7 @@ import (
"sync"
"testing"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
@ -279,7 +278,7 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
uniqueCount int
assetCount int
aggType repository.AggregateType
aggID []string
aggID database.StringArray
}
type res struct {
wantErr bool
@ -430,7 +429,7 @@ func TestCRDB_Push_OneAggregate(t *testing.T) {
t.Errorf("CRDB.Push() error = %v, wantErr %v", err, tt.res.wantErr)
}
countEventRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = $1 AND aggregate_id = ANY($2)", tt.res.eventsRes.aggType, pq.Array(tt.res.eventsRes.aggID))
countEventRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = $1 AND aggregate_id = ANY($2)", tt.res.eventsRes.aggType, tt.res.eventsRes.aggID)
var eventCount int
err := countEventRow.Scan(&eventCount)
if err != nil {
@ -462,8 +461,8 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
}
type eventsRes struct {
pushedEventsCount int
aggType []repository.AggregateType
aggID []string
aggType database.StringArray
aggID database.StringArray
}
type res struct {
wantErr bool
@ -487,7 +486,7 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
eventsRes: eventsRes{
pushedEventsCount: 2,
aggID: []string{"100", "101"},
aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
aggType: database.StringArray{t.Name()},
},
},
},
@ -506,7 +505,7 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
eventsRes: eventsRes{
pushedEventsCount: 4,
aggID: []string{"102", "103"},
aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
aggType: database.StringArray{t.Name()},
},
},
},
@ -533,7 +532,7 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
eventsRes: eventsRes{
pushedEventsCount: 12,
aggID: []string{"106", "107", "108"},
aggType: []repository.AggregateType{repository.AggregateType(t.Name())},
aggType: database.StringArray{t.Name()},
},
},
},
@ -547,7 +546,7 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) {
t.Errorf("CRDB.Push() error = %v, wantErr %v", err, tt.res.wantErr)
}
countRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = ANY($1) AND aggregate_id = ANY($2)", pq.Array(tt.res.eventsRes.aggType), pq.Array(tt.res.eventsRes.aggID))
countRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = ANY($1) AND aggregate_id = ANY($2)", tt.res.eventsRes.aggType, tt.res.eventsRes.aggID)
var count int
err := countRow.Scan(&count)
if err != nil {
@ -645,8 +644,8 @@ func TestCRDB_Push_Parallel(t *testing.T) {
}
type eventsRes struct {
pushedEventsCount int
aggTypes []repository.AggregateType
aggIDs []string
aggTypes database.StringArray
aggIDs database.StringArray
}
type res struct {
errCount int
@ -681,7 +680,7 @@ func TestCRDB_Push_Parallel(t *testing.T) {
eventsRes: eventsRes{
aggIDs: []string{"200", "201", "202", "203"},
pushedEventsCount: 9,
aggTypes: []repository.AggregateType{repository.AggregateType(t.Name())},
aggTypes: database.StringArray{t.Name()},
},
},
},
@ -718,7 +717,7 @@ func TestCRDB_Push_Parallel(t *testing.T) {
eventsRes: eventsRes{
aggIDs: []string{"204", "205", "206"},
pushedEventsCount: 14,
aggTypes: []repository.AggregateType{repository.AggregateType(t.Name())},
aggTypes: database.StringArray{t.Name()},
},
},
},
@ -748,7 +747,7 @@ func TestCRDB_Push_Parallel(t *testing.T) {
eventsRes: eventsRes{
aggIDs: []string{"207", "208"},
pushedEventsCount: 11,
aggTypes: []repository.AggregateType{repository.AggregateType(t.Name())},
aggTypes: database.StringArray{t.Name()},
},
},
},
@ -781,7 +780,7 @@ func TestCRDB_Push_Parallel(t *testing.T) {
t.Errorf("CRDB.Push() error count = %d, wanted err count %d, errs: %v", len(errs), tt.res.errCount, errs)
}
rows, err := testCRDBClient.Query("SELECT event_data FROM eventstore.events where aggregate_type = ANY($1) AND aggregate_id = ANY($2) order by event_sequence", pq.Array(tt.res.eventsRes.aggTypes), pq.Array(tt.res.eventsRes.aggIDs))
rows, err := testCRDBClient.Query("SELECT event_data FROM eventstore.events where aggregate_type = ANY($1) AND aggregate_id = ANY($2) order by event_sequence", tt.res.eventsRes.aggTypes, tt.res.eventsRes.aggIDs)
if err != nil {
t.Error("unable to query inserted rows: ", err)
return
@ -993,10 +992,10 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
events []*repository.Event
}
type res struct {
resourceOwners []string
resourceOwners database.StringArray
}
type fields struct {
aggregateIDs []string
aggregateIDs database.StringArray
aggregateType string
}
tests := []struct {
@ -1128,7 +1127,7 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) {
}
}
rows, err := testCRDBClient.Query("SELECT resource_owner FROM eventstore.events WHERE aggregate_type = $1 AND aggregate_id = ANY($2) ORDER BY event_sequence", tt.fields.aggregateType, pq.Array(tt.fields.aggregateIDs))
rows, err := testCRDBClient.Query("SELECT resource_owner FROM eventstore.events WHERE aggregate_type = $1 AND aggregate_id = ANY($2) ORDER BY event_sequence", tt.fields.aggregateType, tt.fields.aggregateIDs)
if err != nil {
t.Error("unable to query inserted rows: ", err)
return

View File

@ -9,6 +9,8 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/initialise"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/database/cockroach"
)
var (
@ -42,15 +44,22 @@ func TestMain(m *testing.M) {
}
func initDB(db *sql.DB) error {
username := "zitadel"
database := "zitadel"
err := initialise.Initialise(db, initialise.VerifyUser(username, ""),
initialise.VerifyDatabase(database),
initialise.VerifyGrant(database, username))
config := new(database.Config)
config.SetConnector(&cockroach.Config{User: cockroach.User{Username: "zitadel"}, Database: "zitadel"})
if err := initialise.ReadStmts("cockroach"); err != nil {
return err
}
err := initialise.Init(db,
initialise.VerifyUser(config.Username(), ""),
initialise.VerifyDatabase(config.Database()),
initialise.VerifyGrant(config.Database(), config.Username()))
if err != nil {
return err
}
return initialise.VerifyZitadel(db)
return initialise.VerifyZitadel(db, *config)
}
func fillUniqueData(unique_type, field, instanceID string) error {

View File

@ -8,7 +8,6 @@ import (
"fmt"
"strings"
"github.com/lib/pq"
"github.com/zitadel/logging"
z_errors "github.com/zitadel/zitadel/internal/errors"
@ -170,8 +169,6 @@ func prepareCondition(criteria querier, filters [][]*repository.Filter) (clause
for _, f := range filter {
value := f.Value
switch value.(type) {
case []bool, []float64, []int64, []string, []repository.AggregateType, []repository.EventType, *[]bool, *[]float64, *[]int64, *[]string, *[]repository.AggregateType, *[]repository.EventType:
value = pq.Array(value)
case map[string]interface{}:
var err error
value, err = json.Marshal(value)

View File

@ -9,7 +9,6 @@ import (
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/repository"
@ -265,7 +264,7 @@ func Test_prepareCondition(t *testing.T) {
},
res: res{
clause: " WHERE ( aggregate_type = ANY(?) )",
values: []interface{}{pq.Array([]repository.AggregateType{"user", "org"})},
values: []interface{}{[]repository.AggregateType{"user", "org"}},
},
},
{
@ -281,7 +280,7 @@ func Test_prepareCondition(t *testing.T) {
},
res: res{
clause: " WHERE ( aggregate_type = ANY(?) AND aggregate_id = ? AND event_type = ANY(?) )",
values: []interface{}{pq.Array([]repository.AggregateType{"user", "org"}), "1234", pq.Array([]repository.EventType{"user.created", "org.created"})},
values: []interface{}{[]repository.AggregateType{"user", "org"}, "1234", []repository.EventType{"user.created", "org.created"}},
},
},
}

View File

@ -3,11 +3,12 @@ package eventstore
import (
"database/sql"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
//SearchQueryBuilder represents the builder for your filter
// SearchQueryBuilder represents the builder for your filter
// if invalid data are set the filter will fail
type SearchQueryBuilder struct {
columns repository.Columns
@ -79,51 +80,51 @@ func (builder *SearchQueryBuilder) Matches(event Event, existingLen int) (matche
return false
}
//Columns defines which fields are set
// Columns defines which fields are set
func (builder *SearchQueryBuilder) Columns(columns Columns) *SearchQueryBuilder {
builder.columns = repository.Columns(columns)
return builder
}
//Limit defines how many events are returned maximally.
// Limit defines how many events are returned maximally.
func (builder *SearchQueryBuilder) Limit(limit uint64) *SearchQueryBuilder {
builder.limit = limit
return builder
}
//ResourceOwner defines the resource owner (org) of the events
// ResourceOwner defines the resource owner (org) of the events
func (builder *SearchQueryBuilder) ResourceOwner(resourceOwner string) *SearchQueryBuilder {
builder.resourceOwner = resourceOwner
return builder
}
//InstanceID defines the instanceID (system) of the events
// InstanceID defines the instanceID (system) of the events
func (builder *SearchQueryBuilder) InstanceID(instanceID string) *SearchQueryBuilder {
builder.instanceID = instanceID
return builder
}
//OrderDesc changes the sorting order of the returned events to descending
// OrderDesc changes the sorting order of the returned events to descending
func (builder *SearchQueryBuilder) OrderDesc() *SearchQueryBuilder {
builder.desc = true
return builder
}
//OrderAsc changes the sorting order of the returned events to ascending
// OrderAsc changes the sorting order of the returned events to ascending
func (builder *SearchQueryBuilder) OrderAsc() *SearchQueryBuilder {
builder.desc = false
return builder
}
//SetTx ensures that the eventstore library uses the existing transaction
// SetTx ensures that the eventstore library uses the existing transaction
func (builder *SearchQueryBuilder) SetTx(tx *sql.Tx) *SearchQueryBuilder {
builder.tx = tx
return builder
}
//AddQuery creates a new sub query.
//All fields in the sub query are AND-connected in the storage request.
//Multiple sub queries are OR-connected in the storage request.
// AddQuery creates a new sub query.
// All fields in the sub query are AND-connected in the storage request.
// Multiple sub queries are OR-connected in the storage request.
func (builder *SearchQueryBuilder) AddQuery() *SearchQuery {
query := &SearchQuery{
builder: builder,
@ -133,61 +134,61 @@ func (builder *SearchQueryBuilder) AddQuery() *SearchQuery {
return query
}
//Or creates a new sub query on the search query builder
// Or creates a new sub query on the search query builder
func (query SearchQuery) Or() *SearchQuery {
return query.builder.AddQuery()
}
//AggregateTypes filters for events with the given aggregate types
// AggregateTypes filters for events with the given aggregate types
func (query *SearchQuery) AggregateTypes(types ...AggregateType) *SearchQuery {
query.aggregateTypes = types
return query
}
//SequenceGreater filters for events with sequence greater the requested sequence
// SequenceGreater filters for events with sequence greater the requested sequence
func (query *SearchQuery) SequenceGreater(sequence uint64) *SearchQuery {
query.eventSequenceGreater = sequence
return query
}
//SequenceLess filters for events with sequence less the requested sequence
// SequenceLess filters for events with sequence less the requested sequence
func (query *SearchQuery) SequenceLess(sequence uint64) *SearchQuery {
query.eventSequenceLess = sequence
return query
}
//AggregateIDs filters for events with the given aggregate id's
// AggregateIDs filters for events with the given aggregate id's
func (query *SearchQuery) AggregateIDs(ids ...string) *SearchQuery {
query.aggregateIDs = ids
return query
}
//InstanceID filters for events with the given instanceID
// InstanceID filters for events with the given instanceID
func (query *SearchQuery) InstanceID(instanceID string) *SearchQuery {
query.instanceID = instanceID
return query
}
//ExcludedInstanceID filters for events not having the given instanceIDs
// ExcludedInstanceID filters for events not having the given instanceIDs
func (query *SearchQuery) ExcludedInstanceID(instanceIDs ...string) *SearchQuery {
query.excludedInstanceIDs = instanceIDs
return query
}
//EventTypes filters for events with the given event types
// EventTypes filters for events with the given event types
func (query *SearchQuery) EventTypes(types ...EventType) *SearchQuery {
query.eventTypes = types
return query
}
//EventData filters for events with the given event data.
//Use this call with care as it will be slower than the other filters.
// EventData filters for events with the given event data.
// Use this call with care as it will be slower than the other filters.
func (query *SearchQuery) EventData(data map[string]interface{}) *SearchQuery {
query.eventData = data
return query
}
//Builder returns the SearchQueryBuilder of the sub query
// Builder returns the SearchQueryBuilder of the sub query
func (query *SearchQuery) Builder() *SearchQueryBuilder {
return query.builder
}
@ -262,7 +263,7 @@ func (query *SearchQuery) aggregateIDFilter() *repository.Filter {
if len(query.aggregateIDs) == 1 {
return repository.NewFilter(repository.FieldAggregateID, query.aggregateIDs[0], repository.OperationEquals)
}
return repository.NewFilter(repository.FieldAggregateID, query.aggregateIDs, repository.OperationIn)
return repository.NewFilter(repository.FieldAggregateID, database.StringArray(query.aggregateIDs), repository.OperationIn)
}
func (query *SearchQuery) eventTypeFilter() *repository.Filter {
@ -272,9 +273,9 @@ func (query *SearchQuery) eventTypeFilter() *repository.Filter {
if len(query.eventTypes) == 1 {
return repository.NewFilter(repository.FieldEventType, repository.EventType(query.eventTypes[0]), repository.OperationEquals)
}
eventTypes := make([]repository.EventType, len(query.eventTypes))
eventTypes := make(database.StringArray, len(query.eventTypes))
for i, eventType := range query.eventTypes {
eventTypes[i] = repository.EventType(eventType)
eventTypes[i] = string(eventType)
}
return repository.NewFilter(repository.FieldEventType, eventTypes, repository.OperationIn)
}
@ -286,9 +287,9 @@ func (query *SearchQuery) aggregateTypeFilter() *repository.Filter {
if len(query.aggregateTypes) == 1 {
return repository.NewFilter(repository.FieldAggregateType, repository.AggregateType(query.aggregateTypes[0]), repository.OperationEquals)
}
aggregateTypes := make([]repository.AggregateType, len(query.aggregateTypes))
aggregateTypes := make(database.StringArray, len(query.aggregateTypes))
for i, aggregateType := range query.aggregateTypes {
aggregateTypes[i] = repository.AggregateType(aggregateType)
aggregateTypes[i] = string(aggregateType)
}
return repository.NewFilter(repository.FieldAggregateType, aggregateTypes, repository.OperationIn)
}
@ -326,7 +327,7 @@ func (query *SearchQuery) excludedInstanceIDFilter() *repository.Filter {
if len(query.excludedInstanceIDs) == 0 {
return nil
}
return repository.NewFilter(repository.FieldInstanceID, query.excludedInstanceIDs, repository.OperationNotIn)
return repository.NewFilter(repository.FieldInstanceID, database.StringArray(query.excludedInstanceIDs), repository.OperationNotIn)
}
func (builder *SearchQueryBuilder) resourceOwnerFilter() *repository.Filter {

View File

@ -5,6 +5,7 @@ import (
"reflect"
"testing"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
@ -312,7 +313,7 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
Limit: 0,
Filters: [][]*repository.Filter{
{
repository.NewFilter(repository.FieldAggregateType, []repository.AggregateType{"user", "org"}, repository.OperationIn),
repository.NewFilter(repository.FieldAggregateType, database.StringArray{"user", "org"}, repository.OperationIn),
},
},
},
@ -483,7 +484,7 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
Filters: [][]*repository.Filter{
{
repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals),
repository.NewFilter(repository.FieldAggregateID, []string{"1234", "0815"}, repository.OperationIn),
repository.NewFilter(repository.FieldAggregateID, database.StringArray{"1234", "0815"}, repository.OperationIn),
},
},
},
@ -561,7 +562,7 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
Filters: [][]*repository.Filter{
{
repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals),
repository.NewFilter(repository.FieldEventType, []repository.EventType{"user.created", "user.changed"}, repository.OperationIn),
repository.NewFilter(repository.FieldEventType, database.StringArray{"user.created", "user.changed"}, repository.OperationIn),
},
},
},
@ -740,10 +741,10 @@ func assertRepoQuery(t *testing.T, want, got *repository.SearchQuery) {
if !reflect.DeepEqual(got.Columns, want.Columns) {
t.Errorf("wrong columns in query: got: %v want: %v", got.Columns, want.Columns)
}
if !reflect.DeepEqual(got.Desc, want.Desc) {
if got.Desc != want.Desc {
t.Errorf("wrong desc in query: got: %v want: %v", got.Desc, want.Desc)
}
if !reflect.DeepEqual(got.Limit, want.Limit) {
if got.Limit != want.Limit {
t.Errorf("wrong limit in query: got: %v want: %v", got.Limit, want.Limit)
}

View File

@ -2,8 +2,6 @@ package sql
import (
"database/sql"
_ "github.com/lib/pq"
)
func Start(client *sql.DB) *SQL {

View File

@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"github.com/lib/pq"
"github.com/zitadel/logging"
z_errors "github.com/zitadel/zitadel/internal/errors"
@ -72,10 +71,6 @@ func prepareCondition(filters [][]*es_models.Filter) (clause string, values []in
subClauses := make([]string, 0, len(filter))
for _, f := range filter {
value := f.GetValue()
switch value.(type) {
case []bool, []float64, []int64, []string, []es_models.AggregateType, []es_models.EventType, *[]bool, *[]float64, *[]int64, *[]string, *[]es_models.AggregateType, *[]es_models.EventType:
value = pq.Array(value)
}
subClauses = append(subClauses, getCondition(f))
if subClauses[len(subClauses)-1] == "" {

View File

@ -6,8 +6,6 @@ import (
"testing"
"time"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
@ -365,7 +363,7 @@ func Test_prepareCondition(t *testing.T) {
},
res: res{
clause: " WHERE ( aggregate_type = ANY(?) )",
values: []interface{}{pq.Array([]es_models.AggregateType{"user", "org"})},
values: []interface{}{[]es_models.AggregateType{"user", "org"}},
},
},
{
@ -381,7 +379,7 @@ func Test_prepareCondition(t *testing.T) {
},
res: res{
clause: " WHERE ( aggregate_type = ANY(?) AND aggregate_id = ? AND event_type = ANY(?) )",
values: []interface{}{pq.Array([]es_models.AggregateType{"user", "org"}), "1234", pq.Array([]es_models.EventType{"user.created", "org.created"})},
values: []interface{}{[]es_models.AggregateType{"user", "org"}, "1234", []es_models.EventType{"user.created", "org.created"}},
},
},
}

View File

@ -3,9 +3,6 @@ package sql
import (
"context"
"database/sql"
//sql import
_ "github.com/lib/pq"
)
type SQL struct {

View File

@ -31,7 +31,7 @@ func Renew(dbClient *sql.DB, lockTable, lockerID, viewModel, instanceID string,
return crdb.ExecuteTx(context.Background(), dbClient, nil, func(tx *sql.Tx) error {
insert := fmt.Sprintf(insertStmtFormat, lockTable)
result, err := tx.Exec(insert,
lockerID, waitTime.Milliseconds()/millisecondsAsSeconds, viewModel, instanceID)
lockerID, waitTime, viewModel, instanceID)
if err != nil {
tx.Rollback()

View File

@ -38,7 +38,12 @@ func ReduceEvent(handler Handler, event *models.Event) {
if err != nil {
handler.Subscription().Unsubscribe()
logging.WithFields("cause", err, "stack", string(debug.Stack())).Error("reduce panicked")
logging.WithFields(
"cause", err,
"stack", string(debug.Stack()),
"sequence", event.Sequence,
"instnace", event.InstanceID,
).Error("reduce panicked")
}
}()
currentSequence, err := handler.CurrentSequence(event.InstanceID)

View File

@ -75,7 +75,10 @@ func (s *spooledHandler) load(workerID string) {
err := recover()
if err != nil {
logging.WithFields("cause", err, "stack", string(debug.Stack())).Error("reduce panicked")
logging.WithFields(
"cause", err,
"stack", string(debug.Stack()),
).Error("reduce panicked")
}
}()
ctx, cancel := context.WithCancel(context.Background())
@ -167,7 +170,7 @@ func (s *spooledHandler) query(ctx context.Context, instanceIDs ...string) ([]*m
return s.eventstore.FilterEvents(ctx, query)
}
//lock ensures the lock on the database.
// lock ensures the lock on the database.
// the returned channel will be closed if ctx is done or an error occured durring lock
func (s *spooledHandler) lock(ctx context.Context, errs chan<- error, workerID string) chan bool {
renewTimer := time.After(0)

View File

@ -5,11 +5,11 @@ import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/lib/pq"
"github.com/zitadel/logging"
caos_errs "github.com/zitadel/zitadel/internal/errors"
@ -36,18 +36,18 @@ type IDPConfigView struct {
IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"`
AutoRegister bool `json:"autoRegister" gorm:"column:auto_register"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"`
OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"`
OIDCScopes pq.StringArray `json:"scopes" gorm:"column:oidc_scopes"`
OIDCIDPDisplayNameMapping int32 `json:"idpDisplayNameMapping" gorm:"column:oidc_idp_display_name_mapping"`
OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"`
OAuthAuthorizationEndpoint string `json:"authorizationEndpoint" gorm:"column:oauth_authorization_endpoint"`
OAuthTokenEndpoint string `json:"tokenEndpoint" gorm:"column:oauth_token_endpoint"`
JWTEndpoint string `json:"jwtEndpoint" gorm:"jwt_endpoint"`
JWTKeysEndpoint string `json:"keysEndpoint" gorm:"jwt_keys_endpoint"`
JWTHeaderName string `json:"headerName" gorm:"jwt_header_name"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"`
OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"`
OIDCScopes database.StringArray `json:"scopes" gorm:"column:oidc_scopes"`
OIDCIDPDisplayNameMapping int32 `json:"idpDisplayNameMapping" gorm:"column:oidc_idp_display_name_mapping"`
OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"`
OAuthAuthorizationEndpoint string `json:"authorizationEndpoint" gorm:"column:oauth_authorization_endpoint"`
OAuthTokenEndpoint string `json:"tokenEndpoint" gorm:"column:oauth_token_endpoint"`
JWTEndpoint string `json:"jwtEndpoint" gorm:"jwt_endpoint"`
JWTKeysEndpoint string `json:"keysEndpoint" gorm:"jwt_keys_endpoint"`
JWTHeaderName string `json:"headerName" gorm:"jwt_header_name"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"`

View File

@ -51,7 +51,7 @@ func SonyFlakeGenerator() Generator {
}
// the following is a copy of sonyflake (https://github.com/sony/sonyflake/blob/master/sonyflake.go)
//with the change of using the "POD-IP" if no private ip is found
// with the change of using the "POD-IP" if no private ip is found
func privateIPv4() (net.IP, error) {
as, err := net.InterfaceAddrs()
if err != nil {
@ -88,7 +88,7 @@ func isPrivateIPv4(ip net.IP) bool {
func machineID() (uint16, error) {
if GeneratorConfig == nil {
return 0, errors.New("Cannot create a unique ID for the machine, generator has not been configured.")
return 0, errors.New("cannot create a unique id for the machine, generator has not been configured")
}
errors := []string{}

View File

@ -4,10 +4,10 @@ import (
"encoding/json"
"time"
"github.com/lib/pq"
"github.com/zitadel/logging"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
@ -37,30 +37,30 @@ type ApplicationView struct {
HasProjectCheck bool `json:"hasProjectCheck" gorm:"column:has_project_check"`
PrivateLabelingSetting domain.PrivateLabelingSetting `json:"privateLabelingSetting" gorm:"column:private_labeling_setting"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"`
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
OIDCRedirectUris pq.StringArray `json:"redirectUris" gorm:"column:oidc_redirect_uris"`
OIDCResponseTypes pq.Int64Array `json:"responseTypes" gorm:"column:oidc_response_types"`
OIDCGrantTypes pq.Int64Array `json:"grantTypes" gorm:"column:oidc_grant_types"`
OIDCApplicationType int32 `json:"applicationType" gorm:"column:oidc_application_type"`
OIDCAuthMethodType int32 `json:"authMethodType" gorm:"column:oidc_auth_method_type"`
OIDCPostLogoutRedirectUris pq.StringArray `json:"postLogoutRedirectUris" gorm:"column:oidc_post_logout_redirect_uris"`
NoneCompliant bool `json:"-" gorm:"column:none_compliant"`
ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"`
AdditionalOrigins pq.StringArray `json:"additionalOrigins" gorm:"column:additional_origins"`
AccessTokenType int32 `json:"accessTokenType" gorm:"column:access_token_type"`
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion" gorm:"column:access_token_role_assertion"`
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion" gorm:"column:id_token_role_assertion"`
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion" gorm:"column:id_token_userinfo_assertion"`
ClockSkew time.Duration `json:"clockSkew" gorm:"column:clock_skew"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"`
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
OIDCRedirectUris database.StringArray `json:"redirectUris" gorm:"column:oidc_redirect_uris"`
OIDCResponseTypes database.EnumArray[domain.OIDCResponseType] `json:"responseTypes" gorm:"column:oidc_response_types"`
OIDCGrantTypes database.EnumArray[domain.OIDCGrantType] `json:"grantTypes" gorm:"column:oidc_grant_types"`
OIDCApplicationType int32 `json:"applicationType" gorm:"column:oidc_application_type"`
OIDCAuthMethodType int32 `json:"authMethodType" gorm:"column:oidc_auth_method_type"`
OIDCPostLogoutRedirectUris database.StringArray `json:"postLogoutRedirectUris" gorm:"column:oidc_post_logout_redirect_uris"`
NoneCompliant bool `json:"-" gorm:"column:none_compliant"`
ComplianceProblems database.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList database.StringArray `json:"-" gorm:"column:origin_allow_list"`
AdditionalOrigins database.StringArray `json:"additionalOrigins" gorm:"column:additional_origins"`
AccessTokenType int32 `json:"accessTokenType" gorm:"column:access_token_type"`
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion" gorm:"column:access_token_role_assertion"`
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion" gorm:"column:id_token_role_assertion"`
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion" gorm:"column:id_token_userinfo_assertion"`
ClockSkew time.Duration `json:"clockSkew" gorm:"column:clock_skew"`
Sequence uint64 `json:"-" gorm:"sequence"`
}
func OIDCResponseTypesToModel(oidctypes []int64) []model.OIDCResponseType {
func OIDCResponseTypesToModel(oidctypes []domain.OIDCResponseType) []model.OIDCResponseType {
result := make([]model.OIDCResponseType, len(oidctypes))
for i, t := range oidctypes {
result[i] = model.OIDCResponseType(t)
@ -68,7 +68,7 @@ func OIDCResponseTypesToModel(oidctypes []int64) []model.OIDCResponseType {
return result
}
func OIDCGrantTypesToModel(granttypes []int64) []model.OIDCGrantType {
func OIDCGrantTypesToModel(granttypes []domain.OIDCGrantType) []model.OIDCGrantType {
result := make([]model.OIDCGrantType, len(granttypes))
for i, t := range granttypes {
result[i] = model.OIDCGrantType(t)
@ -169,7 +169,7 @@ func (a *ApplicationView) SetData(event *models.Event) error {
}
func (a *ApplicationView) setOriginAllowList() error {
allowList := make([]string, 0)
allowList := make(database.StringArray, 0)
for _, redirect := range a.OIDCRedirectUris {
origin, err := http_util.GetOriginFromURLString(redirect)
if err != nil {

View File

@ -4,9 +4,9 @@ import (
"encoding/json"
"time"
"github.com/lib/pq"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -24,18 +24,18 @@ const (
)
type ProjectGrantView struct {
GrantID string `json:"-" gorm:"column:grant_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
OrgID string `json:"-" gorm:"column:org_id"`
Name string `json:"name" gorm:"column:project_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:project_state"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
ResourceOwnerName string `json:"-" gorm:"column:resource_owner_name"`
OrgName string `json:"-" gorm:"column:org_name"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
GrantedRoleKeys pq.StringArray `json:"-" gorm:"column:granted_role_keys"`
GrantID string `json:"-" gorm:"column:grant_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
OrgID string `json:"-" gorm:"column:org_id"`
Name string `json:"name" gorm:"column:project_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:project_state"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
ResourceOwnerName string `json:"-" gorm:"column:resource_owner_name"`
OrgName string `json:"-" gorm:"column:org_name"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
GrantedRoleKeys database.StringArray `json:"-" gorm:"column:granted_role_keys"`
}
type ProjectGrant struct {

View File

@ -4,9 +4,9 @@ import (
"encoding/json"
"time"
"github.com/lib/pq"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -24,19 +24,19 @@ const (
)
type ProjectGrantMemberView struct {
UserID string `json:"userId" gorm:"column:user_id;primary_key"`
GrantID string `json:"grantId" gorm:"column:grant_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
UserName string `json:"-" gorm:"column:user_name"`
Email string `json:"-" gorm:"column:email_address"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Roles pq.StringArray `json:"roles" gorm:"column:roles"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"`
AvatarKey string `json:"-" gorm:"column:avatar_key"`
UserResourceOwner string `json:"-" gorm:"column:user_resource_owner"`
UserID string `json:"userId" gorm:"column:user_id;primary_key"`
GrantID string `json:"grantId" gorm:"column:grant_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
UserName string `json:"-" gorm:"column:user_name"`
Email string `json:"-" gorm:"column:email_address"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Roles database.StringArray `json:"roles" gorm:"column:roles"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"`
AvatarKey string `json:"-" gorm:"column:avatar_key"`
UserResourceOwner string `json:"-" gorm:"column:user_resource_owner"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`

View File

@ -5,8 +5,6 @@ import (
"reflect"
"testing"
"github.com/lib/pq"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
es_model "github.com/zitadel/zitadel/internal/project/repository/eventsourcing/model"
"github.com/zitadel/zitadel/internal/repository/project"
@ -30,18 +28,18 @@ func TestGrantedProjectMemberAppendEvent(t *testing.T) {
{
name: "append added member event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantMemberAddedType), ResourceOwner: "OrgID", Data: mockProjectGrantMemberData(&es_model.ProjectGrantMember{GrantID: "ProjectGrantID", UserID: "UserID", Roles: pq.StringArray{"Role"}})},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantMemberAddedType), ResourceOwner: "OrgID", Data: mockProjectGrantMemberData(&es_model.ProjectGrantMember{GrantID: "ProjectGrantID", UserID: "UserID", Roles: []string{"Role"}})},
member: &ProjectGrantMemberView{},
},
result: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: pq.StringArray{"Role"}},
result: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: []string{"Role"}},
},
{
name: "append changed member event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantMemberAddedType), ResourceOwner: "OrgID", Data: mockProjectGrantMemberData(&es_model.ProjectGrantMember{GrantID: "ProjectGrantID", Roles: pq.StringArray{"RoleChanged"}})},
member: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: pq.StringArray{"Role"}},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantMemberAddedType), ResourceOwner: "OrgID", Data: mockProjectGrantMemberData(&es_model.ProjectGrantMember{GrantID: "ProjectGrantID", Roles: []string{"RoleChanged"}})},
member: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: []string{"Role"}},
},
result: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: pq.StringArray{"RoleChanged"}},
result: &ProjectGrantMemberView{ProjectID: "AggregateID", UserID: "UserID", GrantID: "ProjectGrantID", Roles: []string{"RoleChanged"}},
},
}
for _, tt := range tests {

View File

@ -5,8 +5,6 @@ import (
"reflect"
"testing"
"github.com/lib/pq"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/project/model"
es_model "github.com/zitadel/zitadel/internal/project/repository/eventsourcing/model"
@ -36,34 +34,34 @@ func TestProjectGrantAppendEvent(t *testing.T) {
{
name: "append added project grant event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantAddedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID", GrantedOrgID: "GrantedOrgID", RoleKeys: pq.StringArray{"Role"}})},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantAddedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID", GrantedOrgID: "GrantedOrgID", RoleKeys: []string{"Role"}})},
project: &ProjectGrantView{},
},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: pq.StringArray{"Role"}},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: []string{"Role"}},
},
{
name: "append change project grant event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantChangedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID", RoleKeys: pq.StringArray{"RoleChanged"}})},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: pq.StringArray{"Role"}},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantChangedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID", RoleKeys: []string{"RoleChanged"}})},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: []string{"Role"}},
},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: pq.StringArray{"RoleChanged"}},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: []string{"RoleChanged"}},
},
{
name: "append deactivate project grant event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantDeactivatedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID"})},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: pq.StringArray{"Role"}},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: []string{"Role"}},
},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateInactive), GrantedRoleKeys: pq.StringArray{"Role"}},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateInactive), GrantedRoleKeys: []string{"Role"}},
},
{
name: "append reactivate project grant event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.GrantReactivatedType), ResourceOwner: "GrantedOrgID", Data: mockProjectGrantData(&es_model.ProjectGrant{GrantID: "ProjectGrantID"})},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateInactive), GrantedRoleKeys: pq.StringArray{"Role"}},
project: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateInactive), GrantedRoleKeys: []string{"Role"}},
},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: pq.StringArray{"Role"}},
result: &ProjectGrantView{ProjectID: "AggregateID", ResourceOwner: "GrantedOrgID", OrgID: "GrantedOrgID", State: int32(model.ProjectStateActive), GrantedRoleKeys: []string{"Role"}},
},
}
for _, tt := range tests {

View File

@ -4,9 +4,9 @@ import (
"encoding/json"
"time"
"github.com/lib/pq"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -23,18 +23,18 @@ const (
)
type ProjectMemberView struct {
UserID string `json:"userId" gorm:"column:user_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id;primary_key"`
UserName string `json:"-" gorm:"column:user_name"`
Email string `json:"-" gorm:"column:email_address"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Roles pq.StringArray `json:"roles" gorm:"column:roles"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"`
AvatarKey string `json:"-" gorm:"column:avatar_key"`
UserResourceOwner string `json:"-" gorm:"column:user_resource_owner"`
UserID string `json:"userId" gorm:"column:user_id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id;primary_key"`
UserName string `json:"-" gorm:"column:user_name"`
Email string `json:"-" gorm:"column:email_address"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Roles database.StringArray `json:"roles" gorm:"column:roles"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"`
AvatarKey string `json:"-" gorm:"column:avatar_key"`
UserResourceOwner string `json:"-" gorm:"column:user_resource_owner"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`

View File

@ -5,8 +5,6 @@ import (
"reflect"
"testing"
"github.com/lib/pq"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
es_model "github.com/zitadel/zitadel/internal/project/repository/eventsourcing/model"
"github.com/zitadel/zitadel/internal/repository/project"
@ -30,18 +28,18 @@ func TestProjectMemberAppendEvent(t *testing.T) {
{
name: "append added member event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.MemberAddedType), ResourceOwner: "OrgID", Data: mockProjectMemberData(&es_model.ProjectMember{UserID: "UserID", Roles: pq.StringArray{"Role"}})},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.MemberAddedType), ResourceOwner: "OrgID", Data: mockProjectMemberData(&es_model.ProjectMember{UserID: "UserID", Roles: []string{"Role"}})},
member: &ProjectMemberView{},
},
result: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: pq.StringArray{"Role"}},
result: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: []string{"Role"}},
},
{
name: "append changed member event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.MemberAddedType), ResourceOwner: "OrgID", Data: mockProjectMemberData(&es_model.ProjectMember{UserID: "UserID", Roles: pq.StringArray{"RoleChanged"}})},
member: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: pq.StringArray{"Role"}},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_models.EventType(project.MemberAddedType), ResourceOwner: "OrgID", Data: mockProjectMemberData(&es_model.ProjectMember{UserID: "UserID", Roles: []string{"RoleChanged"}})},
member: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: []string{"Role"}},
},
result: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: pq.StringArray{"RoleChanged"}},
result: &ProjectMemberView{ProjectID: "AggregateID", UserID: "UserID", Roles: []string{"RoleChanged"}},
},
}
for _, tt := range tests {

View File

@ -6,7 +6,6 @@ import (
"time"
sq "github.com/Masterminds/squirrel"
"github.com/lib/pq"
"github.com/zitadel/zitadel/internal/api/authz"
@ -216,8 +215,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
for rows.Next() {
var (
actionID sql.NullString
actionCreationDate pq.NullTime
actionChangeDate pq.NullTime
actionCreationDate sql.NullTime
actionChangeDate sql.NullTime
actionResourceOwner sql.NullString
actionState sql.NullInt32
actionSequence sql.NullInt64

View File

@ -31,14 +31,14 @@ func Test_FlowPrepares(t *testing.T) {
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@ -46,7 +46,7 @@ func Test_FlowPrepares(t *testing.T) {
` projections.flows_triggers.sequence,`+
` projections.flows_triggers.resource_owner`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
nil,
nil,
),
@ -63,14 +63,14 @@ func Test_FlowPrepares(t *testing.T) {
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@ -78,7 +78,7 @@ func Test_FlowPrepares(t *testing.T) {
` projections.flows_triggers.sequence,`+
` projections.flows_triggers.resource_owner`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
"id",
"creation_date",
@ -144,14 +144,14 @@ func Test_FlowPrepares(t *testing.T) {
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@ -159,7 +159,7 @@ func Test_FlowPrepares(t *testing.T) {
` projections.flows_triggers.sequence,`+
` projections.flows_triggers.resource_owner`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
"id",
"creation_date",
@ -253,14 +253,14 @@ func Test_FlowPrepares(t *testing.T) {
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@ -268,7 +268,7 @@ func Test_FlowPrepares(t *testing.T) {
` projections.flows_triggers.sequence,`+
` projections.flows_triggers.resource_owner`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
"id",
"creation_date",
@ -321,14 +321,14 @@ func Test_FlowPrepares(t *testing.T) {
},
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@ -336,7 +336,7 @@ func Test_FlowPrepares(t *testing.T) {
` projections.flows_triggers.sequence,`+
` projections.flows_triggers.resource_owner`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
@ -353,16 +353,16 @@ func Test_FlowPrepares(t *testing.T) {
prepare: prepareTriggerActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
nil,
nil,
),
@ -374,16 +374,16 @@ func Test_FlowPrepares(t *testing.T) {
prepare: prepareTriggerActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
"id",
"creation_date",
@ -426,16 +426,16 @@ func Test_FlowPrepares(t *testing.T) {
prepare: prepareTriggerActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
"id",
"creation_date",
@ -498,16 +498,16 @@ func Test_FlowPrepares(t *testing.T) {
prepare: prepareTriggerActionsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.action_state,`+
` projections.actions.sequence,`+
` projections.actions.name,`+
` projections.actions.script`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions ON projections.flows_triggers.action_id = projections.actions.id`),
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {

View File

@ -29,18 +29,18 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail,`+
` COUNT(*) OVER ()`+
` FROM projections.actions`),
` FROM projections.actions2`),
nil,
nil,
),
@ -52,18 +52,18 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail,`+
` COUNT(*) OVER ()`+
` FROM projections.actions`),
` FROM projections.actions2`),
[]string{
"id",
"creation_date",
@ -118,18 +118,18 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail,`+
` COUNT(*) OVER ()`+
` FROM projections.actions`),
` FROM projections.actions2`),
[]string{
"id",
"creation_date",
@ -208,18 +208,18 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail,`+
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail,`+
` COUNT(*) OVER ()`+
` FROM projections.actions`),
` FROM projections.actions2`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
@ -236,17 +236,17 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail`+
` FROM projections.actions`),
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail`+
` FROM projections.actions2`),
nil,
nil,
),
@ -264,17 +264,17 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail`+
` FROM projections.actions`),
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail`+
` FROM projections.actions2`),
[]string{
"id",
"creation_date",
@ -319,17 +319,17 @@ func Test_ActionPrepares(t *testing.T) {
prepare: prepareActionQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.actions.id,`+
` projections.actions.creation_date,`+
` projections.actions.change_date,`+
` projections.actions.resource_owner,`+
` projections.actions.sequence,`+
` projections.actions.action_state,`+
` projections.actions.name,`+
` projections.actions.script,`+
` projections.actions.timeout,`+
` projections.actions.allowed_to_fail`+
` FROM projections.actions`),
regexp.QuoteMeta(`SELECT projections.actions2.id,`+
` projections.actions2.creation_date,`+
` projections.actions2.change_date,`+
` projections.actions2.resource_owner,`+
` projections.actions2.sequence,`+
` projections.actions2.action_state,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.timeout,`+
` projections.actions2.allowed_to_fail`+
` FROM projections.actions2`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {

Some files were not shown because too many files have changed in this diff Show More