Files
zitadel/backend/v3/storage/database/operators.go
T
5df8a97f9d feat(repository): implement user repository (#10967)
## Summary

This PR introduces the relational user repository for backend v3,
including schema, repository APIs, reducers/projections, and tests for
both human and machine users.

## What changed

* Added user-focused relational migrations (users, verifications,
metadata, PATs, machine keys, passkeys, identity provider links) and
related FK wiring.
* Implemented/expanded repository domain contracts and storage
implementations for human and machine user flows.
* Added support for machine secret handling and aligned access token
type modeling.
* Extended user domain models (verifications, password/email/phone/TOTP
state, passkeys, metadata, invite handling, identity provider links).
* Updated projection/reduction coverage and integration tests for key
user lifecycle events.
* Regenerated and updated mocks/enums plus supporting database
change/column utilities.

## Notes for reviewers

* This PR is large because it establishes the foundational relational
user storage layer and related test scaffolding. Sorry for that.

## Closes

Closes #10218
Closes #10224
Closes #10225
Closes #10209
Closes #10802

---------

Co-authored-by: Iraq Jaber <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Marco A. <[email protected]>
Co-authored-by: Tim Möhlmann <[email protected]>
2026-02-23 16:03:47 +01:00

187 lines
6.0 KiB
Go

package database
import (
"fmt"
"time"
"golang.org/x/exp/constraints"
)
type wrappedValue[V Value] struct {
value V
fn function
}
func LowerValue[T Value](v T) wrappedValue[T] {
return wrappedValue[T]{value: v, fn: functionLower}
}
func SHA256Value[T Value](v T) wrappedValue[T] {
return wrappedValue[T]{value: v, fn: functionSHA256}
}
func (b wrappedValue[V]) WriteArg(builder *StatementBuilder) {
builder.Grow(len(b.fn) + 5)
builder.WriteString(string(b.fn))
builder.WriteRune('(')
builder.WriteArg(b.value)
builder.WriteRune(')')
}
var _ argWriter = (*wrappedValue[string])(nil)
type Value interface {
Boolean | Number | Text | Instruction | Bytes | any
}
//go:generate enumer -type NumberOperation,TextOperation,BytesOperation -linecomment -output ./operators_enumer.go
type Operation interface {
NumberOperation | TextOperation | BytesOperation
}
type Text interface {
~string | Bytes
}
// TextOperation are operations that can be performed on text values.
type TextOperation uint8
const (
// TextOperationEqual compares two strings for equality.
TextOperationEqual TextOperation = iota + 1 // =
// TextOperationNotEqual compares two strings for inequality.
TextOperationNotEqual // <>
// TextOperationStartsWith checks if the first string starts with the second.
TextOperationStartsWith // LIKE
// TextOperationContains checks if the first string contains the second
TextOperationContains // LIKE
// TextOperationEndsWith checks if the first string ends with the second.
TextOperationEndsWith // LIKE
// TextOperationContainsIgnoreCase checks if the first string contains the second ignoring case
TextOperationContainsIgnoreCase // LIKE
// TextOperationEndsWithIgnoreCase checks if the first string ends with the second ignoring case
TextOperationEndsWithIgnoreCase // LIKE
// TextOperationStartsWithIgnoreCase checks if the first string starts with the second ignoring case
TextOperationStartsWithIgnoreCase // LIKE
// TextOperationEqualIgnoreCase compares two strings for equality ignoring case
TextOperationEqualIgnoreCase // =
// TextOperationNotEqualIgnoreCase compares two strings for inequality ignoring case
TextOperationNotEqualIgnoreCase // <>
)
func (op TextOperation) IsIgnoreCaseOperation() bool {
return op == TextOperationContainsIgnoreCase ||
op == TextOperationEndsWithIgnoreCase ||
op == TextOperationStartsWithIgnoreCase ||
op == TextOperationEqualIgnoreCase ||
op == TextOperationNotEqualIgnoreCase
}
func (op TextOperation) Value() TextOperation {
switch op {
case TextOperationStartsWith, TextOperationContains, TextOperationEndsWith, TextOperationEqual, TextOperationNotEqual:
return op
case TextOperationContainsIgnoreCase:
return TextOperationContains
case TextOperationEndsWithIgnoreCase:
return TextOperationEndsWith
case TextOperationStartsWithIgnoreCase:
return TextOperationStartsWith
case TextOperationEqualIgnoreCase:
return TextOperationEqual
case TextOperationNotEqualIgnoreCase:
return TextOperationNotEqual
default:
panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op))
}
}
func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value any) {
switch op {
case TextOperationContains:
writeOperation[T](builder, col, op.String()+" '%' ||", value)
builder.WriteString(" || '%'")
case TextOperationEndsWith:
writeOperation[T](builder, col, op.String()+" '%' ||", value)
case TextOperationEqual, TextOperationNotEqual:
writeOperation[T](builder, col, op.String(), value)
case TextOperationStartsWith:
writeOperation[T](builder, col, op.String(), value)
builder.WriteString(" || '%'")
case TextOperationContainsIgnoreCase, TextOperationEndsWithIgnoreCase, TextOperationStartsWithIgnoreCase, TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
fallthrough
default:
panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op))
}
}
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.
NumberOperationLessThanOrEqual // <=
// 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.
NumberOperationGreaterThanOrEqual // >=
)
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value any) {
writeOperation[T](builder, col, op.String(), value)
}
type Boolean interface {
~bool
}
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value any) {
writeOperation[T](builder, col, "=", value)
}
type Bytes interface {
~[]byte
}
// BytesOperation are operations that can be performed on bytea values.
type BytesOperation uint8
const (
BytesOperationEqual BytesOperation = iota + 1 // =
BytesOperationNotEqual // <>
)
func writeBytesOperation[T Bytes](builder *StatementBuilder, col Column, op BytesOperation, value any) {
writeOperation[T](builder, col, op.String(), value)
}
func writeOperation[V Value](builder *StatementBuilder, col Column, op string, value any) {
if op == "" {
panic("unsupported operation")
}
switch value.(type) {
case V, wrappedValue[V], *wrappedValue[V]:
default:
panic("unsupported value type")
}
col.WriteQualified(builder)
builder.WriteRune(' ')
builder.WriteString(op)
builder.WriteRune(' ')
builder.WriteArg(value)
}