mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:07:30 +00:00
feat: push telemetry (#6027)
* document analytics config
* rework configuration and docs
* describe HandleActiveInstances better
* describe active instances on quotas better
* only projected events are considered
* cleanup
* describe changes at runtime
* push milestones
* stop tracking events
* calculate and push 4 in 6 milestones
* reduce milestone pushed
* remove docs
* fix scheduled pseudo event projection
* push 5 in 6 milestones
* push 6 in 6 milestones
* ignore client ids
* fix text array contains
* push human readable milestone type
* statement unit tests
* improve dev and db performance
* organize imports
* cleanup
* organize imports
* test projection
* check rows.Err()
* test search query
* pass linting
* review
* test 4 milestones
* simplify milestone by instance ids query
* use type NamespacedCondition
* cleanup
* lint
* lint
* dont overwrite original error
* no opt-in in examples
* cleanup
* prerelease
* enable request headers
* make limit configurable
* review fixes
* only requeue special handlers secondly
* include integration tests
* Revert "include integration tests"
This reverts commit 96db9504ec
.
* pass reducers
* test handlers
* fix unit test
* feat: increment version
* lint
* remove prerelease
* fix integration tests
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/repository/pseudo"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,6 +50,8 @@ type StatementHandler struct {
|
||||
initialized chan bool
|
||||
|
||||
bulkLimit uint64
|
||||
|
||||
reduceScheduledPseudoEvent bool
|
||||
}
|
||||
|
||||
func NewStatementHandler(
|
||||
@@ -57,30 +60,40 @@ func NewStatementHandler(
|
||||
) StatementHandler {
|
||||
aggregateTypes := make([]eventstore.AggregateType, 0, len(config.Reducers))
|
||||
reduces := make(map[eventstore.EventType]handler.Reduce, len(config.Reducers))
|
||||
reduceScheduledPseudoEvent := false
|
||||
for _, aggReducer := range config.Reducers {
|
||||
aggregateTypes = append(aggregateTypes, aggReducer.Aggregate)
|
||||
if aggReducer.Aggregate == pseudo.AggregateType {
|
||||
reduceScheduledPseudoEvent = true
|
||||
if len(config.Reducers) != 1 ||
|
||||
len(aggReducer.EventRedusers) != 1 ||
|
||||
aggReducer.EventRedusers[0].Event != pseudo.ScheduledEventType {
|
||||
panic("if a pseudo.AggregateType is reduced, exactly one event reducer for pseudo.ScheduledEventType is supported and no other aggregate can be reduced")
|
||||
}
|
||||
}
|
||||
for _, eventReducer := range aggReducer.EventRedusers {
|
||||
reduces[eventReducer.Event] = eventReducer.Reduce
|
||||
}
|
||||
}
|
||||
|
||||
h := StatementHandler{
|
||||
client: config.Client,
|
||||
sequenceTable: config.SequenceTable,
|
||||
maxFailureCount: config.MaxFailureCount,
|
||||
currentSequenceStmt: fmt.Sprintf(currentSequenceStmtFormat, config.SequenceTable),
|
||||
updateSequencesBaseStmt: fmt.Sprintf(updateCurrentSequencesStmtFormat, config.SequenceTable),
|
||||
failureCountStmt: fmt.Sprintf(failureCountStmtFormat, config.FailedEventsTable),
|
||||
setFailureCountStmt: fmt.Sprintf(setFailureCountStmtFormat, config.FailedEventsTable),
|
||||
aggregates: aggregateTypes,
|
||||
reduces: reduces,
|
||||
bulkLimit: config.BulkLimit,
|
||||
Locker: NewLocker(config.Client.DB, config.LockTable, config.ProjectionName),
|
||||
initCheck: config.InitCheck,
|
||||
initialized: make(chan bool),
|
||||
client: config.Client,
|
||||
sequenceTable: config.SequenceTable,
|
||||
maxFailureCount: config.MaxFailureCount,
|
||||
currentSequenceStmt: fmt.Sprintf(currentSequenceStmtFormat, config.SequenceTable),
|
||||
updateSequencesBaseStmt: fmt.Sprintf(updateCurrentSequencesStmtFormat, config.SequenceTable),
|
||||
failureCountStmt: fmt.Sprintf(failureCountStmtFormat, config.FailedEventsTable),
|
||||
setFailureCountStmt: fmt.Sprintf(setFailureCountStmtFormat, config.FailedEventsTable),
|
||||
aggregates: aggregateTypes,
|
||||
reduces: reduces,
|
||||
bulkLimit: config.BulkLimit,
|
||||
Locker: NewLocker(config.Client.DB, config.LockTable, config.ProjectionName),
|
||||
initCheck: config.InitCheck,
|
||||
initialized: make(chan bool),
|
||||
reduceScheduledPseudoEvent: reduceScheduledPseudoEvent,
|
||||
}
|
||||
|
||||
h.ProjectionHandler = handler.NewProjectionHandler(ctx, config.ProjectionHandlerConfig, h.reduce, h.Update, h.SearchQuery, h.Lock, h.Unlock, h.initialized)
|
||||
h.ProjectionHandler = handler.NewProjectionHandler(ctx, config.ProjectionHandlerConfig, h.reduce, h.Update, h.searchQuery, h.Lock, h.Unlock, h.initialized, reduceScheduledPseudoEvent)
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -88,10 +101,19 @@ func NewStatementHandler(
|
||||
func (h *StatementHandler) Start() {
|
||||
h.initialized <- true
|
||||
close(h.initialized)
|
||||
h.Subscribe(h.aggregates...)
|
||||
if !h.reduceScheduledPseudoEvent {
|
||||
h.Subscribe(h.aggregates...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *StatementHandler) SearchQuery(ctx context.Context, instanceIDs []string) (*eventstore.SearchQueryBuilder, uint64, error) {
|
||||
func (h *StatementHandler) searchQuery(ctx context.Context, instanceIDs []string) (*eventstore.SearchQueryBuilder, uint64, error) {
|
||||
if h.reduceScheduledPseudoEvent {
|
||||
return nil, 1, nil
|
||||
}
|
||||
return h.dbSearchQuery(ctx, instanceIDs)
|
||||
}
|
||||
|
||||
func (h *StatementHandler) dbSearchQuery(ctx context.Context, instanceIDs []string) (*eventstore.SearchQueryBuilder, uint64, error) {
|
||||
sequences, err := h.currentSequences(ctx, h.client.QueryContext, instanceIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -115,7 +137,6 @@ func (h *StatementHandler) SearchQuery(ctx context.Context, instanceIDs []string
|
||||
InstanceID(instanceID)
|
||||
}
|
||||
}
|
||||
|
||||
return queryBuilder, h.bulkLimit, nil
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/repository/pseudo"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,7 +61,7 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
type fields struct {
|
||||
sequenceTable string
|
||||
projectionName string
|
||||
aggregates []eventstore.AggregateType
|
||||
reducers []handler.AggregateReducer
|
||||
bulkLimit uint64
|
||||
}
|
||||
type args struct {
|
||||
@@ -77,7 +78,7 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
fields: fields{
|
||||
sequenceTable: "my_sequences",
|
||||
projectionName: "my_projection",
|
||||
aggregates: []eventstore.AggregateType{"testAgg"},
|
||||
reducers: failingAggregateReducers("testAgg"),
|
||||
bulkLimit: 5,
|
||||
},
|
||||
args: args{
|
||||
@@ -99,7 +100,7 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
fields: fields{
|
||||
sequenceTable: "my_sequences",
|
||||
projectionName: "my_projection",
|
||||
aggregates: []eventstore.AggregateType{"testAgg"},
|
||||
reducers: failingAggregateReducers("testAgg"),
|
||||
bulkLimit: 5,
|
||||
},
|
||||
args: args{
|
||||
@@ -129,7 +130,7 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
fields: fields{
|
||||
sequenceTable: "my_sequences",
|
||||
projectionName: "my_projection",
|
||||
aggregates: []eventstore.AggregateType{"testAgg"},
|
||||
reducers: failingAggregateReducers("testAgg"),
|
||||
bulkLimit: 5,
|
||||
},
|
||||
args: args{
|
||||
@@ -158,6 +159,32 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
Limit(5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scheduled pseudo event",
|
||||
fields: fields{
|
||||
sequenceTable: "my_sequences",
|
||||
projectionName: "my_projection",
|
||||
reducers: []handler.AggregateReducer{{
|
||||
Aggregate: pseudo.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: pseudo.ScheduledEventType,
|
||||
Reduce: testReduceErr(errors.New("should not be called")),
|
||||
},
|
||||
},
|
||||
}},
|
||||
bulkLimit: 5,
|
||||
},
|
||||
args: args{
|
||||
instanceIDs: []string{"instanceID1", "instanceID2"},
|
||||
},
|
||||
want: want{
|
||||
limit: 1,
|
||||
isErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -177,15 +204,14 @@ func TestProjectionHandler_SearchQuery(t *testing.T) {
|
||||
Client: &database.DB{
|
||||
DB: client,
|
||||
},
|
||||
Reducers: tt.fields.reducers,
|
||||
})
|
||||
|
||||
h.aggregates = tt.fields.aggregates
|
||||
|
||||
for _, expectation := range tt.want.expectations {
|
||||
expectation(mock)
|
||||
}
|
||||
|
||||
query, limit, err := h.SearchQuery(context.Background(), tt.args.instanceIDs)
|
||||
query, limit, err := h.searchQuery(context.Background(), tt.args.instanceIDs)
|
||||
if !tt.want.isErr(err) {
|
||||
t.Errorf("ProjectionHandler.prepareBulkStmts() error = %v", err)
|
||||
return
|
||||
@@ -1768,3 +1794,17 @@ func testReduceErr(err error) handler.Reduce {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func failingAggregateReducers(aggregates ...eventstore.AggregateType) []handler.AggregateReducer {
|
||||
reducers := make([]handler.AggregateReducer, len(aggregates))
|
||||
for idx := range aggregates {
|
||||
reducers[idx] = handler.AggregateReducer{
|
||||
Aggregate: aggregates[idx],
|
||||
EventRedusers: []handler.EventReducer{{
|
||||
Event: "any.event",
|
||||
Reduce: testReduceErr(errors.New("should not be called")),
|
||||
}},
|
||||
}
|
||||
}
|
||||
return reducers
|
||||
}
|
||||
|
@@ -235,12 +235,6 @@ func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func
|
||||
}
|
||||
}
|
||||
|
||||
func AddCopyStatement(conflict, from, to []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.Event) Exec {
|
||||
return func(event eventstore.Event) Exec {
|
||||
return NewCopyStatement(event, conflict, from, to, conditions, opts...).Execute
|
||||
}
|
||||
}
|
||||
|
||||
func NewArrayAppendCol(column string, value interface{}) handler.Column {
|
||||
return handler.Column{
|
||||
Name: column,
|
||||
@@ -286,12 +280,30 @@ func NewCopyCol(column, from string) handler.Column {
|
||||
}
|
||||
|
||||
func NewLessThanCond(column string, value interface{}) handler.Condition {
|
||||
return handler.Condition{
|
||||
Name: column,
|
||||
Value: value,
|
||||
ParameterOpt: func(placeholder string) string {
|
||||
return " < " + placeholder
|
||||
},
|
||||
return func(param string) (string, interface{}) {
|
||||
return column + " < " + param, value
|
||||
}
|
||||
}
|
||||
|
||||
func NewIsNullCond(column string) handler.Condition {
|
||||
return func(param string) (string, interface{}) {
|
||||
return column + " IS NULL", nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewTextArrayContainsCond returns a handler.Condition that checks if the column that stores an array of text contains the given value
|
||||
func NewTextArrayContainsCond(column string, value string) handler.Condition {
|
||||
return func(param string) (string, interface{}) {
|
||||
return column + " @> " + param, database.StringArray{value}
|
||||
}
|
||||
}
|
||||
|
||||
// Not is a function and not a method, so that calling it is well readable
|
||||
// For example conditions := []handler.Condition{ Not(NewTextArrayContainsCond())}
|
||||
func Not(condition handler.Condition) handler.Condition {
|
||||
return func(param string) (string, interface{}) {
|
||||
cond, value := condition(param)
|
||||
return "NOT (" + cond + ")", value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +312,7 @@ func NewLessThanCond(column string, value interface{}) handler.Condition {
|
||||
// if the value of a col is empty the data will be copied from the selected row
|
||||
// if the value of a col is not empty the data will be set by the static value
|
||||
// conds represent the conditions for the selection subquery
|
||||
func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement {
|
||||
func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.Column, nsCond []handler.NamespacedCondition, opts ...execOption) *handler.Statement {
|
||||
columnNames := make([]string, len(to))
|
||||
selectColumns := make([]string, len(from))
|
||||
updateColumns := make([]string, len(columnNames))
|
||||
@@ -319,13 +331,12 @@ func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.C
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wheres := make([]string, len(conds))
|
||||
for i, cond := range conds {
|
||||
argCounter++
|
||||
wheres[i] = "copy_table." + cond.Name + " = $" + strconv.Itoa(argCounter)
|
||||
args = append(args, cond.Value)
|
||||
cond := make([]handler.Condition, len(nsCond))
|
||||
for i := range nsCond {
|
||||
cond[i] = nsCond[i]("copy_table")
|
||||
}
|
||||
wheres, values := conditionsToWhere(cond, len(args))
|
||||
args = append(args, values...)
|
||||
|
||||
conflictTargets := make([]string, len(conflictCols))
|
||||
for i, conflictCol := range conflictCols {
|
||||
@@ -340,7 +351,7 @@ func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.C
|
||||
config.err = handler.ErrNoValues
|
||||
}
|
||||
|
||||
if len(conds) == 0 {
|
||||
if len(cond) == 0 {
|
||||
config.err = handler.ErrNoCondition
|
||||
}
|
||||
|
||||
@@ -394,18 +405,16 @@ func columnsToQuery(cols []handler.Column) (names []string, parameters []string,
|
||||
return names, parameters, values[:parameterIndex]
|
||||
}
|
||||
|
||||
func conditionsToWhere(cols []handler.Condition, paramOffset int) (wheres []string, values []interface{}) {
|
||||
wheres = make([]string, len(cols))
|
||||
values = make([]interface{}, len(cols))
|
||||
|
||||
for i, col := range cols {
|
||||
wheres[i] = "(" + col.Name + " = $" + strconv.Itoa(i+1+paramOffset) + ")"
|
||||
if col.ParameterOpt != nil {
|
||||
wheres[i] = "(" + col.Name + col.ParameterOpt("$"+strconv.Itoa(i+1+paramOffset)) + ")"
|
||||
func conditionsToWhere(conditions []handler.Condition, paramOffset int) (wheres []string, values []interface{}) {
|
||||
wheres = make([]string, len(conditions))
|
||||
values = make([]interface{}, 0, len(conditions))
|
||||
for i, conditionFunc := range conditions {
|
||||
condition, value := conditionFunc("$" + strconv.Itoa(i+1+paramOffset))
|
||||
wheres[i] = "(" + condition + ")"
|
||||
if value != nil {
|
||||
values = append(values, value)
|
||||
}
|
||||
values[i] = col.Value
|
||||
}
|
||||
|
||||
return wheres, values
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
)
|
||||
@@ -420,10 +421,7 @@ func TestNewUpdateStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -450,10 +448,7 @@ func TestNewUpdateStatement(t *testing.T) {
|
||||
},
|
||||
values: []handler.Column{},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -515,10 +510,7 @@ func TestNewUpdateStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -560,10 +552,7 @@ func TestNewUpdateStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -630,10 +619,7 @@ func TestNewDeleteStatement(t *testing.T) {
|
||||
previousSequence: 0,
|
||||
},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -683,10 +669,7 @@ func TestNewDeleteStatement(t *testing.T) {
|
||||
aggregateType: "agg",
|
||||
},
|
||||
conditions: []handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: 1,
|
||||
},
|
||||
handler.NewCond("col1", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -842,11 +825,9 @@ func TestNewMultiStatement(t *testing.T) {
|
||||
execs: []func(eventstore.Event) Exec{
|
||||
AddDeleteStatement(
|
||||
[]handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: 1,
|
||||
},
|
||||
}),
|
||||
handler.NewCond("col1", 1),
|
||||
},
|
||||
),
|
||||
AddCreateStatement(
|
||||
[]handler.Column{
|
||||
{
|
||||
@@ -876,11 +857,9 @@ func TestNewMultiStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
[]handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: 1,
|
||||
},
|
||||
}),
|
||||
handler.NewCond("col1", 1),
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -942,7 +921,7 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
conflictingCols []handler.Column
|
||||
from []handler.Column
|
||||
to []handler.Column
|
||||
conds []handler.Condition
|
||||
conds []handler.NamespacedCondition
|
||||
}
|
||||
type want struct {
|
||||
aggregateType eventstore.AggregateType
|
||||
@@ -966,11 +945,8 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "col2",
|
||||
Value: 1,
|
||||
},
|
||||
conds: []handler.NamespacedCondition{
|
||||
handler.NewNamespacedCondition("col2", 1),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -995,7 +971,7 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conds: []handler.Condition{},
|
||||
conds: []handler.NamespacedCondition{},
|
||||
from: []handler.Column{
|
||||
{
|
||||
Name: "col",
|
||||
@@ -1029,7 +1005,7 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conds: []handler.Condition{},
|
||||
conds: []handler.NamespacedCondition{},
|
||||
from: []handler.Column{
|
||||
{
|
||||
Name: "col",
|
||||
@@ -1066,10 +1042,8 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "col",
|
||||
},
|
||||
conds: []handler.NamespacedCondition{
|
||||
handler.NewNamespacedCondition("col2", nil),
|
||||
},
|
||||
from: []handler.Column{},
|
||||
},
|
||||
@@ -1124,15 +1098,9 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
Name: "col_b",
|
||||
},
|
||||
},
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "id",
|
||||
Value: 2,
|
||||
},
|
||||
{
|
||||
Name: "state",
|
||||
Value: 3,
|
||||
},
|
||||
conds: []handler.NamespacedCondition{
|
||||
handler.NewNamespacedCondition("id", 2),
|
||||
handler.NewNamespacedCondition("state", 3),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -1143,7 +1111,7 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
executer: &wantExecuter{
|
||||
params: []params{
|
||||
{
|
||||
query: "INSERT INTO my_table (state, id, col_a, col_b) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3 ON CONFLICT () DO UPDATE SET (state, id, col_a, col_b) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
|
||||
query: "INSERT INTO my_table (state, id, col_a, col_b) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE (copy_table.id = $2) AND (copy_table.state = $3) ON CONFLICT () DO UPDATE SET (state, id, col_a, col_b) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
|
||||
args: []interface{}{1, 2, 3},
|
||||
},
|
||||
},
|
||||
@@ -1191,15 +1159,9 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
Name: "col_d",
|
||||
},
|
||||
},
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "id",
|
||||
Value: 2,
|
||||
},
|
||||
{
|
||||
Name: "state",
|
||||
Value: 3,
|
||||
},
|
||||
conds: []handler.NamespacedCondition{
|
||||
handler.NewNamespacedCondition("id", 2),
|
||||
handler.NewNamespacedCondition("state", 3),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
@@ -1210,7 +1172,7 @@ func TestNewCopyStatement(t *testing.T) {
|
||||
executer: &wantExecuter{
|
||||
params: []params{
|
||||
{
|
||||
query: "INSERT INTO my_table (state, id, col_c, col_d) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3 ON CONFLICT () DO UPDATE SET (state, id, col_c, col_d) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
|
||||
query: "INSERT INTO my_table (state, id, col_c, col_d) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE (copy_table.id = $2) AND (copy_table.state = $3) ON CONFLICT () DO UPDATE SET (state, id, col_c, col_d) = ($1, EXCLUDED.id, EXCLUDED.col_a, EXCLUDED.col_b)",
|
||||
args: []interface{}{1, 2, 3},
|
||||
},
|
||||
},
|
||||
@@ -1395,7 +1357,7 @@ func Test_columnsToQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_columnsToWhere(t *testing.T) {
|
||||
func Test_conditionsToWhere(t *testing.T) {
|
||||
type args struct {
|
||||
conds []handler.Condition
|
||||
paramOffset int
|
||||
@@ -1421,10 +1383,7 @@ func Test_columnsToWhere(t *testing.T) {
|
||||
name: "no offset",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: "val1",
|
||||
},
|
||||
handler.NewCond("col1", "val1"),
|
||||
},
|
||||
paramOffset: 0,
|
||||
},
|
||||
@@ -1437,14 +1396,8 @@ func Test_columnsToWhere(t *testing.T) {
|
||||
name: "multiple cols",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: "val1",
|
||||
},
|
||||
{
|
||||
Name: "col2",
|
||||
Value: "val2",
|
||||
},
|
||||
handler.NewCond("col1", "val1"),
|
||||
handler.NewCond("col2", "val2"),
|
||||
},
|
||||
paramOffset: 0,
|
||||
},
|
||||
@@ -1457,10 +1410,7 @@ func Test_columnsToWhere(t *testing.T) {
|
||||
name: "2 offset",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: "val1",
|
||||
},
|
||||
handler.NewCond("col1", "val1"),
|
||||
},
|
||||
paramOffset: 2,
|
||||
},
|
||||
@@ -1469,6 +1419,54 @@ func Test_columnsToWhere(t *testing.T) {
|
||||
values: []interface{}{"val1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "less than",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
NewLessThanCond("col1", "val1"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
wheres: []string{"(col1 < $1)"},
|
||||
values: []interface{}{"val1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "is null",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
NewIsNullCond("col1"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
wheres: []string{"(col1 IS NULL)"},
|
||||
values: []interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "text array contains",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
NewTextArrayContainsCond("col1", "val1"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
wheres: []string{"(col1 @> $1)"},
|
||||
values: []interface{}{database.StringArray{"val1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not",
|
||||
args: args{
|
||||
conds: []handler.Condition{
|
||||
Not(handler.NewCond("col1", "val1")),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
wheres: []string{"(NOT (col1 = $1))"},
|
||||
values: []interface{}{"val1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/pseudo"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,19 +50,20 @@ type NowFunc func() time.Time
|
||||
|
||||
type ProjectionHandler struct {
|
||||
Handler
|
||||
ProjectionName string
|
||||
reduce Reduce
|
||||
update Update
|
||||
searchQuery SearchQuery
|
||||
triggerProjection *time.Timer
|
||||
lock Lock
|
||||
unlock Unlock
|
||||
requeueAfter time.Duration
|
||||
retryFailedAfter time.Duration
|
||||
retries int
|
||||
concurrentInstances int
|
||||
handleActiveInstances time.Duration
|
||||
nowFunc NowFunc
|
||||
ProjectionName string
|
||||
reduce Reduce
|
||||
update Update
|
||||
searchQuery SearchQuery
|
||||
triggerProjection *time.Timer
|
||||
lock Lock
|
||||
unlock Unlock
|
||||
requeueAfter time.Duration
|
||||
retryFailedAfter time.Duration
|
||||
retries int
|
||||
concurrentInstances int
|
||||
handleActiveInstances time.Duration
|
||||
nowFunc NowFunc
|
||||
reduceScheduledPseudoEvent bool
|
||||
}
|
||||
|
||||
func NewProjectionHandler(
|
||||
@@ -73,32 +75,35 @@ func NewProjectionHandler(
|
||||
lock Lock,
|
||||
unlock Unlock,
|
||||
initialized <-chan bool,
|
||||
reduceScheduledPseudoEvent bool,
|
||||
) *ProjectionHandler {
|
||||
concurrentInstances := int(config.ConcurrentInstances)
|
||||
if concurrentInstances < 1 {
|
||||
concurrentInstances = 1
|
||||
}
|
||||
h := &ProjectionHandler{
|
||||
Handler: NewHandler(config.HandlerConfig),
|
||||
ProjectionName: config.ProjectionName,
|
||||
reduce: reduce,
|
||||
update: update,
|
||||
searchQuery: query,
|
||||
lock: lock,
|
||||
unlock: unlock,
|
||||
requeueAfter: config.RequeueEvery,
|
||||
triggerProjection: time.NewTimer(0), // first trigger is instant on startup
|
||||
retryFailedAfter: config.RetryFailedAfter,
|
||||
retries: int(config.Retries),
|
||||
concurrentInstances: concurrentInstances,
|
||||
handleActiveInstances: config.HandleActiveInstances,
|
||||
nowFunc: time.Now,
|
||||
Handler: NewHandler(config.HandlerConfig),
|
||||
ProjectionName: config.ProjectionName,
|
||||
reduce: reduce,
|
||||
update: update,
|
||||
searchQuery: query,
|
||||
lock: lock,
|
||||
unlock: unlock,
|
||||
requeueAfter: config.RequeueEvery,
|
||||
triggerProjection: time.NewTimer(0), // first trigger is instant on startup
|
||||
retryFailedAfter: config.RetryFailedAfter,
|
||||
retries: int(config.Retries),
|
||||
concurrentInstances: concurrentInstances,
|
||||
handleActiveInstances: config.HandleActiveInstances,
|
||||
nowFunc: time.Now,
|
||||
reduceScheduledPseudoEvent: reduceScheduledPseudoEvent,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-initialized
|
||||
go h.subscribe(ctx)
|
||||
|
||||
if !h.reduceScheduledPseudoEvent {
|
||||
go h.subscribe(ctx)
|
||||
}
|
||||
go h.schedule(ctx)
|
||||
}()
|
||||
|
||||
@@ -158,6 +163,13 @@ func (h *ProjectionHandler) Process(ctx context.Context, events ...eventstore.Ev
|
||||
|
||||
// FetchEvents checks the current sequences and filters for newer events
|
||||
func (h *ProjectionHandler) FetchEvents(ctx context.Context, instances ...string) ([]eventstore.Event, bool, error) {
|
||||
if h.reduceScheduledPseudoEvent {
|
||||
return h.fetchPseudoEvents(ctx, instances...)
|
||||
}
|
||||
return h.fetchDBEvents(ctx, instances...)
|
||||
}
|
||||
|
||||
func (h *ProjectionHandler) fetchDBEvents(ctx context.Context, instances ...string) ([]eventstore.Event, bool, error) {
|
||||
eventQuery, eventsLimit, err := h.searchQuery(ctx, instances)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -169,6 +181,10 @@ func (h *ProjectionHandler) FetchEvents(ctx context.Context, instances ...string
|
||||
return events, int(eventsLimit) == len(events), err
|
||||
}
|
||||
|
||||
func (h *ProjectionHandler) fetchPseudoEvents(ctx context.Context, instances ...string) ([]eventstore.Event, bool, error) {
|
||||
return []eventstore.Event{pseudo.NewScheduledEvent(ctx, time.Now(), instances...)}, false, nil
|
||||
}
|
||||
|
||||
func (h *ProjectionHandler) subscribe(ctx context.Context) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
|
@@ -342,6 +342,7 @@ func TestProjectionHandler_Process(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
|
||||
index, err := h.Process(tt.args.ctx, tt.args.events...)
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -62,11 +61,18 @@ func NewJSONCol(name string, value interface{}) Column {
|
||||
return NewCol(name, marshalled)
|
||||
}
|
||||
|
||||
type Condition Column
|
||||
type Condition func(param string) (string, interface{})
|
||||
|
||||
type NamespacedCondition func(namespace string) Condition
|
||||
|
||||
func NewCond(name string, value interface{}) Condition {
|
||||
return Condition{
|
||||
Name: name,
|
||||
Value: value,
|
||||
return func(param string) (string, interface{}) {
|
||||
return name + " = " + param, value
|
||||
}
|
||||
}
|
||||
|
||||
func NewNamespacedCondition(name string, value interface{}) NamespacedCondition {
|
||||
return func(namespace string) Condition {
|
||||
return NewCond(namespace+"."+name, value)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user