mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-10 16:15:12 +00:00
refactor(rt): add primary key columns to repo interfaces (#10843)
# Which Problems Are Solved Relational table's primary keys are part of the SQL table definition. However, by abstraction through the repository interfaces callers (service layer) have no clear understanding of the primary key definion. This might lead to confusion during implementation: - The repository makes a sanity check on required conditions before query execution and returns an error if the condition was not met. - When a Get method is executed, an error is returned if not exactly one row is found. Get replaces the legacy GetXxxByID methods we have on projections. However, a list of flexible conditions can be passed. - Operations like UPDATE and DELETE should often only affect a single row, identified by the primary key. If the primary key conditions are missing an error is returned. Because there is no "type safety" enforced here, the errors are returned during runtime. When all possible combinations of API arguments are not properly tested such errors are actually bugs in the experience of the end-user. (The API should do it's own input validation, but a programmer's error is easily made). # How the Problems Are Solved Add `PrimaryKeyColumns` and `PrimaryKeyCondition` to existing repository intefaces. # Additional Changes - Where reducers already used repositories, the PrimaryKeyConditions are used where possible. # Additional Context - Part of https://github.com/zitadel/zitadel/wiki/Decision-Log#expose-primary-key-definition-in-repository-interfaces --------- Co-authored-by: Marco A. <[email protected]>
This commit is contained in:
@@ -431,6 +431,14 @@ func (i *idProvider) GetSAML(ctx context.Context, client database.QueryExecutor,
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (i idProvider) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
i.InstanceIDColumn(),
|
||||
i.IDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func (idProvider) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn("identity_providers", "instance_id")
|
||||
}
|
||||
@@ -499,6 +507,13 @@ func (idProvider) UpdatedAtColumn() database.Column {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (i idProvider) PrimaryKeyCondition(instanceID, id string) database.Condition {
|
||||
return database.And(
|
||||
i.InstanceIDCondition(instanceID),
|
||||
i.IDCondition(id),
|
||||
)
|
||||
}
|
||||
|
||||
func (i idProvider) InstanceIDCondition(id string) database.Condition {
|
||||
return database.NewTextCondition(i.InstanceIDColumn(), database.TextOperationEqual, id)
|
||||
}
|
||||
|
||||
@@ -166,6 +166,10 @@ func (i instance) SetConsoleAppID(id string) database.Change {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (i instance) PrimaryKeyCondition(instanceID string) database.Condition {
|
||||
return i.IDCondition(instanceID)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.instanceConditions].
|
||||
func (i instance) IDCondition(id string) database.Condition {
|
||||
return database.NewTextCondition(i.IDColumn(), database.TextOperationEqual, id)
|
||||
@@ -204,6 +208,11 @@ func (i instance) ExistsDomain(cond database.Condition) database.Condition {
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (i instance) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{i.IDColumn()}
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.instanceColumns].
|
||||
func (i instance) IDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "id")
|
||||
|
||||
@@ -136,6 +136,10 @@ func (i instanceDomain) SetType(typ domain.DomainType) database.Change {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (i instanceDomain) PrimaryKeyCondition(domain string) database.Condition {
|
||||
return i.DomainCondition(database.TextOperationEqual, domain)
|
||||
}
|
||||
|
||||
// DomainCondition implements [domain.InstanceDomainRepository].
|
||||
func (i instanceDomain) DomainCondition(op database.TextOperation, domain string) database.Condition {
|
||||
return database.NewTextCondition(i.DomainColumn(), op, domain)
|
||||
@@ -160,6 +164,13 @@ func (i instanceDomain) TypeCondition(typ domain.DomainType) database.Condition
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (i instanceDomain) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
i.DomainColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// CreatedAtColumn implements [domain.InstanceDomainRepository].
|
||||
func (i instanceDomain) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "created_at")
|
||||
|
||||
@@ -148,6 +148,13 @@ func (o org) SetState(state domain.OrgState) database.Change {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (o org) PrimaryKeyCondition(instanceID, orgID string) database.Condition {
|
||||
return database.And(
|
||||
o.InstanceIDCondition(instanceID),
|
||||
o.IDCondition(orgID),
|
||||
)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.organizationConditions].
|
||||
func (o org) IDCondition(id string) database.Condition {
|
||||
return database.NewTextCondition(o.IDColumn(), database.TextOperationEqual, id)
|
||||
@@ -221,6 +228,14 @@ func (o org) ExistsMetadata(cond database.Condition) database.Condition {
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (o org) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
o.InstanceIDColumn(),
|
||||
o.IDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.organizationColumns].
|
||||
func (o org) IDColumn() database.Column {
|
||||
return database.NewColumn(o.unqualifiedTableName(), "id")
|
||||
|
||||
@@ -157,6 +157,14 @@ func (o orgDomain) SetUpdatedAt(updatedAt time.Time) database.Change {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (o orgDomain) PrimaryKeyCondition(instanceID, orgID, domain string) database.Condition {
|
||||
return database.And(
|
||||
o.InstanceIDCondition(instanceID),
|
||||
o.OrgIDCondition(orgID),
|
||||
o.DomainCondition(database.TextOperationEqual, domain),
|
||||
)
|
||||
}
|
||||
|
||||
// DomainCondition implements [domain.OrganizationDomainRepository].
|
||||
func (o orgDomain) DomainCondition(op database.TextOperation, domain string) database.Condition {
|
||||
return database.NewTextCondition(o.DomainColumn(), op, domain)
|
||||
@@ -187,6 +195,15 @@ func (o orgDomain) OrgIDCondition(orgID string) database.Condition {
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (o orgDomain) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
o.InstanceIDColumn(),
|
||||
o.OrgIDColumn(),
|
||||
o.DomainColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// CreatedAtColumn implements [domain.OrganizationDomainRepository].
|
||||
// Subtle: this method shadows the method ([domain.OrganizationRepository]).CreatedAtColumn of orgDomain.org.
|
||||
func (o orgDomain) CreatedAtColumn() database.Column {
|
||||
|
||||
@@ -3,6 +3,7 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
@@ -29,14 +30,10 @@ func checkRestrictingColumns(
|
||||
return nil
|
||||
}
|
||||
|
||||
type pkRepository interface {
|
||||
PrimaryKeyColumns() []database.Column
|
||||
}
|
||||
|
||||
// checkPKCondition checks if the Primary Key columns are part of the condition.
|
||||
// This can ensure only a single row is affected by updates and deletes.
|
||||
func checkPKCondition(
|
||||
repo pkRepository,
|
||||
repo domain.Repository,
|
||||
condition database.Condition,
|
||||
) error {
|
||||
return checkRestrictingColumns(
|
||||
|
||||
@@ -135,6 +135,13 @@ func (u user) SetUsername(username string) database.Change {
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (u user) PrimaryKeyCondition(instanceID, userID string) database.Condition {
|
||||
return database.And(
|
||||
u.InstanceIDCondition(instanceID),
|
||||
u.IDCondition(userID),
|
||||
)
|
||||
}
|
||||
|
||||
// InstanceIDCondition implements [domain.userConditions].
|
||||
func (u user) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
@@ -182,6 +189,14 @@ func (u user) DeletedAtCondition(op database.NumberOperation, deletedAt time.Tim
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (u user) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
u.InstanceIDColumn(),
|
||||
u.IDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// InstanceIDColumn implements [domain.userColumns].
|
||||
func (user) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn("users", "instance_id")
|
||||
|
||||
Reference in New Issue
Block a user