2020-10-05 17:09:26 +00:00
|
|
|
package sql
|
|
|
|
|
|
|
|
import (
|
2020-10-21 17:00:41 +00:00
|
|
|
"context"
|
2020-10-05 17:09:26 +00:00
|
|
|
"database/sql"
|
2020-11-23 18:31:12 +00:00
|
|
|
"encoding/json"
|
2020-10-05 17:09:26 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/caos/logging"
|
2020-10-21 17:00:41 +00:00
|
|
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
2020-10-05 17:09:26 +00:00
|
|
|
z_errors "github.com/caos/zitadel/internal/errors"
|
|
|
|
"github.com/caos/zitadel/internal/eventstore/v2/repository"
|
|
|
|
"github.com/lib/pq"
|
|
|
|
)
|
|
|
|
|
2020-10-06 19:28:09 +00:00
|
|
|
type querier interface {
|
2020-10-05 17:09:26 +00:00
|
|
|
columnName(repository.Field) string
|
|
|
|
operation(repository.Operation) string
|
|
|
|
conditionFormat(repository.Operation) string
|
|
|
|
placeholder(query string) string
|
|
|
|
eventQuery() string
|
|
|
|
maxSequenceQuery() string
|
2020-10-21 17:00:41 +00:00
|
|
|
db() *sql.DB
|
|
|
|
orderByEventSequence(desc bool) string
|
2020-10-05 17:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type rowScan func(scan, interface{}) error
|
|
|
|
type scan func(dest ...interface{}) error
|
|
|
|
|
2020-10-21 17:00:41 +00:00
|
|
|
func query(ctx context.Context, criteria querier, searchQuery *repository.SearchQuery, dest interface{}) error {
|
|
|
|
query, rowScanner := prepareColumns(criteria, searchQuery.Columns)
|
2020-10-05 17:09:26 +00:00
|
|
|
where, values := prepareCondition(criteria, searchQuery.Filters)
|
|
|
|
if where == "" || query == "" {
|
2020-10-21 17:00:41 +00:00
|
|
|
return caos_errs.ThrowInvalidArgument(nil, "SQL-rWeBw", "invalid query factory")
|
2020-10-05 17:09:26 +00:00
|
|
|
}
|
|
|
|
query += where
|
|
|
|
|
2020-10-06 19:28:09 +00:00
|
|
|
if searchQuery.Columns != repository.ColumnsMaxSequence {
|
2020-10-21 17:00:41 +00:00
|
|
|
query += criteria.orderByEventSequence(searchQuery.Desc)
|
2020-10-05 17:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if searchQuery.Limit > 0 {
|
|
|
|
values = append(values, searchQuery.Limit)
|
|
|
|
query += " LIMIT ?"
|
|
|
|
}
|
|
|
|
|
|
|
|
query = criteria.placeholder(query)
|
|
|
|
|
2020-10-21 17:00:41 +00:00
|
|
|
rows, err := criteria.db().QueryContext(ctx, query, values...)
|
|
|
|
if err != nil {
|
|
|
|
logging.Log("SQL-HP3Uk").WithError(err).Info("query failed")
|
|
|
|
return caos_errs.ThrowInternal(err, "SQL-IJuyR", "unable to filter events")
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
err = rowScanner(rows.Scan, dest)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-10-05 17:09:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 19:28:09 +00:00
|
|
|
func prepareColumns(criteria querier, columns repository.Columns) (string, func(s scan, dest interface{}) error) {
|
2020-10-05 17:09:26 +00:00
|
|
|
switch columns {
|
2020-10-06 19:28:09 +00:00
|
|
|
case repository.ColumnsMaxSequence:
|
2020-10-05 18:39:36 +00:00
|
|
|
return criteria.maxSequenceQuery(), maxSequenceScanner
|
2020-10-06 19:28:09 +00:00
|
|
|
case repository.ColumnsEvent:
|
2020-10-05 18:39:36 +00:00
|
|
|
return criteria.eventQuery(), eventsScanner
|
2020-10-05 17:09:26 +00:00
|
|
|
default:
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-05 18:39:36 +00:00
|
|
|
func maxSequenceScanner(row scan, dest interface{}) (err error) {
|
2020-10-05 17:09:26 +00:00
|
|
|
sequence, ok := dest.(*Sequence)
|
|
|
|
if !ok {
|
|
|
|
return z_errors.ThrowInvalidArgument(nil, "SQL-NBjA9", "type must be sequence")
|
|
|
|
}
|
|
|
|
err = row(sequence)
|
|
|
|
if err == nil || errors.Is(err, sql.ErrNoRows) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return z_errors.ThrowInternal(err, "SQL-bN5xg", "something went wrong")
|
|
|
|
}
|
|
|
|
|
2020-10-05 18:39:36 +00:00
|
|
|
func eventsScanner(scanner scan, dest interface{}) (err error) {
|
|
|
|
events, ok := dest.(*[]*repository.Event)
|
2020-10-05 17:09:26 +00:00
|
|
|
if !ok {
|
|
|
|
return z_errors.ThrowInvalidArgument(nil, "SQL-4GP6F", "type must be event")
|
|
|
|
}
|
|
|
|
var previousSequence Sequence
|
|
|
|
data := make(Data, 0)
|
2020-10-05 18:39:36 +00:00
|
|
|
event := new(repository.Event)
|
2020-10-05 17:09:26 +00:00
|
|
|
|
2020-10-05 18:39:36 +00:00
|
|
|
err = scanner(
|
2020-10-05 17:09:26 +00:00
|
|
|
&event.CreationDate,
|
|
|
|
&event.Type,
|
|
|
|
&event.Sequence,
|
|
|
|
&previousSequence,
|
|
|
|
&data,
|
|
|
|
&event.EditorService,
|
|
|
|
&event.EditorUser,
|
|
|
|
&event.ResourceOwner,
|
|
|
|
&event.AggregateType,
|
|
|
|
&event.AggregateID,
|
|
|
|
&event.Version,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
logging.Log("SQL-kn1Sw").WithError(err).Warn("unable to scan row")
|
|
|
|
return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row")
|
|
|
|
}
|
|
|
|
|
|
|
|
event.PreviousSequence = uint64(previousSequence)
|
|
|
|
|
|
|
|
event.Data = make([]byte, len(data))
|
|
|
|
copy(event.Data, data)
|
|
|
|
|
2020-10-05 18:39:36 +00:00
|
|
|
*events = append(*events, event)
|
|
|
|
|
2020-10-05 17:09:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-06 19:28:09 +00:00
|
|
|
func prepareCondition(criteria querier, filters []*repository.Filter) (clause string, values []interface{}) {
|
2020-10-05 17:09:26 +00:00
|
|
|
values = make([]interface{}, len(filters))
|
|
|
|
clauses := make([]string, len(filters))
|
|
|
|
|
|
|
|
if len(filters) == 0 {
|
|
|
|
return clause, values
|
|
|
|
}
|
|
|
|
for i, filter := range filters {
|
2020-10-05 18:39:36 +00:00
|
|
|
value := filter.Value
|
2020-10-05 17:09:26 +00:00
|
|
|
switch value.(type) {
|
|
|
|
case []bool, []float64, []int64, []string, []repository.AggregateType, []repository.EventType, *[]bool, *[]float64, *[]int64, *[]string, *[]repository.AggregateType, *[]repository.EventType:
|
|
|
|
value = pq.Array(value)
|
2020-11-23 18:31:12 +00:00
|
|
|
case map[string]interface{}:
|
|
|
|
var err error
|
|
|
|
value, err = json.Marshal(value)
|
|
|
|
logging.Log("SQL-BSsNy").OnError(err).Warn("unable to marshal search value")
|
2020-10-05 17:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
clauses[i] = getCondition(criteria, filter)
|
|
|
|
if clauses[i] == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
values[i] = value
|
|
|
|
}
|
|
|
|
return " WHERE " + strings.Join(clauses, " AND "), values
|
|
|
|
}
|
|
|
|
|
2020-10-06 19:28:09 +00:00
|
|
|
func getCondition(cond querier, filter *repository.Filter) (condition string) {
|
2020-10-05 18:39:36 +00:00
|
|
|
field := cond.columnName(filter.Field)
|
|
|
|
operation := cond.operation(filter.Operation)
|
2020-10-05 17:09:26 +00:00
|
|
|
if field == "" || operation == "" {
|
|
|
|
return ""
|
|
|
|
}
|
2020-10-05 18:39:36 +00:00
|
|
|
format := cond.conditionFormat(filter.Operation)
|
2020-10-05 17:09:26 +00:00
|
|
|
|
|
|
|
return fmt.Sprintf(format, field, operation)
|
|
|
|
}
|