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:
Elio Bischof
2023-07-06 08:38:13 +02:00
committed by GitHub
parent fa93bb7e85
commit bb756482c7
53 changed files with 2214 additions and 231 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -342,6 +342,7 @@ func TestProjectionHandler_Process(t *testing.T) {
nil,
nil,
nil,
false,
)
index, err := h.Process(tt.args.ctx, tt.args.events...)

View File

@@ -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)
}
}