mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 17:27:31 +00:00
move files
This commit is contained in:
@@ -3,7 +3,6 @@ package domain
|
||||
import (
|
||||
"context"
|
||||
|
||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||
)
|
||||
|
||||
@@ -20,10 +19,8 @@ var (
|
||||
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
|
||||
cmd := &CreateUserCommand{
|
||||
user: &User{
|
||||
User: v4.User{
|
||||
Username: username,
|
||||
Traits: &v4.Human{},
|
||||
},
|
||||
Username: username,
|
||||
Traits: &Human{},
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -1,45 +1,45 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// "go.opentelemetry.io/otel"
|
||||
// "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
// sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
|
||||
. "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||
)
|
||||
// . "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
// "github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||
// )
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// func TestExample(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
|
||||
// SetPool(pool)
|
||||
// // SetPool(pool)
|
||||
|
||||
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
|
||||
require.NoError(t, err)
|
||||
tracerProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSyncer(exporter),
|
||||
)
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")})
|
||||
defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }()
|
||||
// exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
|
||||
// require.NoError(t, err)
|
||||
// tracerProvider := sdktrace.NewTracerProvider(
|
||||
// sdktrace.WithSyncer(exporter),
|
||||
// )
|
||||
// otel.SetTracerProvider(tracerProvider)
|
||||
// SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")})
|
||||
// defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }()
|
||||
|
||||
SetUserRepository(repository.User)
|
||||
SetInstanceRepository(repository.Instance)
|
||||
SetCryptoRepository(repository.Crypto)
|
||||
// SetUserRepository(repository.User)
|
||||
// SetInstanceRepository(repository.Instance)
|
||||
// SetCryptoRepository(repository.Crypto)
|
||||
|
||||
t.Run("verified email", func(t *testing.T) {
|
||||
err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true)))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
// t.Run("verified email", func(t *testing.T) {
|
||||
// err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true)))
|
||||
// assert.NoError(t, err)
|
||||
// })
|
||||
|
||||
t.Run("unverified email", func(t *testing.T) {
|
||||
err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false)))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
// t.Run("unverified email", func(t *testing.T) {
|
||||
// err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false)))
|
||||
// assert.NoError(t, err)
|
||||
// })
|
||||
// }
|
||||
|
@@ -14,7 +14,7 @@ func NewEmailVerifiedCommand(userID string, isVerified bool) *EmailVerifiedComma
|
||||
return &EmailVerifiedCommand{
|
||||
UserID: userID,
|
||||
Email: &Email{
|
||||
IsVerified: isVerified,
|
||||
VerifiedAt: time.Time{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -1,82 +0,0 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
// TextOperation |
|
||||
// NumberOperation |
|
||||
// BoolOperation
|
||||
|
||||
op()
|
||||
}
|
||||
|
||||
type clause[F ~uint8, Op Operation] struct {
|
||||
field F
|
||||
op Op
|
||||
}
|
||||
|
||||
func (c *clause[F, Op]) Field() F {
|
||||
return c.field
|
||||
}
|
||||
|
||||
func (c *clause[F, Op]) Operation() Op {
|
||||
return c.op
|
||||
}
|
||||
|
||||
type Text interface {
|
||||
~string | ~[]byte
|
||||
}
|
||||
|
||||
type TextOperation uint8
|
||||
|
||||
const (
|
||||
TextOperationEqual TextOperation = iota
|
||||
TextOperationNotEqual
|
||||
TextOperationStartsWith
|
||||
TextOperationStartsWithIgnoreCase
|
||||
)
|
||||
|
||||
func (TextOperation) op() {}
|
||||
|
||||
type Number interface {
|
||||
constraints.Integer | constraints.Float | constraints.Complex | time.Time
|
||||
}
|
||||
|
||||
type NumberOperation uint8
|
||||
|
||||
const (
|
||||
NumberOperationEqual NumberOperation = iota
|
||||
NumberOperationNotEqual
|
||||
NumberOperationLessThan
|
||||
NumberOperationLessThanOrEqual
|
||||
NumberOperationGreaterThan
|
||||
NumberOperationGreaterThanOrEqual
|
||||
)
|
||||
|
||||
func (NumberOperation) op() {}
|
||||
|
||||
type Bool interface {
|
||||
~bool
|
||||
}
|
||||
|
||||
type BoolOperation uint8
|
||||
|
||||
const (
|
||||
BoolOperationIs BoolOperation = iota
|
||||
BoolOperationNot
|
||||
)
|
||||
|
||||
func (BoolOperation) op() {}
|
||||
|
||||
type ListOperation uint8
|
||||
|
||||
const (
|
||||
ListOperationContains ListOperation = iota
|
||||
ListOperationNotContains
|
||||
)
|
||||
|
||||
func (ListOperation) op() {}
|
@@ -4,82 +4,129 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type userColumns interface {
|
||||
// TODO: move v4.columns to domain
|
||||
InstanceIDColumn() v4.Column
|
||||
OrgIDColumn() v4.Column
|
||||
IDColumn() v4.Column
|
||||
usernameColumn() v4.Column
|
||||
CreatedAtColumn() v4.Column
|
||||
UpdatedAtColumn() v4.Column
|
||||
DeletedAtColumn() v4.Column
|
||||
// InstanceIDColumn returns the column for the instance id field.
|
||||
InstanceIDColumn() database.Column
|
||||
// OrgIDColumn returns the column for the org id field.
|
||||
OrgIDColumn() database.Column
|
||||
// IDColumn returns the column for the id field.
|
||||
IDColumn() database.Column
|
||||
// UsernameColumn returns the column for the username field.
|
||||
UsernameColumn() database.Column
|
||||
// CreatedAtColumn returns the column for the created at field.
|
||||
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
|
||||
}
|
||||
|
||||
type userConditions interface {
|
||||
InstanceIDCondition(instanceID string) v4.Condition
|
||||
OrgIDCondition(orgID string) v4.Condition
|
||||
IDCondition(userID string) v4.Condition
|
||||
UsernameCondition(op v4.TextOperator, username string) v4.Condition
|
||||
CreatedAtCondition(op v4.NumberOperator, createdAt time.Time) v4.Condition
|
||||
UpdatedAtCondition(op v4.NumberOperator, updatedAt time.Time) v4.Condition
|
||||
DeletedCondition(isDeleted bool) v4.Condition
|
||||
DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition
|
||||
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// OrgIDCondition returns an equal filter on the org id field.
|
||||
OrgIDCondition(orgID string) database.Condition
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(userID string) database.Condition
|
||||
// UsernameCondition returns a filter on the username field.
|
||||
UsernameCondition(op database.TextOperation, username string) database.Condition
|
||||
// CreatedAtCondition returns a filter on the created at field.
|
||||
CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition
|
||||
// UpdatedAtCondition returns a filter on the updated at field.
|
||||
UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition
|
||||
// DeletedAtCondition filters for deleted users is isDeleted is set to true otherwise only not deleted users must be filtered.
|
||||
DeletedCondition(isDeleted bool) database.Condition
|
||||
// DeletedAtCondition filters for deleted users based on the given parameters.
|
||||
DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition
|
||||
}
|
||||
|
||||
type userChanges interface {
|
||||
SetUsername(username string) v4.Change
|
||||
// SetUsername sets the username column.
|
||||
SetUsername(username string) database.Change
|
||||
}
|
||||
|
||||
type UserRepository interface {
|
||||
userColumns
|
||||
userConditions
|
||||
userChanges
|
||||
// TODO: move condition to domain
|
||||
Get(ctx context.Context, opts v4.QueryOption) (*User, error)
|
||||
List(ctx context.Context, opts v4.QueryOption) ([]*User, error)
|
||||
Delete(ctx context.Context, condition v4.Condition) error
|
||||
|
||||
// Get returns a user based on the given condition.
|
||||
Get(ctx context.Context, opts ...database.QueryOption) (*User, error)
|
||||
// List returns a list of users based on the given condition.
|
||||
List(ctx context.Context, opts ...database.QueryOption) ([]*User, error)
|
||||
// Create creates a new user.
|
||||
Create(ctx context.Context, user *User) error
|
||||
// Delete removes users based on the given condition.
|
||||
Delete(ctx context.Context, condition database.Condition) error
|
||||
// Human returns the [HumanRepository].
|
||||
Human() HumanRepository
|
||||
// Machine returns the [MachineRepository].
|
||||
Machine() MachineRepository
|
||||
}
|
||||
|
||||
type humanColumns interface {
|
||||
userColumns
|
||||
FirstNameColumn() v4.Column
|
||||
LastNameColumn() v4.Column
|
||||
EmailAddressColumn() v4.Column
|
||||
EmailVerifiedAtColumn() v4.Column
|
||||
PhoneNumberColumn() v4.Column
|
||||
PhoneVerifiedAtColumn() v4.Column
|
||||
// FirstNameColumn returns the column for the first name field.
|
||||
FirstNameColumn() database.Column
|
||||
// LastNameColumn returns the column for the last name field.
|
||||
LastNameColumn() database.Column
|
||||
// EmailAddressColumn returns the column for the email address field.
|
||||
EmailAddressColumn() database.Column
|
||||
// EmailVerifiedAtColumn returns the column for the email verified at field.
|
||||
EmailVerifiedAtColumn() database.Column
|
||||
// PhoneNumberColumn returns the column for the phone number field.
|
||||
PhoneNumberColumn() database.Column
|
||||
// PhoneVerifiedAtColumn returns the column for the phone verified at field.
|
||||
PhoneVerifiedAtColumn() database.Column
|
||||
}
|
||||
|
||||
type humanConditions interface {
|
||||
userConditions
|
||||
FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition
|
||||
LastNameCondition(op v4.TextOperator, lastName string) v4.Condition
|
||||
EmailAddressCondition(op v4.TextOperator, email string) v4.Condition
|
||||
EmailAddressVerifiedCondition(isVerified bool) v4.Condition
|
||||
EmailVerifiedAtCondition(op v4.TextOperator, emailVerifiedAt string) v4.Condition
|
||||
PhoneNumberCondition(op v4.TextOperator, phoneNumber string) v4.Condition
|
||||
PhoneNumberVerifiedCondition(isVerified bool) v4.Condition
|
||||
PhoneVerifiedAtCondition(op v4.TextOperator, phoneVerifiedAt string) v4.Condition
|
||||
// FirstNameCondition returns a filter on the first name field.
|
||||
FirstNameCondition(op database.TextOperation, firstName string) database.Condition
|
||||
// LastNameCondition returns a filter on the last name field.
|
||||
LastNameCondition(op database.TextOperation, lastName string) database.Condition
|
||||
// EmailAddressCondition returns a filter on the email address field.
|
||||
EmailAddressCondition(op database.TextOperation, email string) database.Condition
|
||||
// EmailVerifiedCondition returns a filter that checks if the email is verified or not.
|
||||
EmailVerifiedCondition(isVerified bool) database.Condition
|
||||
// EmailVerifiedAtCondition returns a filter on the email verified at field.
|
||||
EmailVerifiedAtCondition(op database.NumberOperation, emailVerifiedAt time.Time) database.Condition
|
||||
|
||||
// PhoneNumberCondition returns a filter on the phone number field.
|
||||
PhoneNumberCondition(op database.TextOperation, phoneNumber string) database.Condition
|
||||
// PhoneVerifiedCondition returns a filter that checks if the phone is verified or not.
|
||||
PhoneVerifiedCondition(isVerified bool) database.Condition
|
||||
// PhoneVerifiedAtCondition returns a filter on the phone verified at field.
|
||||
PhoneVerifiedAtCondition(op database.NumberOperation, phoneVerifiedAt time.Time) database.Condition
|
||||
}
|
||||
|
||||
type humanChanges interface {
|
||||
userChanges
|
||||
SetFirstName(firstName string) v4.Change
|
||||
SetLastName(lastName string) v4.Change
|
||||
// SetFirstName sets the first name field of the human.
|
||||
SetFirstName(firstName string) database.Change
|
||||
// SetLastName sets the last name field of the human.
|
||||
SetLastName(lastName string) database.Change
|
||||
|
||||
SetEmail(address string, verified *time.Time) v4.Change
|
||||
SetEmailAddress(email string) v4.Change
|
||||
SetEmailVerifiedAt(emailVerifiedAt time.Time) v4.Change
|
||||
// SetEmail sets the email address and verified field of the email
|
||||
// if verifiedAt is nil the email is not verified
|
||||
SetEmail(address string, verifiedAt *time.Time) database.Change
|
||||
// SetEmailAddress sets the email address field of the email
|
||||
SetEmailAddress(email string) database.Change
|
||||
// SetEmailVerifiedAt sets the verified column of the email
|
||||
// if at is zero the statement uses the database timestamp
|
||||
SetEmailVerifiedAt(at time.Time) database.Change
|
||||
|
||||
SetPhone(number string, verifiedAt *time.Time) v4.Change
|
||||
SetPhoneNumber(phoneNumber string) v4.Change
|
||||
SetPhoneVerifiedAt(phoneVerifiedAt time.Time) v4.Change
|
||||
// SetPhone sets the phone number and verified field
|
||||
// if verifiedAt is nil the phone is not verified
|
||||
SetPhone(number string, verifiedAt *time.Time) database.Change
|
||||
// SetPhoneNumber sets the phone number field
|
||||
SetPhoneNumber(phoneNumber string) database.Change
|
||||
// SetPhoneVerifiedAt sets the verified field of the phone
|
||||
// if at is zero the statement uses the database timestamp
|
||||
SetPhoneVerifiedAt(at time.Time) database.Change
|
||||
}
|
||||
|
||||
type HumanRepository interface {
|
||||
@@ -87,144 +134,95 @@ type HumanRepository interface {
|
||||
humanConditions
|
||||
humanChanges
|
||||
|
||||
GetEmail(ctx context.Context, condition v4.Condition) (*Email, error)
|
||||
// TODO: replace any with add email update columns
|
||||
Create(ctx context.Context, user *User) error
|
||||
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
|
||||
// Get returns an email based on the given condition.
|
||||
GetEmail(ctx context.Context, condition database.Condition) (*Email, error)
|
||||
// Update updates human users based on the given condition and changes.
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||
}
|
||||
|
||||
type machineColumns interface {
|
||||
userColumns
|
||||
DescriptionColumn() v4.Column
|
||||
// DescriptionColumn returns the column for the description field.
|
||||
DescriptionColumn() database.Column
|
||||
}
|
||||
|
||||
type machineConditions interface {
|
||||
userConditions
|
||||
DescriptionCondition(op v4.TextOperator, description string) v4.Condition
|
||||
// DescriptionCondition returns a filter on the description field.
|
||||
DescriptionCondition(op database.TextOperation, description string) database.Condition
|
||||
}
|
||||
|
||||
type machineChanges interface {
|
||||
userChanges
|
||||
SetDescription(description string) v4.Change
|
||||
// SetDescription sets the description field of the machine.
|
||||
SetDescription(description string) database.Change
|
||||
}
|
||||
|
||||
type MachineRepository interface {
|
||||
// Update updates machine users based on the given condition and changes.
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||
|
||||
machineColumns
|
||||
machineConditions
|
||||
machineChanges
|
||||
|
||||
Create(ctx context.Context, user *User) error
|
||||
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
|
||||
}
|
||||
|
||||
// type UserRepository interface {
|
||||
// // Get(ctx context.Context, clauses ...UserClause) (*User, error)
|
||||
// // Search(ctx context.Context, clauses ...UserClause) ([]*User, error)
|
||||
type UserTraits interface {
|
||||
Type() UserType
|
||||
}
|
||||
|
||||
// UserQuery[UserOperation]
|
||||
// Human() HumanQuery
|
||||
// Machine() MachineQuery
|
||||
// }
|
||||
type UserType string
|
||||
|
||||
// type UserQuery[Op UserOperation] interface {
|
||||
// ByID(id string) UserQuery[Op]
|
||||
// Username(username string) UserQuery[Op]
|
||||
// Exec() Op
|
||||
// }
|
||||
|
||||
// type HumanQuery interface {
|
||||
// UserQuery[HumanOperation]
|
||||
// Email(op TextOperation, email string) HumanQuery
|
||||
// HumanOperation
|
||||
// }
|
||||
|
||||
// type MachineQuery interface {
|
||||
// UserQuery[MachineOperation]
|
||||
// MachineOperation
|
||||
// }
|
||||
|
||||
// type UserClause interface {
|
||||
// Field() UserField
|
||||
// Operation() Operation
|
||||
// Args() []any
|
||||
// }
|
||||
|
||||
// type UserField uint8
|
||||
|
||||
// const (
|
||||
// // Fields used for all users
|
||||
// UserFieldInstanceID UserField = iota + 1
|
||||
// UserFieldOrgID
|
||||
// UserFieldID
|
||||
// UserFieldUsername
|
||||
|
||||
// // Fields used for human users
|
||||
// UserHumanFieldEmail
|
||||
// UserHumanFieldEmailVerified
|
||||
|
||||
// // Fields used for machine users
|
||||
// UserMachineFieldDescription
|
||||
// )
|
||||
|
||||
// type userByIDClause struct {
|
||||
// id string
|
||||
// }
|
||||
|
||||
// func (c *userByIDClause) Field() UserField {
|
||||
// return UserFieldID
|
||||
// }
|
||||
|
||||
// func (c *userByIDClause) Operation() Operation {
|
||||
// return TextOperationEqual
|
||||
// }
|
||||
|
||||
// func (c *userByIDClause) Args() []any {
|
||||
// return []any{c.id}
|
||||
// }
|
||||
|
||||
// type UserOperation interface {
|
||||
// Delete(ctx context.Context) error
|
||||
// SetUsername(ctx context.Context, username string) error
|
||||
// }
|
||||
|
||||
// type HumanOperation interface {
|
||||
// UserOperation
|
||||
// SetEmail(ctx context.Context, email string) error
|
||||
// SetEmailVerified(ctx context.Context, email string) error
|
||||
// GetEmail(ctx context.Context) (*Email, error)
|
||||
// }
|
||||
|
||||
// type MachineOperation interface {
|
||||
// UserOperation
|
||||
// SetDescription(ctx context.Context, description string) error
|
||||
// }
|
||||
const (
|
||||
UserTypeHuman UserType = "human"
|
||||
UserTypeMachine UserType = "machine"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
v4.User
|
||||
InstanceID string
|
||||
OrgID string
|
||||
ID string
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt time.Time
|
||||
|
||||
Username string
|
||||
|
||||
Traits UserTraits
|
||||
}
|
||||
|
||||
type Human struct {
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email *Email `json:"email,omitempty"`
|
||||
Phone *Phone `json:"phone,omitempty"`
|
||||
}
|
||||
|
||||
// Type implements [UserTraits].
|
||||
func (h *Human) Type() UserType {
|
||||
return UserTypeHuman
|
||||
}
|
||||
|
||||
var _ UserTraits = (*Human)(nil)
|
||||
|
||||
type Email struct {
|
||||
v4.Email
|
||||
IsVerified bool
|
||||
Address string `json:"address"`
|
||||
VerifiedAt time.Time `json:"verifiedAt"`
|
||||
}
|
||||
|
||||
// type userTraits interface {
|
||||
// isUserTraits()
|
||||
// }
|
||||
type Phone struct {
|
||||
Number string `json:"number"`
|
||||
VerifiedAt time.Time `json:"verifiedAt"`
|
||||
}
|
||||
|
||||
// type Human struct {
|
||||
// Email *Email `json:"email"`
|
||||
// }
|
||||
type Machine struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// func (*Human) isUserTraits() {}
|
||||
// Type implements [UserTraits].
|
||||
func (m *Machine) Type() UserType {
|
||||
return UserTypeMachine
|
||||
}
|
||||
|
||||
// type Machine struct {
|
||||
// Description string `json:"description"`
|
||||
// }
|
||||
|
||||
// func (*Machine) isUserTraits() {}
|
||||
|
||||
// type Email struct {
|
||||
// Address string `json:"address"`
|
||||
// IsVerified bool `json:"isVerified"`
|
||||
// }
|
||||
var _ UserTraits = (*Machine)(nil)
|
||||
|
51
backend/v3/storage/database/change.go
Normal file
51
backend/v3/storage/database/change.go
Normal 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)
|
55
backend/v3/storage/database/column.go
Normal file
55
backend/v3/storage/database/column.go
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
139
backend/v3/storage/database/operators.go
Normal file
139
backend/v3/storage/database/operators.go
Normal 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))
|
||||
}
|
66
backend/v3/storage/database/query.go
Normal file
66
backend/v3/storage/database/query.go
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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))
|
||||
}
|
@@ -4,7 +4,6 @@ type Org struct {
|
||||
InstanceID string
|
||||
ID string
|
||||
Name string
|
||||
Dates
|
||||
}
|
||||
|
||||
type GetOrg struct{}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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"))
|
||||
})
|
||||
}
|
||||
|
@@ -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,
|
||||
},
|
||||
|
50
backend/v3/storage/database/statement.go
Normal file
50
backend/v3/storage/database/statement.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user