Files
zitadel/backend/v3/storage/database/operators.go

137 lines
4.2 KiB
Go
Raw Normal View History

feat(backend): state persisted objects (#9870) This PR initiates the rework of Zitadel's backend to state-persisted objects. This change is a step towards a more scalable and maintainable architecture. ## Changes * **New `/backend/v3` package**: A new package structure has been introduced to house the reworked backend logic. This includes: * `domain`: Contains the core business logic, commands, and repository interfaces. * `storage`: Implements the repository interfaces for database interactions with new transactional tables. * `telemetry`: Provides logging and tracing capabilities. * **Transactional Tables**: New database tables have been defined for `instances`, `instance_domains`, `organizations`, and `org_domains`. * **Projections**: New projections have been created to populate the new relational tables from the existing event store, ensuring data consistency during the migration. * **Repositories**: New repositories provide an abstraction layer for accessing and manipulating the data in the new tables. * **Setup**: A new setup step for `TransactionalTables` has been added to manage the database migrations for the new tables. This PR lays the foundation for future work to fully transition to state-persisted objects for these components, which will improve performance and simplify data access patterns. This PR initiates the rework of ZITADEL's backend to state-persisted objects. This is a foundational step towards a new architecture that will improve performance and maintainability. The following objects are migrated from event-sourced aggregates to state-persisted objects: * Instances * incl. Domains * Orgs * incl. Domains The structure of the new backend implementation follows the software architecture defined in this [wiki page](https://github.com/zitadel/zitadel/wiki/Software-Architecturel). This PR includes: * The initial implementation of the new transactional repositories for the objects listed above. * Projections to populate the new relational tables from the existing event store. * Adjustments to the build and test process to accommodate the new backend structure. This is a work in progress and further changes will be made to complete the migration. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Iraq Jaber <iraq+github@zitadel.com> Co-authored-by: Iraq <66622793+kkrime@users.noreply.github.com> Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
2025-09-05 10:54:34 +02:00
package database
import (
"time"
"golang.org/x/exp/constraints"
)
type Value interface {
Boolean | Number | Text | Instruction
}
type Operation interface {
BooleanOperation | NumberOperation | TextOperation
}
type Text interface {
~string | ~[]byte
}
// TextOperation are operations that can be performed on text values.
type TextOperation uint8
const (
// TextOperationEqual compares two strings for equality.
TextOperationEqual TextOperation = iota + 1
// TextOperationEqualIgnoreCase compares two strings for equality, ignoring case.
TextOperationEqualIgnoreCase
// TextOperationNotEqual compares two strings for inequality.
TextOperationNotEqual
// TextOperationNotEqualIgnoreCase compares two strings for inequality, ignoring case.
TextOperationNotEqualIgnoreCase
// TextOperationStartsWith checks if the first string starts with the second.
TextOperationStartsWith
// TextOperationStartsWithIgnoreCase checks if the first string starts with the second, ignoring case.
TextOperationStartsWithIgnoreCase
)
var textOperations = map[TextOperation]string{
TextOperationEqual: " = ",
TextOperationEqualIgnoreCase: " LIKE ",
TextOperationNotEqual: " <> ",
TextOperationNotEqualIgnoreCase: " NOT LIKE ",
TextOperationStartsWith: " LIKE ",
TextOperationStartsWithIgnoreCase: " LIKE ",
}
func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value T) {
switch op {
case TextOperationEqual, TextOperationNotEqual:
col.WriteQualified(builder)
builder.WriteString(textOperations[op])
builder.WriteArg(value)
case TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
builder.WriteString("LOWER(")
col.WriteQualified(builder)
builder.WriteString(")")
builder.WriteString(textOperations[op])
builder.WriteString("LOWER(")
builder.WriteArg(value)
builder.WriteString(")")
case TextOperationStartsWith:
col.WriteQualified(builder)
builder.WriteString(textOperations[op])
builder.WriteArg(value)
builder.WriteString(" || '%'")
case TextOperationStartsWithIgnoreCase:
builder.WriteString("LOWER(")
col.WriteQualified(builder)
builder.WriteString(")")
builder.WriteString(textOperations[op])
builder.WriteString("LOWER(")
builder.WriteArg(value)
builder.WriteString(")")
builder.WriteString(" || '%'")
default:
panic("unsupported text operation")
}
}
type Number interface {
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
}
// NumberOperation are operations that can be performed on number values.
type NumberOperation uint8
const (
// NumberOperationEqual compares two numbers for equality.
NumberOperationEqual NumberOperation = iota + 1
// NumberOperationNotEqual compares two numbers for inequality.
NumberOperationNotEqual
// NumberOperationLessThan compares two numbers to check if the first is less than the second.
NumberOperationLessThan
// NumberOperationLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
NumberOperationAtLeast
// NumberOperationGreaterThan compares two numbers to check if the first is greater than the second.
NumberOperationGreaterThan
// NumberOperationGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
NumberOperationAtMost
)
var numberOperations = map[NumberOperation]string{
NumberOperationEqual: " = ",
NumberOperationNotEqual: " <> ",
NumberOperationLessThan: " < ",
NumberOperationAtLeast: " <= ",
NumberOperationGreaterThan: " > ",
NumberOperationAtMost: " >= ",
}
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value T) {
col.WriteQualified(builder)
builder.WriteString(numberOperations[op])
builder.WriteArg(value)
}
type Boolean interface {
~bool
}
// BooleanOperation are operations that can be performed on boolean values.
type BooleanOperation uint8
const (
BooleanOperationIsTrue BooleanOperation = iota + 1
BooleanOperationIsFalse
)
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value T) {
col.WriteQualified(builder)
builder.WriteString(" = ")
builder.WriteArg(value)
}