fix: handle multiple statements for a single event in projections (#2313)

* fix: handle multiple statements for a single event in projections

* export func type

* fix test

* Update internal/eventstore/handler/crdb/statement.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update internal/eventstore/handler/crdb/statement.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* change to pointers

* add error test case

Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Livio Amstutz 2021-09-08 13:54:31 +02:00 committed by GitHub
parent ec6a3a1847
commit 1ac1492fd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 546 additions and 301 deletions

View File

@ -20,7 +20,7 @@ const (
") AS failure_count" ") AS failure_count"
) )
func (h *StatementHandler) handleFailedStmt(tx *sql.Tx, stmt handler.Statement, execErr error) (shouldContinue bool) { func (h *StatementHandler) handleFailedStmt(tx *sql.Tx, stmt *handler.Statement, execErr error) (shouldContinue bool) {
failureCount, err := h.failureCount(tx, stmt.Sequence) failureCount, err := h.failureCount(tx, stmt.Sequence)
if err != nil { if err != nil {
logging.LogWithFields("CRDB-WJaFk", "projection", h.ProjectionName, "seq", stmt.Sequence).WithError(err).Warn("unable to get failure count") logging.LogWithFields("CRDB-WJaFk", "projection", h.ProjectionName, "seq", stmt.Sequence).WithError(err).Warn("unable to get failure count")

View File

@ -116,7 +116,7 @@ func (h *StatementHandler) SearchQuery() (*eventstore.SearchQueryBuilder, uint64
} }
//Update implements handler.Update //Update implements handler.Update
func (h *StatementHandler) Update(ctx context.Context, stmts []handler.Statement, reduce handler.Reduce) (unexecutedStmts []handler.Statement, err error) { func (h *StatementHandler) Update(ctx context.Context, stmts []*handler.Statement, reduce handler.Reduce) (unexecutedStmts []*handler.Statement, err error) {
tx, err := h.client.BeginTx(ctx, nil) tx, err := h.client.BeginTx(ctx, nil)
if err != nil { if err != nil {
return stmts, errors.ThrowInternal(err, "CRDB-e89Gq", "begin failed") return stmts, errors.ThrowInternal(err, "CRDB-e89Gq", "begin failed")
@ -158,7 +158,7 @@ func (h *StatementHandler) Update(ctx context.Context, stmts []handler.Statement
return stmts, handler.ErrSomeStmtsFailed return stmts, handler.ErrSomeStmtsFailed
} }
unexecutedStmts = make([]handler.Statement, len(stmts)-(lastSuccessfulIdx+1)) unexecutedStmts = make([]*handler.Statement, len(stmts)-(lastSuccessfulIdx+1))
copy(unexecutedStmts, stmts[lastSuccessfulIdx+1:]) copy(unexecutedStmts, stmts[lastSuccessfulIdx+1:])
stmts = nil stmts = nil
@ -174,7 +174,7 @@ func (h *StatementHandler) fetchPreviousStmts(
stmtSeq uint64, stmtSeq uint64,
sequences currentSequences, sequences currentSequences,
reduce handler.Reduce, reduce handler.Reduce,
) (previousStmts []handler.Statement, err error) { ) (previousStmts []*handler.Statement, err error) {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent) query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent)
queriesAdded := false queriesAdded := false
@ -202,18 +202,18 @@ func (h *StatementHandler) fetchPreviousStmts(
} }
for _, event := range events { for _, event := range events {
stmts, err := reduce(event) stmt, err := reduce(event)
if err != nil { if err != nil {
return nil, err return nil, err
} }
previousStmts = append(previousStmts, stmts...) previousStmts = append(previousStmts, stmt)
} }
return previousStmts, nil return previousStmts, nil
} }
func (h *StatementHandler) executeStmts( func (h *StatementHandler) executeStmts(
tx *sql.Tx, tx *sql.Tx,
stmts []handler.Statement, stmts []*handler.Statement,
sequences currentSequences, sequences currentSequences,
) int { ) int {
@ -244,7 +244,7 @@ func (h *StatementHandler) executeStmts(
//executeStmt handles sql statements //executeStmt handles sql statements
//an error is returned if the statement could not be inserted properly //an error is returned if the statement could not be inserted properly
func (h *StatementHandler) executeStmt(tx *sql.Tx, stmt handler.Statement) error { func (h *StatementHandler) executeStmt(tx *sql.Tx, stmt *handler.Statement) error {
if stmt.IsNoop() { if stmt.IsNoop() {
return nil return nil
} }

View File

@ -162,7 +162,7 @@ func TestStatementHandler_Update(t *testing.T) {
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
stmts []handler.Statement stmts []*handler.Statement
reduce handler.Reduce reduce handler.Reduce
} }
tests := []struct { tests := []struct {
@ -212,7 +212,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewNoOpStatement(&testEvent{ NewNoOpStatement(&testEvent{
aggregateType: "agg", aggregateType: "agg",
sequence: 6, sequence: 6,
@ -242,7 +242,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "testAgg", aggregateType: "testAgg",
@ -279,7 +279,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -320,7 +320,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -361,7 +361,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewNoOpStatement(&testEvent{ NewNoOpStatement(&testEvent{
aggregateType: "testAgg", aggregateType: "testAgg",
sequence: 7, sequence: 7,
@ -392,7 +392,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewNoOpStatement(&testEvent{ NewNoOpStatement(&testEvent{
aggregateType: "testAgg", aggregateType: "testAgg",
sequence: 7, sequence: 7,
@ -428,7 +428,7 @@ func TestStatementHandler_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewNoOpStatement(&testEvent{ NewNoOpStatement(&testEvent{
aggregateType: "testAgg", aggregateType: "testAgg",
sequence: 7, sequence: 7,
@ -684,7 +684,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
failedEventsTable string failedEventsTable string
} }
type args struct { type args struct {
stmts []handler.Statement stmts []*handler.Statement
sequences currentSequences sequences currentSequences
} }
type want struct { type want struct {
@ -703,7 +703,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
projectionName: "my_projection", projectionName: "my_projection",
}, },
args: args{ args: args{
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -732,7 +732,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
projectionName: "my_projection", projectionName: "my_projection",
}, },
args: args{ args: args{
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -780,7 +780,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
failedEventsTable: "failed_events", failedEventsTable: "failed_events",
}, },
args: args{ args: args{
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -844,7 +844,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
failedEventsTable: "failed_events", failedEventsTable: "failed_events",
}, },
args: args{ args: args{
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -909,7 +909,7 @@ func TestStatementHandler_executeStmts(t *testing.T) {
projectionName: "my_projection", projectionName: "my_projection",
}, },
args: args{ args: args{
stmts: []handler.Statement{ stmts: []*handler.Statement{
NewCreateStatement( NewCreateStatement(
&testEvent{ &testEvent{
aggregateType: "agg", aggregateType: "agg",
@ -946,6 +946,29 @@ func TestStatementHandler_executeStmts(t *testing.T) {
Value: "val", Value: "val",
}, },
}), }),
NewMultiStatement(
&testEvent{
aggregateType: "agg",
sequence: 8,
previousSequence: 7,
},
AddCreateStatement(
[]handler.Column{
{
Name: "col",
Value: "val",
},
},
),
AddCreateStatement(
[]handler.Column{
{
Name: "col",
Value: "val",
},
},
),
),
}, },
sequences: currentSequences{ sequences: currentSequences{
"agg": 2, "agg": 2,
@ -962,8 +985,12 @@ func TestStatementHandler_executeStmts(t *testing.T) {
expectSavePoint(), expectSavePoint(),
expectCreate("my_projection", []string{"col"}, []string{"$1"}), expectCreate("my_projection", []string{"col"}, []string{"$1"}),
expectSavePointRelease(), expectSavePointRelease(),
expectSavePoint(),
expectCreate("my_projection", []string{"col"}, []string{"$1"}),
expectCreate("my_projection", []string{"col"}, []string{"$1"}),
expectSavePointRelease(),
}, },
idx: 2, idx: 3,
}, },
}, },
} }
@ -1025,7 +1052,7 @@ func TestStatementHandler_executeStmt(t *testing.T) {
projectionName string projectionName string
} }
type args struct { type args struct {
stmt handler.Statement stmt *handler.Statement
} }
type want struct { type want struct {
expectations []mockExpectation expectations []mockExpectation
@ -1532,16 +1559,14 @@ func TestStatementHandler_updateCurrentSequence(t *testing.T) {
} }
} }
func testReduce(stmts ...handler.Statement) handler.Reduce { func testReduce() handler.Reduce {
return func(event eventstore.EventReader) ([]handler.Statement, error) { return func(event eventstore.EventReader) (*handler.Statement, error) {
return []handler.Statement{ return NewNoOpStatement(event), nil
NewNoOpStatement(event),
}, nil
} }
} }
func testReduceErr(err error) handler.Reduce { func testReduceErr(err error) handler.Reduce {
return func(event eventstore.EventReader) ([]handler.Statement, error) { return func(event eventstore.EventReader) (*handler.Statement, error) {
return nil, err return nil, err
} }
} }

View File

@ -6,10 +6,10 @@ import (
) )
//reduce implements handler.Reduce function //reduce implements handler.Reduce function
func (h *StatementHandler) reduce(event eventstore.EventReader) ([]handler.Statement, error) { func (h *StatementHandler) reduce(event eventstore.EventReader) (*handler.Statement, error) {
reduce, ok := h.reduces[event.Type()] reduce, ok := h.reduces[event.Type()]
if !ok { if !ok {
return []handler.Statement{NewNoOpStatement(event)}, nil return NewNoOpStatement(event), nil
} }
return reduce(event) return reduce(event)

View File

@ -23,7 +23,7 @@ func WithTableSuffix(name string) func(*execConfig) {
} }
} }
func NewCreateStatement(event eventstore.EventReader, values []handler.Column, opts ...execOption) handler.Statement { func NewCreateStatement(event eventstore.EventReader, values []handler.Column, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values) cols, params, args := columnsToQuery(values)
columnNames := strings.Join(cols, ", ") columnNames := strings.Join(cols, ", ")
valuesPlaceholder := strings.Join(params, ", ") valuesPlaceholder := strings.Join(params, ", ")
@ -40,7 +40,7 @@ func NewCreateStatement(event eventstore.EventReader, values []handler.Column, o
return "INSERT INTO " + config.tableName + " (" + columnNames + ") VALUES (" + valuesPlaceholder + ")" return "INSERT INTO " + config.tableName + " (" + columnNames + ") VALUES (" + valuesPlaceholder + ")"
} }
return handler.Statement{ return &handler.Statement{
AggregateType: event.Aggregate().Type, AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(), Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(), PreviousSequence: event.PreviousAggregateTypeSequence(),
@ -48,7 +48,7 @@ func NewCreateStatement(event eventstore.EventReader, values []handler.Column, o
} }
} }
func NewUpsertStatement(event eventstore.EventReader, values []handler.Column, opts ...execOption) handler.Statement { func NewUpsertStatement(event eventstore.EventReader, values []handler.Column, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values) cols, params, args := columnsToQuery(values)
columnNames := strings.Join(cols, ", ") columnNames := strings.Join(cols, ", ")
valuesPlaceholder := strings.Join(params, ", ") valuesPlaceholder := strings.Join(params, ", ")
@ -65,7 +65,7 @@ func NewUpsertStatement(event eventstore.EventReader, values []handler.Column, o
return "UPSERT INTO " + config.tableName + " (" + columnNames + ") VALUES (" + valuesPlaceholder + ")" return "UPSERT INTO " + config.tableName + " (" + columnNames + ") VALUES (" + valuesPlaceholder + ")"
} }
return handler.Statement{ return &handler.Statement{
AggregateType: event.Aggregate().Type, AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(), Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(), PreviousSequence: event.PreviousAggregateTypeSequence(),
@ -73,7 +73,7 @@ func NewUpsertStatement(event eventstore.EventReader, values []handler.Column, o
} }
} }
func NewUpdateStatement(event eventstore.EventReader, values []handler.Column, conditions []handler.Condition, opts ...execOption) handler.Statement { func NewUpdateStatement(event eventstore.EventReader, values []handler.Column, conditions []handler.Condition, opts ...execOption) *handler.Statement {
cols, params, args := columnsToQuery(values) cols, params, args := columnsToQuery(values)
wheres, whereArgs := conditionsToWhere(conditions, len(params)) wheres, whereArgs := conditionsToWhere(conditions, len(params))
args = append(args, whereArgs...) args = append(args, whereArgs...)
@ -98,7 +98,7 @@ func NewUpdateStatement(event eventstore.EventReader, values []handler.Column, c
return "UPDATE " + config.tableName + " SET (" + columnNames + ") = (" + valuesPlaceholder + ") WHERE " + wheresPlaceholders return "UPDATE " + config.tableName + " SET (" + columnNames + ") = (" + valuesPlaceholder + ") WHERE " + wheresPlaceholders
} }
return handler.Statement{ return &handler.Statement{
AggregateType: event.Aggregate().Type, AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(), Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(), PreviousSequence: event.PreviousAggregateTypeSequence(),
@ -106,7 +106,7 @@ func NewUpdateStatement(event eventstore.EventReader, values []handler.Column, c
} }
} }
func NewDeleteStatement(event eventstore.EventReader, conditions []handler.Condition, opts ...execOption) handler.Statement { func NewDeleteStatement(event eventstore.EventReader, conditions []handler.Condition, opts ...execOption) *handler.Statement {
wheres, args := conditionsToWhere(conditions, 0) wheres, args := conditionsToWhere(conditions, 0)
wheresPlaceholders := strings.Join(wheres, " AND ") wheresPlaceholders := strings.Join(wheres, " AND ")
@ -123,7 +123,7 @@ func NewDeleteStatement(event eventstore.EventReader, conditions []handler.Condi
return "DELETE FROM " + config.tableName + " WHERE " + wheresPlaceholders return "DELETE FROM " + config.tableName + " WHERE " + wheresPlaceholders
} }
return handler.Statement{ return &handler.Statement{
AggregateType: event.Aggregate().Type, AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(), Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(), PreviousSequence: event.PreviousAggregateTypeSequence(),
@ -131,14 +131,56 @@ func NewDeleteStatement(event eventstore.EventReader, conditions []handler.Condi
} }
} }
func NewNoOpStatement(event eventstore.EventReader) handler.Statement { func NewNoOpStatement(event eventstore.EventReader) *handler.Statement {
return handler.Statement{ return &handler.Statement{
AggregateType: event.Aggregate().Type, AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(), Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(), PreviousSequence: event.PreviousAggregateTypeSequence(),
} }
} }
func NewMultiStatement(event eventstore.EventReader, opts ...func(eventstore.EventReader) Exec) *handler.Statement {
if len(opts) == 0 {
return NewNoOpStatement(event)
}
execs := make([]Exec, len(opts))
for i, opt := range opts {
execs[i] = opt(event)
}
return &handler.Statement{
AggregateType: event.Aggregate().Type,
Sequence: event.Sequence(),
PreviousSequence: event.PreviousAggregateTypeSequence(),
Execute: multiExec(execs),
}
}
type Exec func(ex handler.Executer, projectionName string) error
func AddCreateStatement(columns []handler.Column, opts ...execOption) func(eventstore.EventReader) Exec {
return func(event eventstore.EventReader) Exec {
return NewCreateStatement(event, columns, opts...).Execute
}
}
func AddUpsertStatement(values []handler.Column, opts ...execOption) func(eventstore.EventReader) Exec {
return func(event eventstore.EventReader) Exec {
return NewUpsertStatement(event, values, opts...).Execute
}
}
func AddUpdateStatement(values []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.EventReader) Exec {
return func(event eventstore.EventReader) Exec {
return NewUpdateStatement(event, values, conditions, opts...).Execute
}
}
func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func(eventstore.EventReader) Exec {
return func(event eventstore.EventReader) Exec {
return NewDeleteStatement(event, conditions, opts...).Execute
}
}
func columnsToQuery(cols []handler.Column) (names []string, parameters []string, values []interface{}) { func columnsToQuery(cols []handler.Column) (names []string, parameters []string, values []interface{}) {
names = make([]string, len(cols)) names = make([]string, len(cols))
values = make([]interface{}, len(cols)) values = make([]interface{}, len(cols))
@ -166,7 +208,7 @@ func conditionsToWhere(cols []handler.Condition, paramOffset int) (wheres []stri
type query func(config execConfig) string type query func(config execConfig) string
func exec(config execConfig, q query, opts []execOption) func(ex handler.Executer, projectionName string) error { func exec(config execConfig, q query, opts []execOption) Exec {
return func(ex handler.Executer, projectionName string) error { return func(ex handler.Executer, projectionName string) error {
if projectionName == "" { if projectionName == "" {
return handler.ErrNoProjection return handler.ErrNoProjection
@ -188,3 +230,14 @@ func exec(config execConfig, q query, opts []execOption) func(ex handler.Execute
return nil return nil
} }
} }
func multiExec(execList []Exec) Exec {
return func(ex handler.Executer, projectionName string) error {
for _, exec := range execList {
if err := exec(ex, projectionName); err != nil {
return err
}
}
return nil
}
}

View File

@ -11,13 +11,18 @@ import (
) )
type wantExecuter struct { type wantExecuter struct {
query string params []params
args []interface{} i int
t *testing.T t *testing.T
wasExecuted bool wasExecuted bool
shouldExecute bool shouldExecute bool
} }
type params struct {
query string
args []interface{}
}
var errTestErr = errors.New("some error") var errTestErr = errors.New("some error")
func (ex *wantExecuter) check(t *testing.T) { func (ex *wantExecuter) check(t *testing.T) {
@ -34,12 +39,18 @@ func (ex *wantExecuter) check(t *testing.T) {
func (ex *wantExecuter) Exec(query string, args ...interface{}) (sql.Result, error) { func (ex *wantExecuter) Exec(query string, args ...interface{}) (sql.Result, error) {
ex.t.Helper() ex.t.Helper()
ex.wasExecuted = true ex.wasExecuted = true
if query != ex.query { if ex.i >= len(ex.params) {
ex.t.Errorf("wrong query:\n expected:\n %q\n got:\n %q", ex.query, query) ex.t.Errorf("did not expect more exec, but got:\n %q with %q", query, args)
return nil, nil
} }
if !reflect.DeepEqual(ex.args, args) { p := ex.params[ex.i]
ex.t.Errorf("wrong args:\n expected:\n %v\n got:\n %v", ex.args, args) if query != p.query {
ex.t.Errorf("wrong query:\n expected:\n %q\n got:\n %q", p.query, query)
} }
if !reflect.DeepEqual(p.args, args) {
ex.t.Errorf("wrong args:\n expected:\n %v\n got:\n %v", p.args, args)
}
ex.i++
return nil, nil return nil, nil
} }
@ -137,9 +148,13 @@ func TestNewCreateStatement(t *testing.T) {
sequence: 1, sequence: 1,
previousSequence: 1, previousSequence: 1,
executer: &wantExecuter{ executer: &wantExecuter{
query: "INSERT INTO my_table (col1) VALUES ($1)", params: []params{
{
query: "INSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{"val"},
},
},
shouldExecute: true, shouldExecute: true,
args: []interface{}{"val"},
}, },
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
@ -255,9 +270,13 @@ func TestNewUpsertStatement(t *testing.T) {
sequence: 1, sequence: 1,
previousSequence: 1, previousSequence: 1,
executer: &wantExecuter{ executer: &wantExecuter{
query: "UPSERT INTO my_table (col1) VALUES ($1)", params: []params{
{
query: "UPSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{"val"},
},
},
shouldExecute: true, shouldExecute: true,
args: []interface{}{"val"},
}, },
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
@ -422,9 +441,13 @@ func TestNewUpdateStatement(t *testing.T) {
sequence: 1, sequence: 1,
previousSequence: 1, previousSequence: 1,
executer: &wantExecuter{ executer: &wantExecuter{
query: "UPDATE my_table SET (col1) = ($1) WHERE (col2 = $2)", params: []params{
{
query: "UPDATE my_table SET (col1) = ($1) WHERE (col2 = $2)",
args: []interface{}{"val", 1},
},
},
shouldExecute: true, shouldExecute: true,
args: []interface{}{"val", 1},
}, },
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
@ -541,9 +564,13 @@ func TestNewDeleteStatement(t *testing.T) {
sequence: 1, sequence: 1,
previousSequence: 1, previousSequence: 1,
executer: &wantExecuter{ executer: &wantExecuter{
query: "DELETE FROM my_table WHERE (col1 = $1)", params: []params{
{
query: "DELETE FROM my_table WHERE (col1 = $1)",
args: []interface{}{1},
},
},
shouldExecute: true, shouldExecute: true,
args: []interface{}{1},
}, },
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
@ -572,7 +599,7 @@ func TestNewNoOpStatement(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want handler.Statement want *handler.Statement
}{ }{
{ {
name: "generate correctly", name: "generate correctly",
@ -583,7 +610,7 @@ func TestNewNoOpStatement(t *testing.T) {
previousSequence: 3, previousSequence: 3,
}, },
}, },
want: handler.Statement{ want: &handler.Statement{
AggregateType: "agg", AggregateType: "agg",
Execute: nil, Execute: nil,
Sequence: 5, Sequence: 5,
@ -600,6 +627,174 @@ func TestNewNoOpStatement(t *testing.T) {
} }
} }
func TestNewMultiStatement(t *testing.T) {
type args struct {
table string
event *testEvent
execs []func(eventstore.EventReader) Exec
}
type want struct {
table string
aggregateType eventstore.AggregateType
sequence uint64
previousSequence uint64
executer *wantExecuter
isErr func(error) bool
}
tests := []struct {
name string
args args
want want
}{
{
name: "no op",
args: args{
table: "my_table",
event: &testEvent{
aggregateType: "agg",
sequence: 1,
previousSequence: 0,
},
execs: nil,
},
want: want{
executer: nil,
},
},
{
name: "no condition",
args: args{
table: "my_table",
event: &testEvent{
aggregateType: "agg",
sequence: 1,
previousSequence: 0,
},
execs: []func(eventstore.EventReader) Exec{
AddDeleteStatement(
[]handler.Condition{},
),
AddCreateStatement(
[]handler.Column{
{
Name: "col1",
Value: 1,
},
}),
},
},
want: want{
table: "my_table",
aggregateType: "agg",
sequence: 1,
previousSequence: 1,
executer: &wantExecuter{
shouldExecute: false,
},
isErr: func(err error) bool {
return errors.Is(err, handler.ErrNoCondition)
},
},
},
{
name: "correct",
args: args{
table: "my_table",
event: &testEvent{
sequence: 1,
previousSequence: 0,
aggregateType: "agg",
},
execs: []func(eventstore.EventReader) Exec{
AddDeleteStatement(
[]handler.Condition{
{
Name: "col1",
Value: 1,
},
}),
AddCreateStatement(
[]handler.Column{
{
Name: "col1",
Value: 1,
},
}),
AddUpsertStatement(
[]handler.Column{
{
Name: "col1",
Value: 1,
},
}),
AddUpdateStatement(
[]handler.Column{
{
Name: "col1",
Value: 1,
},
},
[]handler.Condition{
{
Name: "col1",
Value: 1,
},
}),
},
},
want: want{
table: "my_table",
aggregateType: "agg",
sequence: 1,
previousSequence: 1,
executer: &wantExecuter{
params: []params{
{
query: "DELETE FROM my_table WHERE (col1 = $1)",
args: []interface{}{1},
},
{
query: "INSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{1},
},
{
query: "UPSERT INTO my_table (col1) VALUES ($1)",
args: []interface{}{1},
},
{
query: "UPDATE my_table SET (col1) = ($1) WHERE (col1 = $2)",
args: []interface{}{1, 1},
},
},
shouldExecute: true,
},
isErr: func(err error) bool {
return err == nil
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stmt := NewMultiStatement(tt.args.event, tt.args.execs...)
if tt.want.executer != nil && stmt.Execute == nil {
t.Error("expected executer, but was nil")
}
if stmt.Execute == nil {
return
}
tt.want.executer.t = t
err := stmt.Execute(tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
tt.want.executer.check(t)
})
}
}
func TestStatement_Execute(t *testing.T) { func TestStatement_Execute(t *testing.T) {
type fields struct { type fields struct {
execute func(ex handler.Executer, projectionName string) error execute func(ex handler.Executer, projectionName string) error

View File

@ -18,11 +18,11 @@ type ProjectionHandlerConfig struct {
} }
//Update updates the projection with the given statements //Update updates the projection with the given statements
type Update func(context.Context, []Statement, Reduce) (unexecutedStmts []Statement, err error) type Update func(context.Context, []*Statement, Reduce) (unexecutedStmts []*Statement, err error)
//Reduce reduces the given event to a statement //Reduce reduces the given event to a statement
//which is used to update the projection //which is used to update the projection
type Reduce func(eventstore.EventReader) ([]Statement, error) type Reduce func(eventstore.EventReader) (*Statement, error)
//Lock is used for mutex handling if needed on the projection //Lock is used for mutex handling if needed on the projection
type Lock func(context.Context, time.Duration) <-chan error type Lock func(context.Context, time.Duration) <-chan error
@ -46,7 +46,7 @@ type ProjectionHandler struct {
ProjectionName string ProjectionName string
lockMu sync.Mutex lockMu sync.Mutex
stmts []Statement stmts []*Statement
} }
func NewProjectionHandler(config ProjectionHandlerConfig) *ProjectionHandler { func NewProjectionHandler(config ProjectionHandlerConfig) *ProjectionHandler {
@ -156,7 +156,7 @@ func (h *ProjectionHandler) processEvent(
event eventstore.EventReader, event eventstore.EventReader,
reduce Reduce, reduce Reduce,
) error { ) error {
stmts, err := reduce(event) stmt, err := reduce(event)
if err != nil { if err != nil {
logging.Log("EVENT-PTr4j").WithError(err).Warn("unable to process event") logging.Log("EVENT-PTr4j").WithError(err).Warn("unable to process event")
return err return err
@ -165,7 +165,7 @@ func (h *ProjectionHandler) processEvent(
h.lockMu.Lock() h.lockMu.Lock()
defer h.lockMu.Unlock() defer h.lockMu.Unlock()
h.stmts = append(h.stmts, stmts...) h.stmts = append(h.stmts, stmt)
return nil return nil
} }

View File

@ -24,8 +24,9 @@ var (
ErrUpdate = errors.New("update err") ErrUpdate = errors.New("update err")
) )
func newTestStatement(seq, previousSeq uint64) Statement { func newTestStatement(aggType eventstore.AggregateType, seq, previousSeq uint64) *Statement {
return Statement{ return &Statement{
AggregateType: aggType,
Sequence: seq, Sequence: seq,
PreviousSequence: previousSeq, PreviousSequence: previousSeq,
} }
@ -39,7 +40,7 @@ func initTimer() *time.Timer {
func TestProjectionHandler_processEvent(t *testing.T) { func TestProjectionHandler_processEvent(t *testing.T) {
type fields struct { type fields struct {
stmts []Statement stmts []*Statement
pushSet bool pushSet bool
shouldPush *time.Timer shouldPush *time.Timer
} }
@ -50,7 +51,7 @@ func TestProjectionHandler_processEvent(t *testing.T) {
} }
type want struct { type want struct {
isErr func(err error) bool isErr func(err error) bool
stmts []Statement stmts []*Statement
} }
tests := []struct { tests := []struct {
name string name string
@ -76,41 +77,43 @@ func TestProjectionHandler_processEvent(t *testing.T) {
}, },
}, },
{ {
name: "no stmts", name: "single new stmt",
fields: fields{ fields: fields{
stmts: nil, stmts: nil,
pushSet: false, pushSet: false,
shouldPush: initTimer(), shouldPush: initTimer(),
}, },
args: args{ args: args{
reduce: testReduce(), reduce: testReduce(newTestStatement("aggregate1", 1, 0)),
}, },
want: want{ want: want{
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
}, },
stmts: nil, stmts: []*Statement{
newTestStatement("aggregate1", 1, 0),
},
}, },
}, },
{ {
name: "existing stmts", name: "existing stmts",
fields: fields{ fields: fields{
stmts: []Statement{ stmts: []*Statement{
newTestStatement(1, 0), newTestStatement("aggregate1", 1, 0),
}, },
pushSet: false, pushSet: false,
shouldPush: initTimer(), shouldPush: initTimer(),
}, },
args: args{ args: args{
reduce: testReduce(newTestStatement(2, 1)), reduce: testReduce(newTestStatement("aggregate1", 2, 1)),
}, },
want: want{ want: want{
isErr: func(err error) bool { isErr: func(err error) bool {
return err == nil return err == nil
}, },
stmts: []Statement{ stmts: []*Statement{
newTestStatement(1, 0), newTestStatement("aggregate1", 1, 0),
newTestStatement(2, 1), newTestStatement("aggregate1", 2, 1),
}, },
}, },
}, },
@ -163,7 +166,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
query: testQuery(nil, 0, ErrQuery), query: testQuery(nil, 0, ErrQuery),
reduce: testReduce(), reduce: testReduce(newTestStatement("aggregate1", 1, 0)),
}, },
fields: fields{}, fields: fields{},
want: want{ want: want{
@ -185,7 +188,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
5, 5,
nil, nil,
), ),
reduce: testReduce(), reduce: testReduce(newTestStatement("test", 1, 0)),
}, },
fields: fields{ fields: fields{
eventstore: eventstore.NewEventstore( eventstore: eventstore.NewEventstore(
@ -211,7 +214,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
5, 5,
nil, nil,
), ),
reduce: testReduce(), reduce: testReduce(newTestStatement("test", 1, 0)),
}, },
fields: fields{ fields: fields{
eventstore: eventstore.NewEventstore( eventstore: eventstore.NewEventstore(
@ -237,7 +240,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
5, 5,
nil, nil,
), ),
reduce: testReduce(), reduce: testReduce(newTestStatement("test", 1, 0)),
}, },
fields: fields{ fields: fields{
eventstore: eventstore.NewEventstore( eventstore: eventstore.NewEventstore(
@ -284,7 +287,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
2, 2,
nil, nil,
), ),
reduce: testReduce(), reduce: testReduce(newTestStatement("test", 1, 0)),
}, },
fields: fields{ fields: fields{
eventstore: eventstore.NewEventstore( eventstore: eventstore.NewEventstore(
@ -343,7 +346,7 @@ func TestProjectionHandler_fetchBulkStmts(t *testing.T) {
func TestProjectionHandler_push(t *testing.T) { func TestProjectionHandler_push(t *testing.T) {
type fields struct { type fields struct {
stmts []Statement stmts []*Statement
pushSet bool pushSet bool
} }
type args struct { type args struct {
@ -365,9 +368,9 @@ func TestProjectionHandler_push(t *testing.T) {
{ {
name: "previous lock", name: "previous lock",
fields: fields{ fields: fields{
stmts: []Statement{ stmts: []*Statement{
newTestStatement(1, 0), newTestStatement("aggregate1", 1, 0),
newTestStatement(2, 1), newTestStatement("aggregate1", 2, 1),
}, },
pushSet: true, pushSet: true,
}, },
@ -375,7 +378,7 @@ func TestProjectionHandler_push(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
previousLock: 200 * time.Millisecond, previousLock: 200 * time.Millisecond,
update: testUpdate(t, 2, nil), update: testUpdate(t, 2, nil),
reduce: testReduce(), reduce: testReduce(newTestStatement("aggregate1", 1, 0)),
}, },
want: want{ want: want{
isErr: func(err error) bool { return err == nil }, isErr: func(err error) bool { return err == nil },
@ -385,16 +388,16 @@ func TestProjectionHandler_push(t *testing.T) {
{ {
name: "error in update", name: "error in update",
fields: fields{ fields: fields{
stmts: []Statement{ stmts: []*Statement{
newTestStatement(1, 0), newTestStatement("aggregate1", 1, 0),
newTestStatement(2, 1), newTestStatement("aggregate1", 2, 1),
}, },
pushSet: true, pushSet: true,
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
update: testUpdate(t, 2, errors.New("some error")), update: testUpdate(t, 2, errors.New("some error")),
reduce: testReduce(), reduce: testReduce(newTestStatement("test", 1, 0)),
}, },
want: want{ want: want{
isErr: func(err error) bool { return err.Error() == "some error" }, isErr: func(err error) bool { return err.Error() == "some error" },
@ -668,7 +671,7 @@ func TestProjectionHandler_prepareExecuteBulk(t *testing.T) {
type fields struct { type fields struct {
Handler Handler Handler Handler
SequenceTable string SequenceTable string
stmts []Statement stmts []*Statement
pushSet bool pushSet bool
shouldPush *time.Timer shouldPush *time.Timer
} }
@ -754,7 +757,7 @@ func TestProjectionHandler_prepareExecuteBulk(t *testing.T) {
nil, nil,
), ),
reduce: testReduce( reduce: testReduce(
newTestStatement(2, 1), newTestStatement("aggregate1", 2, 1),
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
@ -797,7 +800,7 @@ func TestProjectionHandler_prepareExecuteBulk(t *testing.T) {
shouldPush: initTimer(), shouldPush: initTimer(),
}, },
args: args{ args: args{
update: testUpdate(t, 4, nil), update: testUpdate(t, 2, nil),
query: testQuery( query: testQuery(
eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery(). AddQuery().
@ -807,8 +810,7 @@ func TestProjectionHandler_prepareExecuteBulk(t *testing.T) {
nil, nil,
), ),
reduce: testReduce( reduce: testReduce(
newTestStatement(1, 0), newTestStatement("aggregate1", 0, 1),
newTestStatement(2, 1),
), ),
ctx: context.Background(), ctx: context.Background(),
}, },
@ -838,22 +840,22 @@ func TestProjectionHandler_prepareExecuteBulk(t *testing.T) {
} }
func testUpdate(t *testing.T, expectedStmtCount int, returnedErr error) Update { func testUpdate(t *testing.T, expectedStmtCount int, returnedErr error) Update {
return func(ctx context.Context, stmts []Statement, reduce Reduce) ([]Statement, error) { return func(ctx context.Context, stmts []*Statement, reduce Reduce) ([]*Statement, error) {
if expectedStmtCount != len(stmts) { if expectedStmtCount != len(stmts) {
t.Errorf("expected %d stmts got %d", expectedStmtCount, len(stmts)) t.Errorf("expected %d stmts got %d", expectedStmtCount, len(stmts))
} }
return []Statement{}, returnedErr return []*Statement{}, returnedErr
} }
} }
func testReduce(stmts ...Statement) Reduce { func testReduce(stmts *Statement) Reduce {
return func(event eventstore.EventReader) ([]Statement, error) { return func(event eventstore.EventReader) (*Statement, error) {
return stmts, nil return stmts, nil
} }
} }
func testReduceErr(err error) Reduce { func testReduceErr(err error) Reduce {
return func(event eventstore.EventReader) ([]Statement, error) { return func(event eventstore.EventReader) (*Statement, error) {
return nil, err return nil, err
} }
} }

View File

@ -65,29 +65,27 @@ const (
orgNameCol = "name" orgNameCol = "name"
) )
func (p *OrgProjection) reduceOrgAdded(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgProjection) reduceOrgAdded(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgAddedEvent) e, ok := event.(*org.OrgAddedEvent)
if !ok { if !ok {
logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", org.OrgAddedEventType).Error("was not an event") logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", org.OrgAddedEventType).Error("was not an event")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewCreateStatement(
crdb.NewCreateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(orgIDCol, e.Aggregate().ID),
handler.NewCol(orgIDCol, e.Aggregate().ID), handler.NewCol(orgCreationDateCol, e.CreationDate()),
handler.NewCol(orgCreationDateCol, e.CreationDate()), handler.NewCol(orgChangeDateCol, e.CreationDate()),
handler.NewCol(orgChangeDateCol, e.CreationDate()), handler.NewCol(orgResourceOwnerCol, e.Aggregate().ResourceOwner),
handler.NewCol(orgResourceOwnerCol, e.Aggregate().ResourceOwner), handler.NewCol(orgSequenceCol, e.Sequence()),
handler.NewCol(orgSequenceCol, e.Sequence()), handler.NewCol(orgNameCol, e.Name),
handler.NewCol(orgNameCol, e.Name), handler.NewCol(orgStateCol, domain.OrgStateActive),
handler.NewCol(orgStateCol, domain.OrgStateActive), },
}, ), nil
),
}, nil
} }
func (p *OrgProjection) reduceOrgChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgProjection) reduceOrgChanged(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgChangedEvent) e, ok := event.(*org.OrgChangedEvent)
if !ok { if !ok {
logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", org.OrgChangedEventType).Error("wrong event type") logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", org.OrgChangedEventType).Error("wrong event type")
@ -100,76 +98,68 @@ func (p *OrgProjection) reduceOrgChanged(event eventstore.EventReader) ([]handle
if e.Name != "" { if e.Name != "" {
values = append(values, handler.NewCol(orgNameCol, e.Name)) values = append(values, handler.NewCol(orgNameCol, e.Name))
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, values,
values, []handler.Condition{
[]handler.Condition{ handler.NewCond(orgIDCol, e.Aggregate().ID),
handler.NewCond(orgIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *OrgProjection) reduceOrgDeactivated(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgProjection) reduceOrgDeactivated(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgDeactivatedEvent) e, ok := event.(*org.OrgDeactivatedEvent)
if !ok { if !ok {
logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence, "expectedType", org.OrgDeactivatedEventType).Error("wrong event type") logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence, "expectedType", org.OrgDeactivatedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-BApK4", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-BApK4", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(orgChangeDateCol, e.CreationDate()),
handler.NewCol(orgChangeDateCol, e.CreationDate()), handler.NewCol(orgSequenceCol, e.Sequence()),
handler.NewCol(orgSequenceCol, e.Sequence()), handler.NewCol(orgStateCol, domain.OrgStateInactive),
handler.NewCol(orgStateCol, domain.OrgStateInactive), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(orgIDCol, e.Aggregate().ID),
handler.NewCond(orgIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *OrgProjection) reduceOrgReactivated(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgProjection) reduceOrgReactivated(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgReactivatedEvent) e, ok := event.(*org.OrgReactivatedEvent)
if !ok { if !ok {
logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence, "expectedType", org.OrgReactivatedEventType).Error("wrong event type") logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence, "expectedType", org.OrgReactivatedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-o37De", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-o37De", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(orgChangeDateCol, e.CreationDate()),
handler.NewCol(orgChangeDateCol, e.CreationDate()), handler.NewCol(orgSequenceCol, e.Sequence()),
handler.NewCol(orgSequenceCol, e.Sequence()), handler.NewCol(orgStateCol, domain.OrgStateActive),
handler.NewCol(orgStateCol, domain.OrgStateActive), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(orgIDCol, e.Aggregate().ID),
handler.NewCond(orgIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *OrgProjection) reducePrimaryDomainSet(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgProjection) reducePrimaryDomainSet(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.DomainPrimarySetEvent) e, ok := event.(*org.DomainPrimarySetEvent)
if !ok { if !ok {
logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", org.OrgDomainPrimarySetEventType).Error("wrong event type") logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", org.OrgDomainPrimarySetEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(orgChangeDateCol, e.CreationDate()),
handler.NewCol(orgChangeDateCol, e.CreationDate()), handler.NewCol(orgSequenceCol, e.Sequence()),
handler.NewCol(orgSequenceCol, e.Sequence()), handler.NewCol(orgDomainCol, e.Domain),
handler.NewCol(orgDomainCol, e.Domain), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(orgIDCol, e.Aggregate().ID),
handler.NewCond(orgIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }

View File

@ -110,7 +110,7 @@ func (p *OrgOwnerProjection) reducers() []handler.AggregateReducer {
} }
} }
func (p *OrgOwnerProjection) reduceMemberAdded(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceMemberAdded(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.MemberAddedEvent) e, ok := event.(*org.MemberAddedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-kL530", "seq", event.Sequence, "expected", org.MemberAddedEventType).Error("wrong event type") logging.LogWithFields("PROJE-kL530", "seq", event.Sequence, "expected", org.MemberAddedEventType).Error("wrong event type")
@ -118,7 +118,7 @@ func (p *OrgOwnerProjection) reduceMemberAdded(event eventstore.EventReader) ([]
} }
if !isOrgOwner(e.Roles) { if !isOrgOwner(e.Roles) {
return []handler.Statement{crdb.NewNoOpStatement(e)}, nil return crdb.NewNoOpStatement(e), nil
} }
stmt, err := p.addOwner(e, e.Aggregate().ResourceOwner, e.UserID) stmt, err := p.addOwner(e, e.Aggregate().ResourceOwner, e.UserID)
@ -126,10 +126,10 @@ func (p *OrgOwnerProjection) reduceMemberAdded(event eventstore.EventReader) ([]
return nil, err return nil, err
} }
return []handler.Statement{stmt}, nil return stmt, nil
} }
func (p *OrgOwnerProjection) reduceMemberChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceMemberChanged(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.MemberChangedEvent) e, ok := event.(*org.MemberChangedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-kL530", "seq", event.Sequence, "expected", org.MemberAddedEventType).Error("wrong event type") logging.LogWithFields("PROJE-kL530", "seq", event.Sequence, "expected", org.MemberAddedEventType).Error("wrong event type")
@ -137,7 +137,7 @@ func (p *OrgOwnerProjection) reduceMemberChanged(event eventstore.EventReader) (
} }
if !isOrgOwner(e.Roles) { if !isOrgOwner(e.Roles) {
return []handler.Statement{p.deleteOwner(e, e.Aggregate().ID, e.UserID)}, nil return p.deleteOwner(e, e.Aggregate().ID, e.UserID), nil
} }
stmt, err := p.addOwner(e, e.Aggregate().ResourceOwner, e.UserID) stmt, err := p.addOwner(e, e.Aggregate().ResourceOwner, e.UserID)
@ -145,41 +145,39 @@ func (p *OrgOwnerProjection) reduceMemberChanged(event eventstore.EventReader) (
return nil, err return nil, err
} }
return []handler.Statement{stmt}, nil return stmt, nil
} }
func (p *OrgOwnerProjection) reduceMemberRemoved(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceMemberRemoved(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.MemberRemovedEvent) e, ok := event.(*org.MemberRemovedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-boIbP", "seq", event.Sequence, "expected", org.MemberRemovedEventType).Error("wrong event type") logging.LogWithFields("PROJE-boIbP", "seq", event.Sequence, "expected", org.MemberRemovedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "PROJE-pk6TS", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "PROJE-pk6TS", "reduce.wrong.event.type")
} }
return []handler.Statement{p.deleteOwner(e, e.Aggregate().ID, e.UserID)}, nil return p.deleteOwner(e, e.Aggregate().ID, e.UserID), nil
} }
func (p *OrgOwnerProjection) reduceHumanEmailChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceHumanEmailChanged(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*user.HumanEmailChangedEvent) e, ok := event.(*user.HumanEmailChangedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-IHFwh", "seq", event.Sequence, "expected", user.HumanEmailChangedType).Error("wrong event type") logging.LogWithFields("PROJE-IHFwh", "seq", event.Sequence, "expected", user.HumanEmailChangedType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "PROJE-jMlwT", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "PROJE-jMlwT", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(userEmailCol, e.EmailAddress),
handler.NewCol(userEmailCol, e.EmailAddress), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(userIDCol, e.Aggregate().ID),
handler.NewCond(userIDCol, e.Aggregate().ID), },
}, crdb.WithTableSuffix(userTableSuffix),
crdb.WithTableSuffix(userTableSuffix), ), nil
),
}, nil
} }
func (p *OrgOwnerProjection) reduceHumanProfileChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceHumanProfileChanged(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*user.HumanProfileChangedEvent) e, ok := event.(*user.HumanProfileChangedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-WqgUS", "seq", event.Sequence, "expected", user.HumanProfileChangedType).Error("wrong event type") logging.LogWithFields("PROJE-WqgUS", "seq", event.Sequence, "expected", user.HumanProfileChangedType).Error("wrong event type")
@ -201,41 +199,37 @@ func (p *OrgOwnerProjection) reduceHumanProfileChanged(event eventstore.EventRea
} }
if len(values) == 0 { if len(values) == 0 {
return []handler.Statement{crdb.NewNoOpStatement(e)}, nil return crdb.NewNoOpStatement(e), nil
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, values,
values, []handler.Condition{
[]handler.Condition{ handler.NewCond(userIDCol, e.Aggregate().ID),
handler.NewCond(userIDCol, e.Aggregate().ID), },
}, crdb.WithTableSuffix(userTableSuffix),
crdb.WithTableSuffix(userTableSuffix), ), nil
),
}, nil
} }
func (p *OrgOwnerProjection) reduceOrgAdded(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceOrgAdded(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgAddedEvent) e, ok := event.(*org.OrgAddedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-wbOrL", "seq", event.Sequence, "expected", org.OrgAddedEventType).Error("wrong event type") logging.LogWithFields("PROJE-wbOrL", "seq", event.Sequence, "expected", org.OrgAddedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "PROJE-pk6TS", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "PROJE-pk6TS", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewCreateStatement(
crdb.NewCreateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(orgIDCol, e.Aggregate().ResourceOwner),
handler.NewCol(orgIDCol, e.Aggregate().ResourceOwner), handler.NewCol(orgNameCol, e.Name),
handler.NewCol(orgNameCol, e.Name), handler.NewCol(orgCreationDateCol, e.CreationDate()),
handler.NewCol(orgCreationDateCol, e.CreationDate()), },
}, crdb.WithTableSuffix(orgTableSuffix),
crdb.WithTableSuffix(orgTableSuffix), ), nil
),
}, nil
} }
func (p *OrgOwnerProjection) reduceOrgChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceOrgChanged(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgChangedEvent) e, ok := event.(*org.OrgChangedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-piy2b", "seq", event.Sequence, "expected", org.OrgChangedEventType).Error("wrong event type") logging.LogWithFields("PROJE-piy2b", "seq", event.Sequence, "expected", org.OrgChangedEventType).Error("wrong event type")
@ -248,46 +242,42 @@ func (p *OrgOwnerProjection) reduceOrgChanged(event eventstore.EventReader) ([]h
} }
if len(values) == 0 { if len(values) == 0 {
return []handler.Statement{crdb.NewNoOpStatement(e)}, nil return crdb.NewNoOpStatement(e), nil
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, values,
values, []handler.Condition{
[]handler.Condition{ handler.NewCond(orgIDCol, e.Aggregate().ResourceOwner),
handler.NewCond(orgIDCol, e.Aggregate().ResourceOwner), },
}, crdb.WithTableSuffix(orgTableSuffix),
crdb.WithTableSuffix(orgTableSuffix), ), nil
),
}, nil
} }
func (p *OrgOwnerProjection) reduceOrgRemoved(event eventstore.EventReader) ([]handler.Statement, error) { func (p *OrgOwnerProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*org.OrgChangedEvent) e, ok := event.(*org.OrgRemovedEvent)
if !ok { if !ok {
logging.LogWithFields("PROJE-F1mHQ", "seq", event.Sequence, "expected", org.OrgRemovedEventType).Error("wrong event type") logging.LogWithFields("PROJE-F1mHQ", "seq", event.Sequence, "expected", org.OrgRemovedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "PROJE-9ZR2w", "reduce.wrong.event.type") return nil, errors.ThrowInvalidArgument(nil, "PROJE-9ZR2w", "reduce.wrong.event.type")
} }
return []handler.Statement{ return crdb.NewMultiStatement(e,
//delete org in org table //delete org in org table
crdb.NewDeleteStatement( crdb.AddDeleteStatement(
e,
[]handler.Condition{ []handler.Condition{
handler.NewCond(orgIDCol, e.Aggregate().ResourceOwner), handler.NewCond(orgIDCol, e.Aggregate().ResourceOwner),
}, },
crdb.WithTableSuffix(orgTableSuffix), crdb.WithTableSuffix(orgTableSuffix),
), ),
// delete users of the org // delete users of the org
crdb.NewDeleteStatement( crdb.AddDeleteStatement(
e,
[]handler.Condition{ []handler.Condition{
handler.NewCond(userOrgIDCol, e.Aggregate().ResourceOwner), handler.NewCond(userOrgIDCol, e.Aggregate().ResourceOwner),
}, },
crdb.WithTableSuffix(userTableSuffix), crdb.WithTableSuffix(userTableSuffix),
), ),
}, nil ), nil
} }
func isOrgOwner(roles []string) bool { func isOrgOwner(roles []string) bool {
@ -299,7 +289,7 @@ func isOrgOwner(roles []string) bool {
return false return false
} }
func (p *OrgOwnerProjection) deleteOwner(event eventstore.EventReader, orgID, ownerID string) handler.Statement { func (p *OrgOwnerProjection) deleteOwner(event eventstore.EventReader, orgID, ownerID string) *handler.Statement {
return crdb.NewDeleteStatement( return crdb.NewDeleteStatement(
event, event,
[]handler.Condition{ []handler.Condition{
@ -310,7 +300,7 @@ func (p *OrgOwnerProjection) deleteOwner(event eventstore.EventReader, orgID, ow
) )
} }
func (p *OrgOwnerProjection) addOwner(event eventstore.EventReader, orgID, userID string) (handler.Statement, error) { func (p *OrgOwnerProjection) addOwner(event eventstore.EventReader, orgID, userID string) (*handler.Statement, error) {
events, err := p.Eventstore.FilterEvents(context.Background(), events, err := p.Eventstore.FilterEvents(context.Background(),
eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery(). AddQuery().
@ -330,12 +320,12 @@ func (p *OrgOwnerProjection) addOwner(event eventstore.EventReader, orgID, userI
SequenceLess(event.Sequence()). SequenceLess(event.Sequence()).
Builder()) Builder())
if err != nil { if err != nil {
return handler.Statement{}, err return nil, err
} }
if len(events) == 0 { if len(events) == 0 {
logging.LogWithFields("mqd3w", "user", userID, "org", orgID, "seq", event.Sequence()).Warn("no events for user found") logging.LogWithFields("mqd3w", "user", userID, "org", orgID, "seq", event.Sequence()).Warn("no events for user found")
return handler.Statement{}, errors.ThrowInternal(nil, "PROJE-Qk7Tv", "unable to find user events") return nil, errors.ThrowInternal(nil, "PROJE-Qk7Tv", "unable to find user events")
} }
owner := &OrgOwner{ owner := &OrgOwner{

View File

@ -66,89 +66,79 @@ const (
projectInactive projectInactive
) )
func (p *ProjectProjection) reduceProjectAdded(event eventstore.EventReader) ([]handler.Statement, error) { func (p *ProjectProjection) reduceProjectAdded(event eventstore.EventReader) (*handler.Statement, error) {
e := event.(*project.ProjectAddedEvent) e := event.(*project.ProjectAddedEvent)
return []handler.Statement{ return crdb.NewCreateStatement(
crdb.NewCreateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(projectIDCol, e.Aggregate().ID),
handler.NewCol(projectIDCol, e.Aggregate().ID), handler.NewCol(projectNameCol, e.Name),
handler.NewCol(projectNameCol, e.Name), handler.NewCol(projectCreationDateCol, e.CreationDate()),
handler.NewCol(projectCreationDateCol, e.CreationDate()), handler.NewCol(projectChangeDateCol, e.CreationDate()),
handler.NewCol(projectChangeDateCol, e.CreationDate()), handler.NewCol(projectOwnerCol, e.Aggregate().ResourceOwner),
handler.NewCol(projectOwnerCol, e.Aggregate().ResourceOwner), handler.NewCol(projectCreatorCol, e.EditorUser()),
handler.NewCol(projectCreatorCol, e.EditorUser()), handler.NewCol(projectStateCol, projectActive),
handler.NewCol(projectStateCol, projectActive), },
}, ), nil
),
}, nil
} }
func (p *ProjectProjection) reduceProjectChanged(event eventstore.EventReader) ([]handler.Statement, error) { func (p *ProjectProjection) reduceProjectChanged(event eventstore.EventReader) (*handler.Statement, error) {
e := event.(*project.ProjectChangeEvent) e := event.(*project.ProjectChangeEvent)
if e.Name == nil { if e.Name == nil {
return []handler.Statement{crdb.NewNoOpStatement(e)}, nil return crdb.NewNoOpStatement(e), nil
} }
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(projectNameCol, e.Name),
handler.NewCol(projectNameCol, e.Name), handler.NewCol(projectChangeDateCol, e.CreationDate()),
handler.NewCol(projectChangeDateCol, e.CreationDate()), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(projectIDCol, e.Aggregate().ID),
handler.NewCond(projectIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *ProjectProjection) reduceProjectDeactivated(event eventstore.EventReader) ([]handler.Statement, error) { func (p *ProjectProjection) reduceProjectDeactivated(event eventstore.EventReader) (*handler.Statement, error) {
e := event.(*project.ProjectDeactivatedEvent) e := event.(*project.ProjectDeactivatedEvent)
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(projectStateCol, projectInactive),
handler.NewCol(projectStateCol, projectInactive), handler.NewCol(projectChangeDateCol, e.CreationDate()),
handler.NewCol(projectChangeDateCol, e.CreationDate()), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(projectIDCol, e.Aggregate().ID),
handler.NewCond(projectIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *ProjectProjection) reduceProjectReactivated(event eventstore.EventReader) ([]handler.Statement, error) { func (p *ProjectProjection) reduceProjectReactivated(event eventstore.EventReader) (*handler.Statement, error) {
e := event.(*project.ProjectReactivatedEvent) e := event.(*project.ProjectReactivatedEvent)
return []handler.Statement{ return crdb.NewUpdateStatement(
crdb.NewUpdateStatement( e,
e, []handler.Column{
[]handler.Column{ handler.NewCol(projectStateCol, projectActive),
handler.NewCol(projectStateCol, projectActive), handler.NewCol(projectChangeDateCol, e.CreationDate()),
handler.NewCol(projectChangeDateCol, e.CreationDate()), },
}, []handler.Condition{
[]handler.Condition{ handler.NewCond(projectIDCol, e.Aggregate().ID),
handler.NewCond(projectIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }
func (p *ProjectProjection) reduceProjectRemoved(event eventstore.EventReader) ([]handler.Statement, error) { func (p *ProjectProjection) reduceProjectRemoved(event eventstore.EventReader) (*handler.Statement, error) {
e := event.(*project.ProjectRemovedEvent) e := event.(*project.ProjectRemovedEvent)
return []handler.Statement{ return crdb.NewDeleteStatement(
crdb.NewDeleteStatement( e,
e, []handler.Condition{
[]handler.Condition{ handler.NewCond(projectIDCol, e.Aggregate().ID),
handler.NewCond(projectIDCol, e.Aggregate().ID), },
}, ), nil
),
}, nil
} }