feat: administrator (#271)

* feat: get views and failed events

* feat: get views and failed events

* feat: get views and failed events

* Update internal/view/repository/sequence.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* Update internal/view/repository/general_query.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-06-25 08:01:13 +02:00
committed by GitHub
parent b88f200434
commit 8bfa1a083c
90 changed files with 3555 additions and 1661 deletions

View File

@@ -0,0 +1,25 @@
package repository
import (
"database/sql"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/errors"
"github.com/jinzhu/gorm"
)
type ViewConfig struct {
SQL *types.SQL
}
func Start(conf ViewConfig) (*sql.DB, *gorm.DB, error) {
sqlClient, err := sql.Open("postgres", conf.SQL.ConnectionString())
if err != nil {
return nil, nil, errors.ThrowPreconditionFailed(err, "SQL-9qBtr", "unable to open database connection")
}
client, err := gorm.Open("postgres", sqlClient)
if err != nil {
return nil, nil, err
}
return sqlClient, client, nil
}

View File

@@ -0,0 +1,421 @@
package repository
import (
"database/sql/driver"
"fmt"
"strconv"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/model"
)
var (
expectedGetByID = `SELECT \* FROM "%s" WHERE \(%s = \$1\) LIMIT 1`
expectedGetByQuery = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) LIMIT 1`
expectedGetByQueryCaseSensitive = `SELECT \* FROM "%s" WHERE \(%s %s \$1\) LIMIT 1`
expectedSave = `UPDATE "%s" SET "test" = \$1 WHERE "%s"."%s" = \$2`
expectedRemove = `DELETE FROM "%s" WHERE \(%s = \$1\)`
expectedRemoveByKeys = func(i int, table string) string {
sql := fmt.Sprintf(`DELETE FROM "%s"`, table)
sql += ` WHERE \(%s = \$1\)`
for j := 1; j < i; j++ {
sql = sql + ` AND \(%s = \$` + strconv.Itoa(j+1) + `\)`
}
return sql
}
expectedRemoveByObject = `DELETE FROM "%s" WHERE "%s"."%s" = \$1`
expectedRemoveByObjectMultiplePK = `DELETE FROM "%s" WHERE "%s"."%s" = \$1 AND "%s"."%s" = \$2`
expectedTruncate = `TRUNCATE %s;`
expectedSearch = `SELECT \* FROM "%s" OFFSET 0`
expectedSearchCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchLimit = `SELECT \* FROM "%s" LIMIT %v OFFSET 0`
expectedSearchLimitCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchOffset = `SELECT \* FROM "%s" OFFSET %v`
expectedSearchOffsetCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchSorting = `SELECT \* FROM "%s" ORDER BY %s %s OFFSET 0`
expectedSearchSortingCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchQuery = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) OFFSET 0`
expectedSearchQueryCount = `SELECT count\(\*\) FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\)`
expectedSearchQueryAllParams = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) ORDER BY %s %s LIMIT %v OFFSET %v`
expectedSearchQueryAllParamCount = `SELECT count\(\*\) FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\)`
)
type TestSearchRequest struct {
limit uint64
offset uint64
sortingColumn ColumnKey
asc bool
queries []SearchQuery
}
func (req TestSearchRequest) GetLimit() uint64 {
return req.limit
}
func (req TestSearchRequest) GetOffset() uint64 {
return req.offset
}
func (req TestSearchRequest) GetSortingColumn() ColumnKey {
return req.sortingColumn
}
func (req TestSearchRequest) GetAsc() bool {
return req.asc
}
func (req TestSearchRequest) GetQueries() []SearchQuery {
return req.queries
}
type TestSearchQuery struct {
key TestSearchKey
method model.SearchMethod
value string
}
func (req TestSearchQuery) GetKey() ColumnKey {
return req.key
}
func (req TestSearchQuery) GetMethod() model.SearchMethod {
return req.method
}
func (req TestSearchQuery) GetValue() interface{} {
return req.value
}
type TestSearchKey int32
const (
TestSearchKey_UNDEFINED TestSearchKey = iota
TestSearchKey_TEST
TestSearchKey_ID
)
func (key TestSearchKey) ToColumnName() string {
switch TestSearchKey(key) {
case TestSearchKey_TEST:
return "test"
case TestSearchKey_ID:
return "id"
default:
return ""
}
}
type Test struct {
ID string `json:"-" gorm:"column:primary_id;primary_key"`
Test string `json:"test" gorm:"column:test"`
}
type TestMultiplePK struct {
TestID string `gorm:"column:testId;primary_key"`
HodorID string `gorm:"column:hodorId;primary_key"`
Test string `gorm:"column:test"`
}
type dbMock struct {
db *gorm.DB
mock sqlmock.Sqlmock
}
func (db *dbMock) close() {
db.db.Close()
}
func mockDB(t *testing.T) *dbMock {
mockDB := dbMock{}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("error occured while creating stub db %v", err)
}
mockDB.mock = mock
mockDB.db, err = gorm.Open("postgres", db)
if err != nil {
t.Fatalf("error occured while connecting to stub db: %v", err)
}
mockDB.mock.MatchExpectationsInOrder(true)
return &mockDB
}
func (db *dbMock) expectBegin(err error) *dbMock {
if err != nil {
db.mock.ExpectBegin().WillReturnError(err)
} else {
db.mock.ExpectBegin()
}
return db
}
func (db *dbMock) expectCommit(err error) *dbMock {
if err != nil {
db.mock.ExpectCommit().WillReturnError(err)
} else {
db.mock.ExpectCommit()
}
return db
}
func (db *dbMock) expectRollback(err error) *dbMock {
if err != nil {
db.mock.ExpectRollback().WillReturnError(err)
} else {
db.mock.ExpectRollback()
}
return db
}
func (db *dbMock) expectGetByID(table, key, value string) *dbMock {
query := fmt.Sprintf(expectedGetByID, table, key)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(sqlmock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByIDErr(table, key, value string, err error) *dbMock {
query := fmt.Sprintf(expectedGetByID, table, key)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectGetByQuery(table, key, method, value string) *dbMock {
query := fmt.Sprintf(expectedGetByQuery, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(sqlmock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByQueryCaseSensitive(table, key, method, value string) *dbMock {
query := fmt.Sprintf(expectedGetByQueryCaseSensitive, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(sqlmock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByQueryErr(table, key, method, value string, err error) *dbMock {
query := fmt.Sprintf(expectedGetByQuery, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectSave(table string, object Test) *dbMock {
query := fmt.Sprintf(expectedSave, table, table, "primary_id")
db.mock.ExpectExec(query).
WithArgs(object.Test, object.ID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectSaveErr(table string, object Test, err error) *dbMock {
query := fmt.Sprintf(expectedSave, table, table, "id")
db.mock.ExpectExec(query).
WithArgs(object.Test, object.ID).
WillReturnError(err)
return db
}
func (db *dbMock) expectRemove(table, key, value string) *dbMock {
query := fmt.Sprintf(expectedRemove, table, key)
db.mock.ExpectExec(query).
WithArgs(value).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveKeys(table string, keys ...Key) *dbMock {
keynames := make([]interface{}, len(keys))
keyvalues := make([]driver.Value, len(keys))
for i, key := range keys {
keynames[i] = key.Key.ToColumnName()
keyvalues[i] = key.Value
}
query := fmt.Sprintf(expectedRemoveByKeys(len(keys), table), keynames...)
db.mock.ExpectExec(query).
WithArgs(keyvalues...).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveByObject(table string, object Test) *dbMock {
query := fmt.Sprintf(expectedRemoveByObject, table, table, "primary_id")
db.mock.ExpectExec(query).
WithArgs(object.ID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveByObjectMultiplePKs(table string, object TestMultiplePK) *dbMock {
query := fmt.Sprintf(expectedRemoveByObjectMultiplePK, table, table, "testId", table, "hodorId")
db.mock.ExpectExec(query).
WithArgs(object.TestID, object.HodorID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveErr(table, key, value string, err error) *dbMock {
query := fmt.Sprintf(expectedRemove, table, key)
db.mock.ExpectExec(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectTruncate(table string) *dbMock {
query := fmt.Sprintf(expectedTruncate, table)
db.mock.ExpectExec(query).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectTruncateErr(table string, err error) *dbMock {
query := fmt.Sprintf(expectedTruncate, table)
db.mock.ExpectExec(query).
WillReturnError(err)
return db
}
func (db *dbMock) expectGetSearchRequestNoParams(table string, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearch, table)
queryCount := fmt.Sprintf(expectedSearchCount, table)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithLimit(table string, limit, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchLimit, table, limit)
queryCount := fmt.Sprintf(expectedSearchLimitCount, table)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithOffset(table string, offset, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchOffset, table, offset)
queryCount := fmt.Sprintf(expectedSearchOffsetCount, table)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithSorting(table, sorting string, sortingColumn ColumnKey, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchSorting, table, sortingColumn.ToColumnName(), sorting)
queryCount := fmt.Sprintf(expectedSearchSortingCount, table)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithSearchQuery(table, key, method, value string, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchQuery, table, key, method)
queryCount := fmt.Sprintf(expectedSearchQueryCount, table, key, method)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WithArgs(value).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithAllParams(table, key, method, value, sorting string, sortingColumn ColumnKey, limit, offset, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchQueryAllParams, table, key, method, sortingColumn.ToColumnName(), sorting, limit, offset)
queryCount := fmt.Sprintf(expectedSearchQueryAllParamCount, table, key, method)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WithArgs(value).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestErr(table string, resultAmount, total int, err error) *dbMock {
query := fmt.Sprintf(expectedSearch, table)
queryCount := fmt.Sprintf(expectedSearchCount, table)
rows := sqlmock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnError(err)
return db
}

View File

@@ -0,0 +1,131 @@
package repository
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/model"
view_model "github.com/caos/zitadel/internal/view/model"
"github.com/jinzhu/gorm"
"strings"
)
const (
errViewNameKey = "view_name"
errFailedSeqKey = "failed_sequence"
)
type FailedEvent struct {
ViewName string `gorm:"column:view_name;primary_key"`
FailedSequence uint64 `gorm:"column:failed_sequence;primary_key"`
FailureCount uint64 `gorm:"column:failure_count"`
ErrMsg string `gorm:"column:err_msg"`
}
type FailedEventSearchQuery struct {
Key FailedEventSearchKey
Method model.SearchMethod
Value interface{}
}
func (req FailedEventSearchQuery) GetKey() ColumnKey {
return failedEventSearchKey(req.Key)
}
func (req FailedEventSearchQuery) GetMethod() model.SearchMethod {
return req.Method
}
func (req FailedEventSearchQuery) GetValue() interface{} {
return req.Value
}
type FailedEventSearchKey int32
const (
FailedEventKeyUndefined FailedEventSearchKey = iota
FailedEventKeyViewName
FailedEventKeyFailedSequence
)
type failedEventSearchKey FailedEventSearchKey
func (key failedEventSearchKey) ToColumnName() string {
switch FailedEventSearchKey(key) {
case FailedEventKeyViewName:
return "view_name"
case FailedEventKeyFailedSequence:
return "failed_sequence"
default:
return ""
}
}
func FailedEventFromModel(failedEvent *view_model.FailedEvent) *FailedEvent {
return &FailedEvent{
ViewName: failedEvent.Database + "." + failedEvent.ViewName,
FailureCount: failedEvent.FailureCount,
FailedSequence: failedEvent.FailedSequence,
ErrMsg: failedEvent.ErrMsg,
}
}
func FailedEventToModel(failedEvent *FailedEvent) *view_model.FailedEvent {
dbView := strings.Split(failedEvent.ViewName, ".")
return &view_model.FailedEvent{
Database: dbView[0],
ViewName: dbView[1],
FailureCount: failedEvent.FailureCount,
FailedSequence: failedEvent.FailedSequence,
ErrMsg: failedEvent.ErrMsg,
}
}
func SaveFailedEvent(db *gorm.DB, table string, failedEvent *FailedEvent) error {
save := PrepareSave(table)
err := save(db, failedEvent)
if err != nil {
return errors.ThrowInternal(err, "VIEW-5kOhP", "unable to updated failed events")
}
return nil
}
func RemoveFailedEvent(db *gorm.DB, table string, failedEvent *FailedEvent) error {
delete := PrepareDeleteByKeys(table,
Key{Key: failedEventSearchKey(FailedEventKeyViewName), Value: failedEvent.ViewName},
Key{Key: failedEventSearchKey(FailedEventKeyFailedSequence), Value: failedEvent.FailedSequence},
)
return delete(db)
}
func LatestFailedEvent(db *gorm.DB, table, viewName string, sequence uint64) (*FailedEvent, error) {
failedEvent := new(FailedEvent)
queries := []SearchQuery{
FailedEventSearchQuery{Key: FailedEventKeyViewName, Method: model.SearchMethodEqualsIgnoreCase, Value: viewName},
FailedEventSearchQuery{Key: FailedEventKeyFailedSequence, Method: model.SearchMethodEquals, Value: sequence},
}
query := PrepareGetByQuery(table, queries...)
err := query(db, failedEvent)
if err == nil && failedEvent.ViewName != "" {
return failedEvent, nil
}
if errors.IsNotFound(err) {
return &FailedEvent{
ViewName: viewName,
FailedSequence: sequence,
FailureCount: 0,
}, nil
}
return nil, errors.ThrowInternalf(err, "VIEW-9LyCB", "unable to get failed events of %s", viewName)
}
func AllFailedEvents(db *gorm.DB, table string) ([]*FailedEvent, error) {
failedEvents := make([]*FailedEvent, 0)
query := PrepareSearchQuery(table, GeneralSearchRequest{})
_, err := query(db, &failedEvents)
if err != nil {
return nil, err
}
return failedEvents, nil
}

View File

@@ -0,0 +1,53 @@
package repository
import (
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/model"
)
type GeneralSearchRequest model.GeneralSearchRequest
type GeneralSearchQuery model.GeneralSearchQuery
type GeneralSearchKey model.GeneralSearchKey
func (req GeneralSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req GeneralSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req GeneralSearchRequest) GetSortingColumn() ColumnKey {
if req.SortingColumn == model.GeneralSearchKeyUnspecified {
return nil
}
return GeneralSearchKey(req.SortingColumn)
}
func (req GeneralSearchRequest) GetAsc() bool {
return req.Asc
}
func (req GeneralSearchRequest) GetQueries() []SearchQuery {
result := make([]SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = GeneralSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req GeneralSearchQuery) GetKey() ColumnKey {
return GeneralSearchKey(req.Key)
}
func (req GeneralSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req GeneralSearchQuery) GetValue() interface{} {
return req.Value
}
func (key GeneralSearchKey) ToColumnName() string {
return ""
}

View File

@@ -0,0 +1,118 @@
package repository
import (
"fmt"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/model"
"github.com/jinzhu/gorm"
"github.com/lib/pq"
)
type SearchRequest interface {
GetLimit() uint64
GetOffset() uint64
GetSortingColumn() ColumnKey
GetAsc() bool
GetQueries() []SearchQuery
}
type SearchQuery interface {
GetKey() ColumnKey
GetMethod() model.SearchMethod
GetValue() interface{}
}
type ColumnKey interface {
ToColumnName() string
}
func PrepareSearchQuery(table string, request SearchRequest) func(db *gorm.DB, res interface{}) (int, error) {
return func(db *gorm.DB, res interface{}) (int, error) {
count := 0
query := db.Table(table)
if column := request.GetSortingColumn(); column != nil {
order := "DESC"
if request.GetAsc() {
order = "ASC"
}
query = query.Order(fmt.Sprintf("%s %s", column.ToColumnName(), order))
}
for _, q := range request.GetQueries() {
var err error
query, err = SetQuery(query, q.GetKey(), q.GetValue(), q.GetMethod())
if err != nil {
return count, caos_errs.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
}
}
query = query.Count(&count)
if request.GetLimit() != 0 {
query = query.Limit(request.GetLimit())
}
query = query.Offset(request.GetOffset())
err := query.Find(res).Error
if err != nil {
return count, caos_errs.ThrowInternal(err, "VIEW-muSDK", "unable to find result")
}
return count, nil
}
}
func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.SearchMethod) (*gorm.DB, error) {
column := key.ToColumnName()
if column == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-7dz3w", "Column name missing")
}
switch method {
case model.SearchMethodEquals:
query = query.Where(""+column+" = ?", value)
case model.SearchMethodEqualsIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Equal ignore case only possible for strings")
}
query = query.Where("LOWER("+column+") = LOWER(?)", valueText)
case model.SearchMethodStartsWith:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Starts with only possible for strings")
}
query = query.Where(column+" LIKE ?", valueText+"%")
case model.SearchMethodStartsWithIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with ignore case only possible for strings")
}
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
case model.SearchMethodContains:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-3ids", "Contains with only possible for strings")
}
query = query.Where(column+" LIKE ?", "%"+valueText+"%")
case model.SearchMethodContainsIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with ignore case only possible for strings")
}
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText+"%")
case model.SearchMethodNotEquals:
query = query.Where(""+column+" <> ?", value)
case model.SearchMethodGreaterThan:
query = query.Where(column+" > ?", value)
case model.SearchMethodLessThan:
query = query.Where(column+" < ?", value)
case model.SearchMethodIsOneOf:
query = query.Where(column+" IN (?)", value)
case model.SearchMethodListContains:
valueText, ok := value.(string)
if !ok {
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-Psois", "list contains only possible for strings")
}
query = query.Where("? <@ "+column, pq.Array([]string{valueText}))
default:
return nil, nil
}
return query, nil
}

View File

@@ -0,0 +1,156 @@
package repository
import (
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/model"
"github.com/jinzhu/gorm"
"testing"
)
func TestPrepareSearchQuery(t *testing.T) {
type args struct {
table string
searchRequest SearchRequest
}
type res struct {
count int
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"search with no params",
mockDB(t).
expectGetSearchRequestNoParams("TESTTABLE", 1, 1),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{},
},
res{
count: 1,
wantErr: false,
},
},
{
"search with limit",
mockDB(t).
expectGetSearchRequestWithLimit("TESTTABLE", 2, 2, 5),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{limit: 2},
},
res{
count: 5,
wantErr: false,
},
},
{
"search with offset",
mockDB(t).
expectGetSearchRequestWithOffset("TESTTABLE", 2, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{offset: 2},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with sorting asc",
mockDB(t).
expectGetSearchRequestWithSorting("TESTTABLE", "ASC", TestSearchKey_ID, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{sortingColumn: TestSearchKey_ID, asc: true},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with sorting asc",
mockDB(t).
expectGetSearchRequestWithSorting("TESTTABLE", "DESC", TestSearchKey_ID, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{sortingColumn: TestSearchKey_ID},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with search query",
mockDB(t).
expectGetSearchRequestWithSearchQuery("TESTTABLE", TestSearchKey_ID.ToColumnName(), "=", "AggregateID", 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{queries: []SearchQuery{TestSearchQuery{key: TestSearchKey_ID, method: model.SearchMethodEqualsIgnoreCase, value: "AggregateID"}}},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with all params",
mockDB(t).
expectGetSearchRequestWithAllParams("TESTTABLE", TestSearchKey_ID.ToColumnName(), "=", "AggregateID", "ASC", TestSearchKey_ID, 2, 2, 2, 5),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{limit: 2, offset: 2, sortingColumn: TestSearchKey_ID, asc: true, queries: []SearchQuery{TestSearchQuery{key: TestSearchKey_ID, method: model.SearchMethodEqualsIgnoreCase, value: "AggregateID"}}},
},
res{
count: 5,
wantErr: false,
},
},
{
"search db error",
mockDB(t).
expectGetSearchRequestErr("TESTTABLE", 1, 1, gorm.ErrUnaddressable),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{},
},
res{
count: 1,
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &Test{}
getQuery := PrepareSearchQuery(tt.args.table, tt.args.searchRequest)
count, err := getQuery(tt.db.db, res)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if !tt.res.wantErr && count != tt.res.count {
t.Errorf("got wrong count: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}

View File

@@ -0,0 +1,119 @@
package repository
import (
"errors"
"fmt"
"github.com/caos/logging"
"github.com/jinzhu/gorm"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func PrepareGetByKey(table string, key ColumnKey, id string) func(db *gorm.DB, res interface{}) error {
return func(db *gorm.DB, res interface{}) error {
err := db.Table(table).
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).
Take(res).
Error
if err == nil {
return nil
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return caos_errs.ThrowNotFound(err, "VIEW-XRI9c", "object not found")
}
logging.LogWithFields("VIEW-xVShS", "AggregateID", id).WithError(err).Warn("get from view error")
return caos_errs.ThrowInternal(err, "VIEW-J92Td", "Errors.Internal")
}
}
func PrepareGetByQuery(table string, queries ...SearchQuery) func(db *gorm.DB, res interface{}) error {
return func(db *gorm.DB, res interface{}) error {
query := db.Table(table)
for _, q := range queries {
var err error
query, err = SetQuery(query, q.GetKey(), q.GetValue(), q.GetMethod())
if err != nil {
return caos_errs.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
}
}
err := query.Take(res).Error
if err == nil {
return nil
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return caos_errs.ThrowNotFound(err, "VIEW-hodc6", "object not found")
}
logging.LogWithFields("VIEW-Mg6la", "table ", table).WithError(err).Warn("get from cache error")
return caos_errs.ThrowInternal(err, "VIEW-qJBg9", "cache error")
}
}
func PrepareSave(table string) func(db *gorm.DB, object interface{}) error {
return func(db *gorm.DB, object interface{}) error {
err := db.Table(table).Save(object).Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-AfC7G", "unable to put object to view")
}
return nil
}
}
func PrepareDeleteByKey(table string, key ColumnKey, id interface{}) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
err := db.Table(table).
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).
Delete(nil).
Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-die73", "could not delete object")
}
return nil
}
}
type Key struct {
Key ColumnKey
Value interface{}
}
func PrepareDeleteByKeys(table string, keys ...Key) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
for _, key := range keys {
db = db.Table(table).
Where(fmt.Sprintf("%s = ?", key.Key.ToColumnName()), key.Value)
}
err := db.
Delete(nil).
Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-die73", "could not delete object")
}
return nil
}
}
func PrepareDeleteByObject(table string, object interface{}) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
err := db.Table(table).
Delete(object).
Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-lso9w", "could not delete object")
}
return nil
}
}
func PrepareTruncate(table string) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
err := db.
Exec("TRUNCATE " + table).
Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-lso9w", "could not truncate table")
}
return nil
}
}

View File

@@ -0,0 +1,566 @@
package repository
import (
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/model"
"github.com/jinzhu/gorm"
"testing"
)
func TestPrepareGetByKey(t *testing.T) {
type args struct {
table string
key ColumnKey
value string
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"ok",
mockDB(t).
expectGetByID("TESTTABLE", "test", "VALUE"),
args{
table: "TESTTABLE",
key: TestSearchKey_TEST,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"not found",
mockDB(t).
expectGetByIDErr("TESTTABLE", "test", "VALUE", gorm.ErrRecordNotFound),
args{
table: "TESTTABLE",
key: TestSearchKey_TEST,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
{
"db err",
mockDB(t).
expectGetByIDErr("TESTTABLE", "test", "VALUE", gorm.ErrUnaddressable),
args{
table: "TESTTABLE",
key: TestSearchKey_TEST,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &Test{}
getByID := PrepareGetByKey(tt.args.table, tt.args.key, tt.args.value)
err := getByID(tt.db.db, res)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareGetByQuery(t *testing.T) {
type args struct {
table string
searchQuery SearchQuery
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"search with equals case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodEqualsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with equals case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodEquals, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with startswith, case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "LIKE", "VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodStartsWithIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with startswith case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "LIKE", "VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodStartsWith, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with contains case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "LIKE", "%VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with contains case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "LIKE", "%VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodContains, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search expect not found err",
mockDB(t).
expectGetByQueryErr("TESTTABLE", "test", "LIKE", "%VALUE%", gorm.ErrRecordNotFound),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
{
"search expect internal err",
mockDB(t).
expectGetByQueryErr("TESTTABLE", "test", "LIKE", "%VALUE%", gorm.ErrUnaddressable),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
{
"search with invalid column",
mockDB(t).
expectGetByQuery("TESTTABLE", "", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_UNDEFINED, method: model.SearchMethodEqualsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsErrorInvalidArgument,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &Test{}
getByQuery := PrepareGetByQuery(tt.args.table, tt.args.searchQuery)
err := getByQuery(tt.db.db, res)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPreparePut(t *testing.T) {
type args struct {
table string
object *Test
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"ok",
mockDB(t).
expectBegin(nil).
expectSave("TESTTABLE", Test{ID: "AggregateID", Test: "VALUE"}).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "AggregateID", Test: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectSaveErr("TESTTABLE", Test{ID: "AggregateID", Test: "VALUE"}, gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "AggregateID", Test: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getPut := PrepareSave(tt.args.table)
err := getPut(tt.db.db, tt.args.object)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDelete(t *testing.T) {
type args struct {
table string
key ColumnKey
value string
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete",
mockDB(t).
expectBegin(nil).
expectRemove("TESTTABLE", "id", "VALUE").
expectCommit(nil),
args{
table: "TESTTABLE",
key: TestSearchKey_ID,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
key: TestSearchKey_ID,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByKey(tt.args.table, tt.args.key, tt.args.value)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDeleteByKeys(t *testing.T) {
type args struct {
table string
keys []Key
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete single key",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"delete multiple keys",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}, Key{Key: TestSearchKey_TEST, Value: "VALUE2"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
{Key: TestSearchKey_TEST, Value: "VALUE2"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByKeys(tt.args.table, tt.args.keys...)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDeleteByObject(t *testing.T) {
type args struct {
table string
object interface{}
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete",
mockDB(t).
expectBegin(nil).
expectRemoveByObject("TESTTABLE", Test{ID: "VALUE", Test: "TEST"}).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "VALUE", Test: "TEST"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"delete multiple PK",
mockDB(t).
expectBegin(nil).
expectRemoveByObjectMultiplePKs("TESTTABLE", TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"}).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"},
},
res{
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "VALUE", Test: "TEST"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByObject(tt.args.table, tt.args.object)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}

View File

@@ -0,0 +1,88 @@
package repository
import (
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/view/model"
"github.com/jinzhu/gorm"
"strings"
)
type actualSequece struct {
ActualSequence uint64 `gorm:"column:current_sequence"`
}
type CurrentSequence struct {
ViewName string `gorm:"column:view_name;primary_key"`
CurrentSequence uint64 `gorm:"column:current_sequence"`
}
type SequenceSearchKey int32
const (
SequenceSearchKeyUndefined SequenceSearchKey = iota
SequenceSearchKeyViewName
)
type sequenceSearchKey SequenceSearchKey
func (key sequenceSearchKey) ToColumnName() string {
switch SequenceSearchKey(key) {
case SequenceSearchKeyViewName:
return "view_name"
default:
return ""
}
}
func CurrentSequenceToModel(sequence *CurrentSequence) *model.View {
dbView := strings.Split(sequence.ViewName, ".")
return &model.View{
Database: dbView[0],
ViewName: dbView[1],
CurrentSequence: sequence.CurrentSequence,
}
}
func SaveCurrentSequence(db *gorm.DB, table, viewName string, sequence uint64) error {
save := PrepareSave(table)
err := save(db, &CurrentSequence{viewName, sequence})
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-5kOhP", "unable to updated processed sequence")
}
return nil
}
func LatestSequence(db *gorm.DB, table, viewName string) (uint64, error) {
sequence := new(actualSequece)
query := PrepareGetByKey(table, sequenceSearchKey(SequenceSearchKeyViewName), viewName)
err := query(db, sequence)
if err == nil {
return sequence.ActualSequence, nil
}
if caos_errs.IsNotFound(err) {
return 0, nil
}
return 0, caos_errs.ThrowInternalf(err, "VIEW-9LyCB", "unable to get latest sequence of %s", viewName)
}
func AllCurrentSequences(db *gorm.DB, table string) ([]*CurrentSequence, error) {
sequences := make([]*CurrentSequence, 0)
query := PrepareSearchQuery(table, GeneralSearchRequest{})
_, err := query(db, &sequences)
if err != nil {
return nil, err
}
return sequences, nil
}
func ClearView(db *gorm.DB, truncateView, sequenceTable string) error {
truncate := PrepareTruncate(truncateView)
err := truncate(db)
if err != nil {
return err
}
return SaveCurrentSequence(db, sequenceTable, truncateView, 0)
}