move files

This commit is contained in:
adlerhurst
2025-05-06 07:18:11 +02:00
parent 050aa7dd48
commit 6ba86bc67b
21 changed files with 916 additions and 976 deletions

View File

@@ -0,0 +1,51 @@
package database
type Change interface {
Write(builder *StatementBuilder)
}
type change[V Value] struct {
column Column
value V
}
var _ Change = (*change[string])(nil)
func NewChange[V Value](col Column, value V) Change {
return &change[V]{
column: col,
value: value,
}
}
func NewChangePtr[V Value](col Column, value *V) Change {
if value == nil {
return NewChange(col, NullInstruction)
}
return NewChange(col, *value)
}
// Write implements [Change].
func (c change[V]) Write(builder *StatementBuilder) {
c.column.Write(builder)
builder.WriteString(" = ")
builder.WriteArg(c.value)
}
type Changes []Change
func NewChanges(cols ...Change) Change {
return Changes(cols)
}
// Write implements [Change].
func (m Changes) Write(builder *StatementBuilder) {
for i, col := range m {
if i > 0 {
builder.WriteString(", ")
}
col.Write(builder)
}
}
var _ Change = Changes(nil)

View File

@@ -0,0 +1,55 @@
package database
type Columns []Column
// Write implements [Column].
func (m Columns) Write(builder *StatementBuilder) {
for i, col := range m {
if i > 0 {
builder.WriteString(", ")
}
col.Write(builder)
}
}
type Column interface {
Write(builder *StatementBuilder)
}
type column struct {
name string
}
func NewColumn(name string) Column {
return column{name: name}
}
// Write implements [Column].
func (c column) Write(builder *StatementBuilder) {
builder.WriteString(c.name)
}
var _ Column = (*column)(nil)
type ignoreCaseColumn interface {
Column
WriteIgnoreCase(builder *StatementBuilder)
}
func NewIgnoreCaseColumn(name, suffix string) ignoreCaseColumn {
return ignoreCaseCol{
column: column{name: name},
suffix: suffix,
}
}
type ignoreCaseCol struct {
column
suffix string
}
// WriteIgnoreCase implements [ignoreCaseColumn].
func (c ignoreCaseCol) WriteIgnoreCase(builder *StatementBuilder) {
c.column.Write(builder)
builder.WriteString(c.suffix)
}

View File

@@ -1,15 +1,15 @@
package v4
package database
type Condition interface {
writeTo(builder *statementBuilder)
Write(builder *StatementBuilder)
}
type and struct {
conditions []Condition
}
// writeTo implements [Condition].
func (a *and) writeTo(builder *statementBuilder) {
// Write implements [Condition].
func (a *and) Write(builder *StatementBuilder) {
if len(a.conditions) > 1 {
builder.WriteString("(")
defer builder.WriteString(")")
@@ -18,7 +18,7 @@ func (a *and) writeTo(builder *statementBuilder) {
if i > 0 {
builder.WriteString(" AND ")
}
condition.writeTo(builder)
condition.(Condition).Write(builder)
}
}
@@ -32,8 +32,8 @@ type or struct {
conditions []Condition
}
// writeTo implements [Condition].
func (o *or) writeTo(builder *statementBuilder) {
// Write implements [Condition].
func (o *or) Write(builder *StatementBuilder) {
if len(o.conditions) > 1 {
builder.WriteString("(")
defer builder.WriteString(")")
@@ -42,7 +42,7 @@ func (o *or) writeTo(builder *statementBuilder) {
if i > 0 {
builder.WriteString(" OR ")
}
condition.writeTo(builder)
condition.(Condition).Write(builder)
}
}
@@ -56,9 +56,9 @@ type isNull struct {
column Column
}
// writeTo implements [Condition].
func (i *isNull) writeTo(builder *statementBuilder) {
i.column.writeTo(builder)
// Write implements [Condition].
func (i *isNull) Write(builder *StatementBuilder) {
i.column.Write(builder)
builder.WriteString(" IS NULL")
}
@@ -72,40 +72,40 @@ type isNotNull struct {
column Column
}
// writeTo implements [Condition].
func (i *isNotNull) writeTo(builder *statementBuilder) {
i.column.writeTo(builder)
// Write implements [Condition].
func (i *isNotNull) Write(builder *StatementBuilder) {
i.column.Write(builder)
builder.WriteString(" IS NOT NULL")
}
func IsNotNull(column Column) *isNotNull {
return &isNotNull{column: column}
return &isNotNull{column: column.(Column)}
}
var _ Condition = (*isNotNull)(nil)
type valueCondition func(builder *statementBuilder)
type valueCondition func(builder *StatementBuilder)
func newTextCondition[V Text](col Column, op TextOperator, value V) Condition {
return valueCondition(func(builder *statementBuilder) {
func NewTextCondition[V Text](col Column, op TextOperation, value V) Condition {
return valueCondition(func(builder *StatementBuilder) {
writeTextOperation(builder, col, op, value)
})
}
func newNumberCondition[V Number](col Column, op NumberOperator, value V) Condition {
return valueCondition(func(builder *statementBuilder) {
func NewNumberCondition[V Number](col Column, op NumberOperation, value V) Condition {
return valueCondition(func(builder *StatementBuilder) {
writeNumberOperation(builder, col, op, value)
})
}
func newBooleanCondition[V Boolean](col Column, value V) Condition {
return valueCondition(func(builder *statementBuilder) {
func NewBooleanCondition[V Boolean](col Column, value V) Condition {
return valueCondition(func(builder *StatementBuilder) {
writeBooleanOperation(builder, col, value)
})
}
// writeTo implements [Condition].
func (c valueCondition) writeTo(builder *statementBuilder) {
// Write implements [Condition].
func (c valueCondition) Write(builder *StatementBuilder) {
c(builder)
}

View File

@@ -0,0 +1,139 @@
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
}
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.Write(builder)
builder.WriteString(textOperations[op])
builder.WriteString(builder.AppendArg(value))
case TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
ignoreCaseCol.WriteIgnoreCase(builder)
} else {
builder.WriteString("LOWER(")
col.Write(builder)
builder.WriteString(")")
}
builder.WriteString(textOperations[op])
builder.WriteString("LOWER(")
builder.WriteString(builder.AppendArg(value))
builder.WriteString(")")
case TextOperationStartsWith:
col.Write(builder)
builder.WriteString(textOperations[op])
builder.WriteString(builder.AppendArg(value))
builder.WriteString(" || '%'")
case TextOperationStartsWithIgnoreCase:
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
ignoreCaseCol.WriteIgnoreCase(builder)
} else {
builder.WriteString("LOWER(")
col.Write(builder)
builder.WriteString(")")
}
builder.WriteString(textOperations[op])
builder.WriteString("LOWER(")
builder.WriteString(builder.AppendArg(value))
builder.WriteString(")")
builder.WriteString(" || '%'")
default:
panic("unsupported text operation")
}
}
type Number interface {
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
}
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.Write(builder)
builder.WriteString(numberOperations[op])
builder.WriteString(builder.AppendArg(value))
}
type Boolean interface {
~bool
}
type BooleanOperation uint8
const (
BooleanOperationIsTrue BooleanOperation = iota + 1
BooleanOperationIsFalse
)
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value T) {
col.Write(builder)
builder.WriteString(" IS ")
builder.WriteString(builder.AppendArg(value))
}

View File

@@ -0,0 +1,66 @@
package database
type QueryOption func(opts *QueryOpts)
func WithCondition(condition Condition) QueryOption {
return func(opts *QueryOpts) {
opts.Condition = condition
}
}
func WithOrderBy(orderBy ...Column) QueryOption {
return func(opts *QueryOpts) {
opts.OrderBy = orderBy
}
}
func WithLimit(limit uint32) QueryOption {
return func(opts *QueryOpts) {
opts.Limit = limit
}
}
func WithOffset(offset uint32) QueryOption {
return func(opts *QueryOpts) {
opts.Offset = offset
}
}
type QueryOpts struct {
Condition Condition
OrderBy Columns
Limit uint32
Offset uint32
}
func (opts *QueryOpts) WriteCondition(builder *StatementBuilder) {
if opts.Condition == nil {
return
}
builder.WriteString(" WHERE ")
opts.Condition.Write(builder)
}
func (opts *QueryOpts) WriteOrderBy(builder *StatementBuilder) {
if len(opts.OrderBy) == 0 {
return
}
builder.WriteString(" ORDER BY ")
Columns(opts.OrderBy).Write(builder)
}
func (opts *QueryOpts) WriteLimit(builder *StatementBuilder) {
if opts.Limit == 0 {
return
}
builder.WriteString(" LIMIT ")
builder.WriteArg(opts.Limit)
}
func (opts *QueryOpts) WriteOffset(builder *StatementBuilder) {
if opts.Offset == 0 {
return
}
builder.WriteString(" OFFSET ")
builder.WriteArg(opts.Offset)
}

View File

@@ -1,89 +0,0 @@
package v4
type Change interface {
Column
}
type change[V Value] struct {
column Column
value V
}
func newChange[V Value](col Column, value V) Change {
return &change[V]{
column: col,
value: value,
}
}
func newUpdatePtrColumn[V Value](col Column, value *V) Change {
if value == nil {
return newChange(col, nullDBInstruction)
}
return newChange(col, *value)
}
// writeTo implements [Change].
func (c change[V]) writeTo(builder *statementBuilder) {
c.column.writeTo(builder)
builder.WriteString(" = ")
builder.writeArg(c.value)
}
type Changes []Change
func newChanges(cols ...Change) Change {
return Changes(cols)
}
// writeTo implements [Change].
func (m Changes) writeTo(builder *statementBuilder) {
for i, col := range m {
if i > 0 {
builder.WriteString(", ")
}
col.writeTo(builder)
}
}
var _ Change = Changes(nil)
var _ Change = (*change[string])(nil)
type Columns []Column
func (m Columns) writeTo(builder *statementBuilder) {
for i, col := range m {
if i > 0 {
builder.WriteString(", ")
}
col.writeTo(builder)
}
}
type Column interface {
writeTo(builder *statementBuilder)
}
type column struct {
name string
}
func (c column) writeTo(builder *statementBuilder) {
builder.WriteString(c.name)
}
type ignoreCaseColumn interface {
Column
writeIgnoreCaseTo(builder *statementBuilder)
}
type ignoreCaseCol struct {
column
suffix string
}
func (c ignoreCaseCol) writeIgnoreCaseTo(builder *statementBuilder) {
c.column.writeTo(builder)
builder.WriteString(c.suffix)
}

View File

@@ -1,139 +0,0 @@
package v4
import (
"time"
"golang.org/x/exp/constraints"
)
type Value interface {
Boolean | Number | Text | databaseInstruction
}
type Operator interface {
BooleanOperator | NumberOperator | TextOperator
}
type Text interface {
~string | ~[]byte
}
type TextOperator uint8
const (
// TextOperatorEqual compares two strings for equality.
TextOperatorEqual TextOperator = iota + 1
// TextOperatorEqualIgnoreCase compares two strings for equality, ignoring case.
TextOperatorEqualIgnoreCase
// TextOperatorNotEqual compares two strings for inequality.
TextOperatorNotEqual
// TextOperatorNotEqualIgnoreCase compares two strings for inequality, ignoring case.
TextOperatorNotEqualIgnoreCase
// TextOperatorStartsWith checks if the first string starts with the second.
TextOperatorStartsWith
// TextOperatorStartsWithIgnoreCase checks if the first string starts with the second, ignoring case.
TextOperatorStartsWithIgnoreCase
)
var textOperators = map[TextOperator]string{
TextOperatorEqual: " = ",
TextOperatorEqualIgnoreCase: " LIKE ",
TextOperatorNotEqual: " <> ",
TextOperatorNotEqualIgnoreCase: " NOT LIKE ",
TextOperatorStartsWith: " LIKE ",
TextOperatorStartsWithIgnoreCase: " LIKE ",
}
func writeTextOperation[T Text](builder *statementBuilder, col Column, op TextOperator, value T) {
switch op {
case TextOperatorEqual, TextOperatorNotEqual:
col.writeTo(builder)
builder.WriteString(textOperators[op])
builder.WriteString(builder.appendArg(value))
case TextOperatorEqualIgnoreCase, TextOperatorNotEqualIgnoreCase:
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
ignoreCaseCol.writeIgnoreCaseTo(builder)
} else {
builder.WriteString("LOWER(")
col.writeTo(builder)
builder.WriteString(")")
}
builder.WriteString(textOperators[op])
builder.WriteString("LOWER(")
builder.WriteString(builder.appendArg(value))
builder.WriteString(")")
case TextOperatorStartsWith:
col.writeTo(builder)
builder.WriteString(textOperators[op])
builder.WriteString(builder.appendArg(value))
builder.WriteString(" || '%'")
case TextOperatorStartsWithIgnoreCase:
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
ignoreCaseCol.writeIgnoreCaseTo(builder)
} else {
builder.WriteString("LOWER(")
col.writeTo(builder)
builder.WriteString(")")
}
builder.WriteString(textOperators[op])
builder.WriteString("LOWER(")
builder.WriteString(builder.appendArg(value))
builder.WriteString(")")
builder.WriteString(" || '%'")
default:
panic("unsupported text operation")
}
}
type Number interface {
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
}
type NumberOperator uint8
const (
// NumberOperatorEqual compares two numbers for equality.
NumberOperatorEqual NumberOperator = iota + 1
// NumberOperatorNotEqual compares two numbers for inequality.
NumberOperatorNotEqual
// NumberOperatorLessThan compares two numbers to check if the first is less than the second.
NumberOperatorLessThan
// NumberOperatorLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
NumberOperatorAtLeast
// NumberOperatorGreaterThan compares two numbers to check if the first is greater than the second.
NumberOperatorGreaterThan
// NumberOperatorGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
NumberOperatorAtMost
)
var numberOperators = map[NumberOperator]string{
NumberOperatorEqual: " = ",
NumberOperatorNotEqual: " <> ",
NumberOperatorLessThan: " < ",
NumberOperatorAtLeast: " <= ",
NumberOperatorGreaterThan: " > ",
NumberOperatorAtMost: " >= ",
}
func writeNumberOperation[T Number](builder *statementBuilder, col Column, op NumberOperator, value T) {
col.writeTo(builder)
builder.WriteString(numberOperators[op])
builder.WriteString(builder.appendArg(value))
}
type Boolean interface {
~bool
}
type BooleanOperator uint8
const (
BooleanOperatorIsTrue BooleanOperator = iota + 1
BooleanOperatorIsFalse
)
func writeBooleanOperation[T Boolean](builder *statementBuilder, col Column, value T) {
col.writeTo(builder)
builder.WriteString(" IS ")
builder.WriteString(builder.appendArg(value))
}

View File

@@ -4,7 +4,6 @@ type Org struct {
InstanceID string
ID string
Name string
Dates
}
type GetOrg struct{}

View File

@@ -1,66 +0,0 @@
package v4
type queryOpts struct {
condition Condition
orderBy Columns
limit uint32
offset uint32
}
func (opts *queryOpts) writeCondition(builder *statementBuilder) {
if opts.condition == nil {
return
}
builder.WriteString(" WHERE ")
opts.condition.writeTo(builder)
}
func (opts *queryOpts) writeOrderBy(builder *statementBuilder) {
if len(opts.orderBy) == 0 {
return
}
builder.WriteString(" ORDER BY ")
opts.orderBy.writeTo(builder)
}
func (opts *queryOpts) writeLimit(builder *statementBuilder) {
if opts.limit == 0 {
return
}
builder.WriteString(" LIMIT ")
builder.writeArg(opts.limit)
}
func (opts *queryOpts) writeOffset(builder *statementBuilder) {
if opts.offset == 0 {
return
}
builder.WriteString(" OFFSET ")
builder.writeArg(opts.offset)
}
type QueryOption func(*queryOpts)
func WithCondition(condition Condition) QueryOption {
return func(opts *queryOpts) {
opts.condition = condition
}
}
func WithOrderBy(orderBy ...Column) QueryOption {
return func(opts *queryOpts) {
opts.orderBy = orderBy
}
}
func WithLimit(limit uint32) QueryOption {
return func(opts *queryOpts) {
opts.limit = limit
}
}
func WithOffset(offset uint32) QueryOption {
return func(opts *queryOpts) {
opts.offset = offset
}
}

View File

@@ -1,46 +0,0 @@
package v4
import (
"strconv"
"strings"
)
type databaseInstruction string
const (
nowDBInstruction databaseInstruction = "NOW()"
nullDBInstruction databaseInstruction = "NULL"
)
type statementBuilder struct {
strings.Builder
args []any
existingArgs map[any]string
}
func (b *statementBuilder) writeArg(arg any) {
b.WriteString(b.appendArg(arg))
}
func (b *statementBuilder) appendArg(arg any) (placeholder string) {
if b.existingArgs == nil {
b.existingArgs = make(map[any]string)
}
if placeholder, ok := b.existingArgs[arg]; ok {
return placeholder
}
if instruction, ok := arg.(databaseInstruction); ok {
return string(instruction)
}
b.args = append(b.args, arg)
placeholder = "$" + strconv.Itoa(len(b.args))
b.existingArgs[arg] = placeholder
return placeholder
}
func (b *statementBuilder) appendArgs(args ...any) {
for _, arg := range args {
b.appendArg(arg)
}
}

View File

@@ -4,59 +4,55 @@ import (
"context"
"time"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type Dates struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
type User struct {
InstanceID string
OrgID string
ID string
Username string
Traits userTrait
Dates
}
type UserType string
type userTrait interface {
userTrait()
Type() UserType
}
const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
` FROM users_view`
type user struct {
builder statementBuilder
builder database.StatementBuilder
client database.QueryExecutor
}
func UserRepository(client database.QueryExecutor) *user {
func UserRepository(client database.QueryExecutor) domain.UserRepository {
return &user{
client: client,
}
}
func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, err error) {
options := new(queryOpts)
var _ domain.UserRepository = (*user)(nil)
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
// Human implements [domain.UserRepository].
func (u *user) Human() domain.HumanRepository {
return &userHuman{user: u}
}
// Machine implements [domain.UserRepository].
func (u *user) Machine() domain.MachineRepository {
return &userMachine{user: u}
}
// List implements [domain.UserRepository].
func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []*domain.User, err error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
u.builder.WriteString(queryUserStmt)
options.writeCondition(&u.builder)
options.writeOrderBy(&u.builder)
options.writeLimit(&u.builder)
options.writeOffset(&u.builder)
options.WriteCondition(&u.builder)
options.WriteOrderBy(&u.builder)
options.WriteLimit(&u.builder)
options.WriteOffset(&u.builder)
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.args...)
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.Args()...)
if err != nil {
return nil, err
}
@@ -80,142 +76,157 @@ func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, er
return users, nil
}
func (u *user) Get(ctx context.Context, opts ...QueryOption) (*User, error) {
options := new(queryOpts)
// Get implements [domain.UserRepository].
func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.User, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
u.builder.WriteString(queryUserStmt)
options.writeCondition(&u.builder)
options.writeOrderBy(&u.builder)
options.writeLimit(&u.builder)
options.writeOffset(&u.builder)
options.WriteCondition(&u.builder)
options.WriteOrderBy(&u.builder)
options.WriteLimit(&u.builder)
options.WriteOffset(&u.builder)
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.args...))
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...))
}
const (
// TODO: change to separate statements and tables
createUserCte = `WITH user AS (` +
`INSERT INTO users (instance_id, org_id, id, username, type) VALUES ($1, $2, $3, $4, $5)` +
` RETURNING *)`
createHumanStmt = createUserCte + ` INSERT INTO user_humans h (instance_id, org_id, user_id, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
` SELECT u.instance_id, u.org_id, u.id, $6, $7, $8, $9, $10, $11` +
` FROM user u` +
` RETURNING u.created_at, u.updated_at, u.deleted_at`
createMachineStmt = createUserCte + ` INSERT INTO user_machines (instance_id, org_id, user_id, description)` +
` SELECT u.instance_id, u.org_id, u.id, $6` +
` FROM user u` +
` RETURNING u.created_at, u.updated_at`
createHumanStmt = `INSERT INTO human_users (instance_id, org_id, user_id, username, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` +
` RETURNING created_at, updated_at`
createMachineStmt = `INSERT INTO user_machines (instance_id, org_id, user_id, username, description)` +
` VALUES ($1, $2, $3, $4, $5)` +
` RETURNING created_at, updated_at`
)
func (u *user) Create(ctx context.Context, user *User) error {
u.builder.appendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
// Create implements [domain.UserRepository].
func (u *user) Create(ctx context.Context, user *domain.User) error {
u.builder.AppendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
switch trait := user.Traits.(type) {
case *Human:
case *domain.Human:
u.builder.WriteString(createHumanStmt)
u.builder.appendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
case *Machine:
u.builder.AppendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
case *domain.Machine:
u.builder.WriteString(createMachineStmt)
u.builder.appendArgs(trait.Description)
u.builder.AppendArgs(trait.Description)
}
return u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan(&user.Dates.CreatedAt, &user.Dates.UpdatedAt)
return u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
}
func (u *user) Update(ctx context.Context, condition Condition, changes ...Change) error {
u.builder.WriteString("UPDATE users SET ")
Changes(changes).writeTo(&u.builder)
u.writeCondition(condition)
return u.client.Exec(ctx, u.builder.String(), u.builder.args...)
}
func (u *user) Delete(ctx context.Context, condition Condition) error {
// Delete implements [domain.UserRepository].
func (u *user) Delete(ctx context.Context, condition database.Condition) error {
u.builder.WriteString("DELETE FROM users")
u.writeCondition(condition)
return u.client.Exec(ctx, u.builder.String(), u.builder.args...)
return u.client.Exec(ctx, u.builder.String(), u.builder.Args()...)
}
func (u *user) InstanceIDColumn() Column {
return column{name: "instance_id"}
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetUsername implements [domain.userChanges].
func (u user) SetUsername(username string) database.Change {
return database.NewChange(u.UsernameColumn(), username)
}
func (u *user) InstanceIDCondition(instanceID string) Condition {
return newTextCondition(u.InstanceIDColumn(), TextOperatorEqual, instanceID)
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// InstanceIDCondition implements [domain.userConditions].
func (u user) InstanceIDCondition(instanceID string) database.Condition {
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
}
func (u *user) OrgIDColumn() Column {
return column{name: "org_id"}
// OrgIDCondition implements [domain.userConditions].
func (u user) OrgIDCondition(orgID string) database.Condition {
return database.NewTextCondition(u.OrgIDColumn(), database.TextOperationEqual, orgID)
}
func (u *user) OrgIDCondition(orgID string) Condition {
return newTextCondition(u.OrgIDColumn(), TextOperatorEqual, orgID)
// IDCondition implements [domain.userConditions].
func (u user) IDCondition(userID string) database.Condition {
return database.NewTextCondition(u.IDColumn(), database.TextOperationEqual, userID)
}
func (u *user) IDColumn() Column {
return column{name: "id"}
// UsernameCondition implements [domain.userConditions].
func (u user) UsernameCondition(op database.TextOperation, username string) database.Condition {
return database.NewTextCondition(u.UsernameColumn(), op, username)
}
func (u *user) IDCondition(userID string) Condition {
return newTextCondition(u.IDColumn(), TextOperatorEqual, userID)
// CreatedAtCondition implements [domain.userConditions].
func (u user) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
return database.NewNumberCondition(u.CreatedAtColumn(), op, createdAt)
}
func (u *user) UsernameColumn() Column {
return ignoreCaseCol{
column: column{name: "username"},
suffix: "_lower",
}
// UpdatedAtCondition implements [domain.userConditions].
func (u user) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
return database.NewNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
}
func (u user) SetUsername(username string) Change {
return newChange(u.UsernameColumn(), username)
}
func (u *user) UsernameCondition(op TextOperator, username string) Condition {
return newTextCondition(u.UsernameColumn(), op, username)
}
func (u *user) CreatedAtColumn() Column {
return column{name: "created_at"}
}
func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condition {
return newNumberCondition(u.CreatedAtColumn(), op, createdAt)
}
func (u *user) UpdatedAtColumn() Column {
return column{name: "updated_at"}
}
func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condition {
return newNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
}
func (u *user) DeletedAtColumn() Column {
return column{name: "deleted_at"}
}
func (u *user) DeletedCondition(isDeleted bool) Condition {
// DeletedCondition implements [domain.userConditions].
func (u user) DeletedCondition(isDeleted bool) database.Condition {
if isDeleted {
return IsNotNull(u.DeletedAtColumn())
return database.IsNotNull(u.DeletedAtColumn())
}
return IsNull(u.DeletedAtColumn())
return database.IsNull(u.DeletedAtColumn())
}
func (u *user) DeletedAtCondition(op NumberOperator, deletedAt time.Time) Condition {
return newNumberCondition(u.DeletedAtColumn(), op, deletedAt)
// DeletedAtCondition implements [domain.userConditions].
func (u user) DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition {
return database.NewNumberCondition(u.DeletedAtColumn(), op, deletedAt)
}
func (u *user) writeCondition(condition Condition) {
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// InstanceIDColumn implements [domain.userColumns].
func (user) InstanceIDColumn() database.Column {
return database.NewColumn("instance_id")
}
// OrgIDColumn implements [domain.userColumns].
func (user) OrgIDColumn() database.Column {
return database.NewColumn("org_id")
}
// IDColumn implements [domain.userColumns].
func (user) IDColumn() database.Column {
return database.NewColumn("id")
}
// UsernameColumn implements [domain.userColumns].
func (user) UsernameColumn() database.Column {
return database.NewIgnoreCaseColumn("username", "_lower")
}
// FirstNameColumn implements [domain.userColumns].
func (user) CreatedAtColumn() database.Column {
return database.NewColumn("created_at")
}
// UpdatedAtColumn implements [domain.userColumns].
func (user) UpdatedAtColumn() database.Column {
return database.NewColumn("updated_at")
}
// DeletedAtColumn implements [domain.userColumns].
func (user) DeletedAtColumn() database.Column {
return database.NewColumn("deleted_at")
}
func (u *user) writeCondition(condition database.Condition) {
if condition == nil {
return
}
u.builder.WriteString(" WHERE ")
condition.writeTo(&u.builder)
condition.Write(&u.builder)
}
func (u user) columns() Columns {
return Columns{
func (u user) columns() database.Columns {
return database.Columns{
u.InstanceIDColumn(),
u.OrgIDColumn(),
u.IDColumn(),
@@ -226,14 +237,14 @@ func (u user) columns() Columns {
}
}
func scanUser(scanner database.Scanner) (*User, error) {
func scanUser(scanner database.Scanner) (*domain.User, error) {
var (
user User
human Human
email Email
phone Phone
machine Machine
typ UserType
user domain.User
human domain.Human
email domain.Email
phone domain.Phone
machine domain.Machine
typ domain.UserType
)
err := scanner.Scan(
&user.InstanceID,
@@ -241,9 +252,9 @@ func scanUser(scanner database.Scanner) (*User, error) {
&user.ID,
&user.Username,
&typ,
&user.Dates.CreatedAt,
&user.Dates.UpdatedAt,
&user.Dates.DeletedAt,
&user.CreatedAt,
&user.UpdatedAt,
&user.DeletedAt,
&human.FirstName,
&human.LastName,
&email.Address,
@@ -257,7 +268,7 @@ func scanUser(scanner database.Scanner) (*User, error) {
}
switch typ {
case UserTypeHuman:
case domain.UserTypeHuman:
if email.Address != "" {
human.Email = &email
}
@@ -265,7 +276,7 @@ func scanUser(scanner database.Scanner) (*User, error) {
human.Phone = &phone
}
user.Traits = &human
case UserTypeMachine:
case domain.UserTypeMachine:
user.Traits = &machine
}

View File

@@ -3,58 +3,33 @@ package v4
import (
"context"
"time"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type Human struct {
FirstName string
LastName string
Email *Email
Phone *Phone
}
const UserTypeHuman UserType = "human"
func (Human) userTrait() {}
func (h Human) Type() UserType {
return UserTypeHuman
}
var _ userTrait = (*Human)(nil)
type Email struct {
Address string
Verification
}
type Phone struct {
Number string
Verification
}
type Verification struct {
VerifiedAt time.Time
}
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
type userHuman struct {
*user
}
func (u *user) Human() *userHuman {
return &userHuman{user: u}
}
var _ domain.HumanRepository = (*userHuman)(nil)
const userEmailQuery = `SELECT h.email_address, h.email_verified_at FROM user_humans h`
func (u *userHuman) GetEmail(ctx context.Context, condition Condition) (*Email, error) {
var email Email
// GetEmail implements [domain.HumanRepository].
func (u *userHuman) GetEmail(ctx context.Context, condition database.Condition) (*domain.Email, error) {
var email domain.Email
u.builder.WriteString(userEmailQuery)
u.writeCondition(condition)
err := u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan(
err := u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(
&email.Address,
&email.Verification.VerifiedAt,
&email.VerifiedAt,
)
if err != nil {
@@ -63,130 +38,158 @@ func (u *userHuman) GetEmail(ctx context.Context, condition Condition) (*Email,
return &email, nil
}
func (h userHuman) Update(ctx context.Context, condition Condition, changes ...Change) error {
// Update implements [domain.HumanRepository].
func (h userHuman) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
h.builder.WriteString(`UPDATE human_users SET `)
Changes(changes).writeTo(&h.builder)
database.Changes(changes).Write(&h.builder)
h.writeCondition(condition)
stmt := h.builder.String()
return h.client.Exec(ctx, stmt, h.builder.args...)
return h.client.Exec(ctx, stmt, h.builder.Args()...)
}
func (h userHuman) SetFirstName(firstName string) Change {
return newChange(h.FirstNameColumn(), firstName)
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetFirstName implements [domain.humanChanges].
func (h userHuman) SetFirstName(firstName string) database.Change {
return database.NewChange(h.FirstNameColumn(), firstName)
}
func (h userHuman) FirstNameColumn() Column {
return column{"first_name"}
// SetLastName implements [domain.humanChanges].
func (h userHuman) SetLastName(lastName string) database.Change {
return database.NewChange(h.LastNameColumn(), lastName)
}
func (h userHuman) FirstNameCondition(op TextOperator, firstName string) Condition {
return newTextCondition(h.FirstNameColumn(), op, firstName)
}
func (h userHuman) SetLastName(lastName string) Change {
return newChange(h.LastNameColumn(), lastName)
}
func (h userHuman) LastNameColumn() Column {
return column{"last_name"}
}
func (h userHuman) LastNameCondition(op TextOperator, lastName string) Condition {
return newTextCondition(h.LastNameColumn(), op, lastName)
}
func (h userHuman) EmailAddressColumn() Column {
return ignoreCaseCol{
column: column{"email_address"},
suffix: "_lower",
}
}
func (h userHuman) EmailAddressCondition(op TextOperator, email string) Condition {
return newTextCondition(h.EmailAddressColumn(), op, email)
}
func (h userHuman) EmailVerifiedAtColumn() Column {
return column{"email_verified_at"}
}
func (h *userHuman) EmailAddressVerifiedCondition(isVerified bool) Condition {
if isVerified {
return IsNotNull(h.EmailVerifiedAtColumn())
}
return IsNull(h.EmailVerifiedAtColumn())
}
func (h userHuman) EmailVerifiedAtCondition(op TextOperator, emailVerifiedAt string) Condition {
return newTextCondition(h.EmailVerifiedAtColumn(), op, emailVerifiedAt)
}
func (h userHuman) SetEmailAddress(address string) Change {
return newChange(h.EmailAddressColumn(), address)
}
// SetEmailVerified sets the verified column of the email
// if at is zero the statement uses the database timestamp
func (h userHuman) SetEmailVerified(at time.Time) Change {
if at.IsZero() {
return newChange(h.EmailVerifiedAtColumn(), nowDBInstruction)
}
return newChange(h.EmailVerifiedAtColumn(), at)
}
func (h userHuman) SetEmail(address string, verified *time.Time) Change {
return newChanges(
// SetEmail implements [domain.humanChanges].
func (h userHuman) SetEmail(address string, verified *time.Time) database.Change {
return database.NewChanges(
h.SetEmailAddress(address),
newUpdatePtrColumn(h.EmailVerifiedAtColumn(), verified),
database.NewChangePtr(h.EmailVerifiedAtColumn(), verified),
)
}
func (h userHuman) PhoneNumberColumn() Column {
return column{"phone_number"}
// SetEmailAddress implements [domain.humanChanges].
func (h userHuman) SetEmailAddress(address string) database.Change {
return database.NewChange(h.EmailAddressColumn(), address)
}
func (h userHuman) SetPhoneNumber(number string) Change {
return newChange(h.PhoneNumberColumn(), number)
}
func (h userHuman) PhoneNumberCondition(op TextOperator, phoneNumber string) Condition {
return newTextCondition(h.PhoneNumberColumn(), op, phoneNumber)
}
func (h userHuman) PhoneVerifiedAtColumn() Column {
return column{"phone_verified_at"}
}
func (h userHuman) PhoneNumberVerifiedCondition(isVerified bool) Condition {
if isVerified {
return IsNotNull(h.PhoneVerifiedAtColumn())
}
return IsNull(h.PhoneVerifiedAtColumn())
}
// SetPhoneVerified sets the verified column of the phone
// if at is zero the statement uses the database timestamp
func (h userHuman) SetPhoneVerified(at time.Time) Change {
// SetEmailVerifiedAt implements [domain.humanChanges].
func (h userHuman) SetEmailVerifiedAt(at time.Time) database.Change {
if at.IsZero() {
return newChange(h.PhoneVerifiedAtColumn(), nowDBInstruction)
return database.NewChange(h.EmailVerifiedAtColumn(), database.NowInstruction)
}
return newChange(h.PhoneVerifiedAtColumn(), at)
return database.NewChange(h.EmailVerifiedAtColumn(), at)
}
func (h userHuman) PhoneVerifiedAtCondition(op TextOperator, phoneVerifiedAt string) Condition {
return newTextCondition(h.PhoneVerifiedAtColumn(), op, phoneVerifiedAt)
}
func (h userHuman) SetPhone(number string, verifiedAt *time.Time) Change {
return newChanges(
// SetPhone implements [domain.humanChanges].
func (h userHuman) SetPhone(number string, verifiedAt *time.Time) database.Change {
return database.NewChanges(
h.SetPhoneNumber(number),
newUpdatePtrColumn(h.PhoneVerifiedAtColumn(), verifiedAt),
database.NewChangePtr(h.PhoneVerifiedAtColumn(), verifiedAt),
)
}
func (h userHuman) columns() Columns {
// SetPhoneNumber implements [domain.humanChanges].
func (h userHuman) SetPhoneNumber(number string) database.Change {
return database.NewChange(h.PhoneNumberColumn(), number)
}
// SetPhoneVerifiedAt implements [domain.humanChanges].
func (h userHuman) SetPhoneVerifiedAt(at time.Time) database.Change {
if at.IsZero() {
return database.NewChange(h.PhoneVerifiedAtColumn(), database.NowInstruction)
}
return database.NewChange(h.PhoneVerifiedAtColumn(), at)
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// FirstNameCondition implements [domain.humanConditions].
func (h userHuman) FirstNameCondition(op database.TextOperation, firstName string) database.Condition {
return database.NewTextCondition(h.FirstNameColumn(), op, firstName)
}
// LastNameCondition implements [domain.humanConditions].
func (h userHuman) LastNameCondition(op database.TextOperation, lastName string) database.Condition {
return database.NewTextCondition(h.LastNameColumn(), op, lastName)
}
// EmailAddressCondition implements [domain.humanConditions].
func (h userHuman) EmailAddressCondition(op database.TextOperation, email string) database.Condition {
return database.NewTextCondition(h.EmailAddressColumn(), op, email)
}
// EmailVerifiedCondition implements [domain.humanConditions].
func (h *userHuman) EmailVerifiedCondition(isVerified bool) database.Condition {
if isVerified {
return database.IsNotNull(h.EmailVerifiedAtColumn())
}
return database.IsNull(h.EmailVerifiedAtColumn())
}
// EmailVerifiedAtCondition implements [domain.humanConditions].
func (h userHuman) EmailVerifiedAtCondition(op database.NumberOperation, verifiedAt time.Time) database.Condition {
return database.NewNumberCondition(h.EmailVerifiedAtColumn(), op, verifiedAt)
}
// PhoneNumberCondition implements [domain.humanConditions].
func (h userHuman) PhoneNumberCondition(op database.TextOperation, phoneNumber string) database.Condition {
return database.NewTextCondition(h.PhoneNumberColumn(), op, phoneNumber)
}
// PhoneVerifiedCondition implements [domain.humanConditions].
func (h userHuman) PhoneVerifiedCondition(isVerified bool) database.Condition {
if isVerified {
return database.IsNotNull(h.PhoneVerifiedAtColumn())
}
return database.IsNull(h.PhoneVerifiedAtColumn())
}
// PhoneVerifiedAtCondition implements [domain.humanConditions].
func (h userHuman) PhoneVerifiedAtCondition(op database.NumberOperation, verifiedAt time.Time) database.Condition {
return database.NewNumberCondition(h.PhoneVerifiedAtColumn(), op, verifiedAt)
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// FirstNameColumn implements [domain.humanColumns].
func (h userHuman) FirstNameColumn() database.Column {
return database.NewColumn("first_name")
}
// LastNameColumn implements [domain.humanColumns].
func (h userHuman) LastNameColumn() database.Column {
return database.NewColumn("last_name")
}
// EmailAddressColumn implements [domain.humanColumns].
func (h userHuman) EmailAddressColumn() database.Column {
return database.NewIgnoreCaseColumn("email_address", "_lower")
}
// EmailVerifiedAtColumn implements [domain.humanColumns].
func (h userHuman) EmailVerifiedAtColumn() database.Column {
return database.NewColumn("email_verified_at")
}
// PhoneNumberColumn implements [domain.humanColumns].
func (h userHuman) PhoneNumberColumn() database.Column {
return database.NewColumn("phone_number")
}
// PhoneVerifiedAtColumn implements [domain.humanColumns].
func (h userHuman) PhoneVerifiedAtColumn() database.Column {
return database.NewColumn("phone_verified_at")
}
func (h userHuman) columns() database.Columns {
return append(h.user.columns(),
h.FirstNameColumn(),
h.LastNameColumn(),
@@ -197,7 +200,7 @@ func (h userHuman) columns() Columns {
)
}
func (h userHuman) writeReturning(builder *statementBuilder) {
func (h userHuman) writeReturning(builder *database.StatementBuilder) {
builder.WriteString(" RETURNING ")
h.columns().writeTo(builder)
h.columns().Write(builder)
}

View File

@@ -1,72 +1,64 @@
package v4
import "context"
import (
"context"
type Machine struct {
Description string
}
func (Machine) userTrait() {}
func (m Machine) Type() UserType {
return UserTypeMachine
}
const UserTypeMachine UserType = "machine"
var _ userTrait = (*Machine)(nil)
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type userMachine struct {
*user
}
func (u *user) Machine() *userMachine {
return &userMachine{user: u}
}
var _ domain.MachineRepository = (*userMachine)(nil)
func (m userMachine) Update(ctx context.Context, condition Condition, changes ...Change) ([]*Machine, error) {
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
// Update implements [domain.MachineRepository].
func (m userMachine) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (err error) {
m.builder.WriteString("UPDATE user_machines SET ")
Changes(changes).writeTo(&m.builder)
database.Changes(changes).Write(&m.builder)
m.writeCondition(condition)
m.writeReturning()
var machines []*Machine
rows, err := m.client.Query(ctx, m.builder.String(), m.builder.args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
machine := new(Machine)
if err := rows.Scan(&machine.Description); err != nil {
return nil, err
}
machines = append(machines, machine)
}
if err := rows.Err(); err != nil {
return nil, err
}
return machines, nil
return m.client.Exec(ctx, m.builder.String(), m.builder.Args()...)
}
func (userMachine) DescriptionColumn() Column {
return column{"description"}
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetDescription implements [domain.machineChanges].
func (m userMachine) SetDescription(description string) database.Change {
return database.NewChange(m.DescriptionColumn(), description)
}
func (m userMachine) SetDescription(description string) Change {
return newChange(m.DescriptionColumn(), description)
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// DescriptionCondition implements [domain.machineConditions].
func (m userMachine) DescriptionCondition(op database.TextOperation, description string) database.Condition {
return database.NewTextCondition(m.DescriptionColumn(), op, description)
}
func (m userMachine) DescriptionCondition(op TextOperator, description string) Condition {
return newTextCondition(m.DescriptionColumn(), op, description)
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// DescriptionColumn implements [domain.machineColumns].
func (m userMachine) DescriptionColumn() database.Column {
return database.NewColumn("description")
}
func (m userMachine) columns() Columns {
func (m userMachine) columns() database.Columns {
return append(m.user.columns(), m.DescriptionColumn())
}
func (m *userMachine) writeReturning() {
m.builder.WriteString(" RETURNING ")
m.columns().writeTo(&m.builder)
m.columns().Write(&m.builder)
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/backend/v3/storage/database"
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
)
@@ -12,16 +13,16 @@ func TestQueryUser(t *testing.T) {
t.Run("User filters", func(t *testing.T) {
user := v4.UserRepository(nil)
u, err := user.Get(context.Background(),
v4.WithCondition(
v4.And(
v4.Or(
database.WithCondition(
database.And(
database.Or(
user.IDCondition("test"),
user.IDCondition("2"),
),
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
),
),
v4.WithOrderBy(user.CreatedAtColumn()),
database.WithOrderBy(user.CreatedAtColumn()),
)
assert.NoError(t, err)
@@ -32,12 +33,12 @@ func TestQueryUser(t *testing.T) {
user := v4.UserRepository(nil)
machine := user.Machine()
human := user.Human()
email, err := human.GetEmail(context.Background(), v4.And(
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
v4.Or(
machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
human.EmailAddressVerifiedCondition(true),
v4.IsNotNull(machine.DescriptionColumn()),
email, err := human.GetEmail(context.Background(), database.And(
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
database.Or(
machine.DescriptionCondition(database.TextOperationStartsWithIgnoreCase, "test"),
human.EmailVerifiedCondition(true),
database.IsNotNull(machine.DescriptionColumn()),
),
))
@@ -62,6 +63,6 @@ func TestArg(t *testing.T) {
func TestWriteUser(t *testing.T) {
t.Run("update user", func(t *testing.T) {
user := v4.UserRepository(nil)
user.Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
})
}

View File

@@ -31,17 +31,17 @@ var _ domain.UserOperation = (*userOperation)(nil)
func UserIDQuery(id string) domain.UserClause {
return textClause[string]{
clause: clause[domain.TextOperation]{
clause: clause[database.TextOperation]{
field: userFields[domain.UserFieldID],
op: domain.TextOperationEqual,
op: database.TextOperationEqual,
},
value: id,
}
}
func HumanEmailQuery(op domain.TextOperation, email string) domain.UserClause {
func HumanEmailQuery(op database.TextOperation, email string) domain.UserClause {
return textClause[string]{
clause: clause[domain.TextOperation]{
clause: clause[database.TextOperation]{
field: userFields[domain.UserHumanFieldEmail],
op: op,
},
@@ -49,9 +49,9 @@ func HumanEmailQuery(op domain.TextOperation, email string) domain.UserClause {
}
}
func HumanEmailVerifiedQuery(op domain.BoolOperation) domain.UserClause {
return boolClause[domain.BoolOperation]{
clause: clause[domain.BoolOperation]{
func HumanEmailVerifiedQuery(op database.BoolOperation) domain.UserClause {
return boolClause[database.BoolOperation]{
clause: clause[database.BoolOperation]{
field: userFields[domain.UserHumanFieldEmailVerified],
op: op,
},

View File

@@ -0,0 +1,50 @@
package database
import (
"strconv"
"strings"
)
type Instruction string
const (
NowInstruction Instruction = "NOW()"
NullInstruction Instruction = "NULL"
)
type StatementBuilder struct {
strings.Builder
args []any
existingArgs map[any]string
}
func (b *StatementBuilder) WriteArg(arg any) {
b.WriteString(b.AppendArg(arg))
}
func (b *StatementBuilder) AppendArg(arg any) (placeholder string) {
if b.existingArgs == nil {
b.existingArgs = make(map[any]string)
}
if placeholder, ok := b.existingArgs[arg]; ok {
return placeholder
}
if instruction, ok := arg.(Instruction); ok {
return string(instruction)
}
b.args = append(b.args, arg)
placeholder = "$" + strconv.Itoa(len(b.args))
b.existingArgs[arg] = placeholder
return placeholder
}
func (b *StatementBuilder) AppendArgs(args ...any) {
for _, arg := range args {
b.AppendArg(arg)
}
}
func (b *StatementBuilder) Args() []any {
return b.args
}