From 75a04e83aec77c8faeca2cfcaf6e465734d04f1d Mon Sep 17 00:00:00 2001 From: Iraq <66622793+kkrime@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:20:53 +0200 Subject: [PATCH] chore(db): refactoring instance+org tables to not use deleted_at (#10270) --- backend/v3/domain/instance.go | 21 ++++++------ backend/v3/domain/organization.go | 17 ++++------ .../migration/001_instance_table/up.sql | 3 +- .../migration/002_organization_table/up.sql | 11 +------ .../database/events_testing/instance_test.go | 3 +- .../events_testing/organization_test.go | 3 +- .../storage/database/repository/instance.go | 32 ++++++------------- .../database/repository/instance_test.go | 2 -- backend/v3/storage/database/repository/org.go | 31 ++++++------------ .../storage/database/repository/org_test.go | 2 -- .../query/projection/instance_relational.go | 5 +-- internal/query/projection/org_relational.go | 6 +--- .../query/projection/relational_common.go | 1 - 13 files changed, 40 insertions(+), 97 deletions(-) diff --git a/backend/v3/domain/instance.go b/backend/v3/domain/instance.go index 2efe6734b3..10a3620788 100644 --- a/backend/v3/domain/instance.go +++ b/backend/v3/domain/instance.go @@ -9,16 +9,15 @@ import ( ) type Instance struct { - ID string `json:"id,omitempty" db:"id"` - Name string `json:"name,omitempty" db:"name"` - DefaultOrgID string `json:"defaultOrgId,omitempty" db:"default_org_id"` - IAMProjectID string `json:"iamProjectId,omitempty" db:"iam_project_id"` - ConsoleClientID string `json:"consoleClientId,omitempty" db:"console_client_id"` - ConsoleAppID string `json:"consoleAppId,omitempty" db:"console_app_id"` - DefaultLanguage string `json:"defaultLanguage,omitempty" db:"default_language"` - CreatedAt time.Time `json:"createdAt" db:"created_at"` - UpdatedAt time.Time `json:"updatedAt" db:"updated_at"` - DeletedAt *time.Time `json:"deletedAt" db:"deleted_at"` + ID string `json:"id,omitempty" db:"id"` + Name string `json:"name,omitempty" db:"name"` + DefaultOrgID string `json:"defaultOrgId,omitempty" db:"default_org_id"` + IAMProjectID string `json:"iamProjectId,omitempty" db:"iam_project_id"` + ConsoleClientID string `json:"consoleClientId,omitempty" db:"console_client_id"` + ConsoleAppID string `json:"consoleAppId,omitempty" db:"console_app_id"` + DefaultLanguage string `json:"defaultLanguage,omitempty" db:"default_language"` + CreatedAt time.Time `json:"createdAt" db:"created_at"` + UpdatedAt time.Time `json:"updatedAt" db:"updated_at"` } type instanceCacheIndex uint8 @@ -58,8 +57,6 @@ type instanceColumns interface { CreatedAtColumn() database.Column // UpdatedAtColumn returns the column for the updated at field. UpdatedAtColumn() database.Column - // DeletedAtColumn returns the column for the deleted at field. - DeletedAtColumn() database.Column } // instanceConditions define all the conditions for the instance table. diff --git a/backend/v3/domain/organization.go b/backend/v3/domain/organization.go index 94dd80d72f..870cc77838 100644 --- a/backend/v3/domain/organization.go +++ b/backend/v3/domain/organization.go @@ -16,13 +16,12 @@ const ( ) type Organization struct { - ID string `json:"id,omitempty" db:"id"` - Name string `json:"name,omitempty" db:"name"` - InstanceID string `json:"instanceId,omitempty" db:"instance_id"` - State string `json:"state,omitempty" db:"state"` - CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` - UpdatedAt time.Time `json:"updatedAt,omitempty" db:"updated_at"` - DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"` + ID string `json:"id,omitempty" db:"id"` + Name string `json:"name,omitempty" db:"name"` + InstanceID string `json:"instanceId,omitempty" db:"instance_id"` + State string `json:"state,omitempty" db:"state"` + CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` + UpdatedAt time.Time `json:"updatedAt,omitempty" db:"updated_at"` } // OrgIdentifierCondition is used to help specify a single Organization, @@ -46,8 +45,6 @@ type organizationColumns interface { CreatedAtColumn() database.Column // UpdatedAtColumn returns the column for the updated at field. UpdatedAtColumn() database.Column - // DeletedAtColumn returns the column for the deleted at field. - DeletedAtColumn() database.Column } // organizationConditions define all the conditions for the instance table. @@ -77,7 +74,7 @@ type OrganizationRepository interface { organizationChanges Get(ctx context.Context, id OrgIdentifierCondition, instance_id string, opts ...database.Condition) (*Organization, error) - List(ctx context.Context, opts ...database.Condition) ([]*Organization, error) + List(ctx context.Context, conditions ...database.Condition) ([]*Organization, error) Create(ctx context.Context, instance *Organization) error Update(ctx context.Context, id OrgIdentifierCondition, instance_id string, changes ...database.Change) (int64, error) diff --git a/backend/v3/storage/database/dialect/postgres/migration/001_instance_table/up.sql b/backend/v3/storage/database/dialect/postgres/migration/001_instance_table/up.sql index 75ddcfb6cc..b8faaedafd 100644 --- a/backend/v3/storage/database/dialect/postgres/migration/001_instance_table/up.sql +++ b/backend/v3/storage/database/dialect/postgres/migration/001_instance_table/up.sql @@ -7,8 +7,7 @@ CREATE TABLE IF NOT EXISTS zitadel.instances( console_app_id TEXT, -- NOT NULL, default_language TEXT, -- NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, - updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, - deleted_at TIMESTAMPTZ DEFAULT NULL + updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL ); CREATE OR REPLACE FUNCTION zitadel.set_updated_at() diff --git a/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql b/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql index c09757c003..6ef33fbee4 100644 --- a/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql +++ b/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql @@ -10,21 +10,12 @@ CREATE TABLE zitadel.organizations( state zitadel.organization_state NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, - deleted_at TIMESTAMPTZ DEFAULT NULL, PRIMARY KEY (instance_id, id) ); CREATE UNIQUE INDEX org_unique_instance_id_name_idx - ON zitadel.organizations (instance_id, name) - WHERE deleted_at IS NULL; - --- users are able to set the id for organizations -CREATE INDEX org_id_not_deleted_idx ON zitadel.organizations (id) - WHERE deleted_at IS NULL; - -CREATE INDEX org_name_not_deleted_idx ON zitadel.organizations (name) - WHERE deleted_at IS NULL; + ON zitadel.organizations (instance_id, name); CREATE TRIGGER trigger_set_updated_at BEFORE UPDATE ON zitadel.organizations diff --git a/backend/v3/storage/database/events_testing/instance_test.go b/backend/v3/storage/database/events_testing/instance_test.go index dd289b5822..576e65e020 100644 --- a/backend/v3/storage/database/events_testing/instance_test.go +++ b/backend/v3/storage/database/events_testing/instance_test.go @@ -54,7 +54,6 @@ func TestServer_TestInstanceReduces(t *testing.T) { assert.WithinRange(t, instance.CreatedAt, beforeCreate, afterCreate) // event instance.added assert.WithinRange(t, instance.UpdatedAt, beforeCreate, afterCreate) - assert.Nil(t, instance.DeletedAt) }, retryDuration, tick) }) @@ -132,7 +131,7 @@ func TestServer_TestInstanceReduces(t *testing.T) { ) // event instance.removed assert.Nil(t, instance) - require.NoError(t, err) + require.Equal(t, repository.ErrResourceDoesNotExist, err) }, retryDuration, tick) }) } diff --git a/backend/v3/storage/database/events_testing/organization_test.go b/backend/v3/storage/database/events_testing/organization_test.go index a97c15a8c9..54c07e8ab1 100644 --- a/backend/v3/storage/database/events_testing/organization_test.go +++ b/backend/v3/storage/database/events_testing/organization_test.go @@ -46,7 +46,6 @@ func TestServer_TestOrganizationReduces(t *testing.T) { assert.Equal(t, domain.OrgStateActive.String(), organization.State) assert.WithinRange(t, organization.CreatedAt, beforeCreate, afterCreate) assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) - assert.Nil(t, organization.DeletedAt) }, retryDuration, tick) }) @@ -209,7 +208,7 @@ func TestServer_TestOrganizationReduces(t *testing.T) { orgRepo.NameCondition(orgName), instanceID, ) - require.NoError(t, err) + require.Equal(t, repository.ErrResourceDoesNotExist, err) // event org.remove assert.Nil(t, organization) diff --git a/backend/v3/storage/database/repository/instance.go b/backend/v3/storage/database/repository/instance.go index 80e852785f..cc74b95afd 100644 --- a/backend/v3/storage/database/repository/instance.go +++ b/backend/v3/storage/database/repository/instance.go @@ -3,7 +3,6 @@ package repository import ( "context" "errors" - "time" "github.com/jackc/pgx/v5/pgconn" @@ -29,7 +28,7 @@ func InstanceRepository(client database.QueryExecutor) domain.InstanceRepository // repository // ------------------------------------------------------------- -const queryInstanceStmt = `SELECT id, name, default_org_id, iam_project_id, console_client_id, console_app_id, default_language, created_at, updated_at, deleted_at` + +const queryInstanceStmt = `SELECT id, name, default_org_id, iam_project_id, console_client_id, console_app_id, default_language, created_at, updated_at` + ` FROM zitadel.instances` // Get implements [domain.InstanceRepository]. @@ -39,23 +38,20 @@ func (i *instance) Get(ctx context.Context, id string) (*domain.Instance, error) builder.WriteString(queryInstanceStmt) idCondition := i.IDCondition(id) - // return only non deleted instances - conditions := []database.Condition{idCondition, database.IsNull(i.DeletedAtColumn())} - writeCondition(&builder, database.And(conditions...)) + writeCondition(&builder, idCondition) return scanInstance(ctx, i.client, &builder) } // List implements [domain.InstanceRepository]. -func (i *instance) List(ctx context.Context, opts ...database.Condition) ([]*domain.Instance, error) { +func (i *instance) List(ctx context.Context, conditions ...database.Condition) ([]*domain.Instance, error) { var builder database.StatementBuilder builder.WriteString(queryInstanceStmt) - // return only non deleted instances - opts = append(opts, database.IsNull(i.DeletedAtColumn())) - notDeletedCondition := database.And(opts...) - writeCondition(&builder, notDeletedCondition) + if conditions != nil { + writeCondition(&builder, database.And(conditions...)) + } return scanInstances(ctx, i.client, &builder) } @@ -107,9 +103,7 @@ func (i instance) Update(ctx context.Context, id string, changes ...database.Cha database.Changes(changes).Write(&builder) idCondition := i.IDCondition(id) - // don't update deleted instances - conditions := []database.Condition{idCondition, database.IsNull(i.DeletedAtColumn())} - writeCondition(&builder, database.And(conditions...)) + writeCondition(&builder, idCondition) stmt := builder.String() @@ -121,13 +115,10 @@ func (i instance) Update(ctx context.Context, id string, changes ...database.Cha func (i instance) Delete(ctx context.Context, id string) (int64, error) { var builder database.StatementBuilder - builder.WriteString(`UPDATE zitadel.instances SET deleted_at = $1`) - builder.AppendArgs(time.Now()) + builder.WriteString(`DELETE FROM zitadel.instances`) - // don't delete already deleted instance idCondition := i.IDCondition(id) - conditions := []database.Condition{idCondition, database.IsNull(i.DeletedAtColumn())} - writeCondition(&builder, database.And(conditions...)) + writeCondition(&builder, idCondition) return i.client.Exec(ctx, builder.String(), builder.Args()...) } @@ -204,11 +195,6 @@ func (instance) UpdatedAtColumn() database.Column { return database.NewColumn("updated_at") } -// DeletedAtColumn implements [domain.instanceColumns]. -func (instance) DeletedAtColumn() database.Column { - return database.NewColumn("deleted_at") -} - func scanInstance(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Instance, error) { rows, err := querier.Query(ctx, builder.String(), builder.Args()...) if err != nil { diff --git a/backend/v3/storage/database/repository/instance_test.go b/backend/v3/storage/database/repository/instance_test.go index e6fb6964fe..c3509639a8 100644 --- a/backend/v3/storage/database/repository/instance_test.go +++ b/backend/v3/storage/database/repository/instance_test.go @@ -185,7 +185,6 @@ func TestCreateInstance(t *testing.T) { assert.Equal(t, tt.instance.DefaultLanguage, instance.DefaultLanguage) assert.WithinRange(t, instance.CreatedAt, beforeCreate, afterCreate) assert.WithinRange(t, instance.UpdatedAt, beforeCreate, afterCreate) - assert.Nil(t, instance.DeletedAt) }) } } @@ -298,7 +297,6 @@ func TestUpdateInstance(t *testing.T) { assert.Equal(t, newName, instance.Name) assert.WithinRange(t, instance.UpdatedAt, beforeUpdate, afterUpdate) - assert.Nil(t, instance.DeletedAt) }) } } diff --git a/backend/v3/storage/database/repository/org.go b/backend/v3/storage/database/repository/org.go index 90670500fa..be2188bd40 100644 --- a/backend/v3/storage/database/repository/org.go +++ b/backend/v3/storage/database/repository/org.go @@ -3,7 +3,6 @@ package repository import ( "context" "errors" - "time" "github.com/jackc/pgx/v5/pgconn" @@ -29,7 +28,7 @@ func OrganizationRepository(client database.QueryExecutor) domain.OrganizationRe } } -const queryOrganizationStmt = `SELECT id, name, instance_id, state, created_at, updated_at, deleted_at` + +const queryOrganizationStmt = `SELECT id, name, instance_id, state, created_at, updated_at` + ` FROM zitadel.organizations` // Get implements [domain.OrganizationRepository]. @@ -39,24 +38,22 @@ func (o *org) Get(ctx context.Context, id domain.OrgIdentifierCondition, instanc builder.WriteString(queryOrganizationStmt) instanceIDCondition := o.InstanceIDCondition(instanceID) - // don't update deleted organizations - nonDeletedOrgs := database.IsNull(o.DeletedAtColumn()) - conditions = append(conditions, id, instanceIDCondition, nonDeletedOrgs) + conditions = append(conditions, id, instanceIDCondition) writeCondition(&builder, database.And(conditions...)) return scanOrganization(ctx, o.client, &builder) } // List implements [domain.OrganizationRepository]. -func (o *org) List(ctx context.Context, opts ...database.Condition) ([]*domain.Organization, error) { +func (o *org) List(ctx context.Context, conditions ...database.Condition) ([]*domain.Organization, error) { builder := database.StatementBuilder{} builder.WriteString(queryOrganizationStmt) - // return only non deleted organizations - opts = append(opts, database.IsNull(o.DeletedAtColumn())) - writeCondition(&builder, database.And(opts...)) + if conditions != nil { + writeCondition(&builder, database.And(conditions...)) + } orderBy := database.OrderBy(o.CreatedAtColumn()) orderBy.Write(&builder) @@ -122,10 +119,8 @@ func (o org) Update(ctx context.Context, id domain.OrgIdentifierCondition, insta builder.WriteString(`UPDATE zitadel.organizations SET `) instanceIDCondition := o.InstanceIDCondition(instanceID) - // don't update deleted organizations - nonDeletedOrgs := database.IsNull(o.DeletedAtColumn()) - conditions := []database.Condition{id, instanceIDCondition, nonDeletedOrgs} + conditions := []database.Condition{id, instanceIDCondition} database.Changes(changes).Write(&builder) writeCondition(&builder, database.And(conditions...)) @@ -139,14 +134,11 @@ func (o org) Update(ctx context.Context, id domain.OrgIdentifierCondition, insta func (o org) Delete(ctx context.Context, id domain.OrgIdentifierCondition, instanceID string) (int64, error) { builder := database.StatementBuilder{} - builder.WriteString(`UPDATE zitadel.organizations SET deleted_at = $1`) - builder.AppendArgs(time.Now()) + builder.WriteString(`DELETE FROM zitadel.organizations`) instanceIDCondition := o.InstanceIDCondition(instanceID) - // don't update deleted organizations - nonDeletedOrgs := database.IsNull(o.DeletedAtColumn()) - conditions := []database.Condition{id, instanceIDCondition, nonDeletedOrgs} + conditions := []database.Condition{id, instanceIDCondition} writeCondition(&builder, database.And(conditions...)) return o.client.Exec(ctx, builder.String(), builder.Args()...) @@ -224,11 +216,6 @@ func (org) UpdatedAtColumn() database.Column { return database.NewColumn("updated_at") } -// DeletedAtColumn implements [domain.organizationColumns]. -func (org) DeletedAtColumn() database.Column { - return database.NewColumn("deleted_at") -} - func scanOrganization(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Organization, error) { rows, err := querier.Query(ctx, builder.String(), builder.Args()...) if err != nil { diff --git a/backend/v3/storage/database/repository/org_test.go b/backend/v3/storage/database/repository/org_test.go index 243ac4f0b4..3c23ba6ca5 100644 --- a/backend/v3/storage/database/repository/org_test.go +++ b/backend/v3/storage/database/repository/org_test.go @@ -247,7 +247,6 @@ func TestCreateOrganization(t *testing.T) { assert.Equal(t, tt.organization.State, organization.State) assert.WithinRange(t, organization.CreatedAt, beforeCreate, afterCreate) assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) - assert.Nil(t, organization.DeletedAt) }) } } @@ -400,7 +399,6 @@ func TestUpdateOrganization(t *testing.T) { assert.Equal(t, createdOrg.Name, organization.Name) assert.Equal(t, createdOrg.State, organization.State) assert.WithinRange(t, organization.UpdatedAt, beforeUpdate, afterUpdate) - assert.Nil(t, organization.DeletedAt) }) } } diff --git a/internal/query/projection/instance_relational.go b/internal/query/projection/instance_relational.go index 9101fd749b..a2edcfc60e 100644 --- a/internal/query/projection/instance_relational.go +++ b/internal/query/projection/instance_relational.go @@ -97,11 +97,8 @@ func (p *instanceRelationalProjection) reduceInstanceDelete(event eventstore.Eve if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-so2am1", "reduce.wrong.event.type %s", instance.InstanceChangedEventType) } - return handler.NewUpdateStatement( + return handler.NewDeleteStatement( e, - []handler.Column{ - handler.NewCol(DeletedAt, e.CreationDate()), - }, []handler.Condition{ handler.NewCond(InstanceColumnID, e.Aggregate().InstanceID), }, diff --git a/internal/query/projection/org_relational.go b/internal/query/projection/org_relational.go index 2250ab9351..d708c69ea1 100644 --- a/internal/query/projection/org_relational.go +++ b/internal/query/projection/org_relational.go @@ -171,12 +171,8 @@ func (p *orgRelationalProjection) reduceOrgRelationalRemoved(event eventstore.Ev if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-DGm9g", "reduce.wrong.event.type %s", org.OrgRemovedEventType) } - return handler.NewUpdateStatement( + return handler.NewDeleteStatement( e, - []handler.Column{ - handler.NewCol(UpdatedAt, e.CreationDate()), - handler.NewCol(DeletedAt, e.CreationDate()), - }, []handler.Condition{ handler.NewCond(OrgColumnID, e.Aggregate().ID), handler.NewCond(OrgColumnInstanceID, e.Aggregate().InstanceID), diff --git a/internal/query/projection/relational_common.go b/internal/query/projection/relational_common.go index 7b0d957661..bdb1fdd10e 100644 --- a/internal/query/projection/relational_common.go +++ b/internal/query/projection/relational_common.go @@ -4,5 +4,4 @@ const ( State = "state" CreatedAt = "created_at" UpdatedAt = "updated_at" - DeletedAt = "deleted_at" )