Fed. node store implementation & gen. code

This commit is contained in:
Denis Arh
2020-12-13 18:53:18 +01:00
committed by Peter Grlica
parent 7d2ae8880b
commit de889ae378
10 changed files with 690 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
package store
// This file is auto-generated.
//
// Template: pkg/codegen/assets/store_base.gen.go.tpl
// Definitions: store/federation_nodes.yaml
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
import (
"context"
"github.com/cortezaproject/corteza-server/federation/types"
)
type (
FederationNodes interface {
SearchFederationNodes(ctx context.Context, f types.NodeFilter) (types.NodeSet, types.NodeFilter, error)
LookupFederationNodeByID(ctx context.Context, id uint64) (*types.Node, error)
CreateFederationNode(ctx context.Context, rr ...*types.Node) error
UpdateFederationNode(ctx context.Context, rr ...*types.Node) error
UpsertFederationNode(ctx context.Context, rr ...*types.Node) error
DeleteFederationNode(ctx context.Context, rr ...*types.Node) error
DeleteFederationNodeByID(ctx context.Context, ID uint64) error
TruncateFederationNodes(ctx context.Context) error
}
)
var _ *types.Node
var _ context.Context
// SearchFederationNodes returns all matching FederationNodes from store
func SearchFederationNodes(ctx context.Context, s FederationNodes, f types.NodeFilter) (types.NodeSet, types.NodeFilter, error) {
return s.SearchFederationNodes(ctx, f)
}
// LookupFederationNodeByID searches for shared federation node by ID
//
// It returns shared federation node
func LookupFederationNodeByID(ctx context.Context, s FederationNodes, id uint64) (*types.Node, error) {
return s.LookupFederationNodeByID(ctx, id)
}
// CreateFederationNode creates one or more FederationNodes in store
func CreateFederationNode(ctx context.Context, s FederationNodes, rr ...*types.Node) error {
return s.CreateFederationNode(ctx, rr...)
}
// UpdateFederationNode updates one or more (existing) FederationNodes in store
func UpdateFederationNode(ctx context.Context, s FederationNodes, rr ...*types.Node) error {
return s.UpdateFederationNode(ctx, rr...)
}
// UpsertFederationNode creates new or updates existing one or more FederationNodes in store
func UpsertFederationNode(ctx context.Context, s FederationNodes, rr ...*types.Node) error {
return s.UpsertFederationNode(ctx, rr...)
}
// DeleteFederationNode Deletes one or more FederationNodes from store
func DeleteFederationNode(ctx context.Context, s FederationNodes, rr ...*types.Node) error {
return s.DeleteFederationNode(ctx, rr...)
}
// DeleteFederationNodeByID Deletes FederationNode from store
func DeleteFederationNodeByID(ctx context.Context, s FederationNodes, ID uint64) error {
return s.DeleteFederationNodeByID(ctx, ID)
}
// TruncateFederationNodes Deletes all FederationNodes from store
func TruncateFederationNodes(ctx context.Context, s FederationNodes) error {
return s.TruncateFederationNodes(ctx)
}
+40
View File
@@ -0,0 +1,40 @@
import:
- github.com/cortezaproject/corteza-server/federation/types
types:
type: types.Node
fields:
- { field: ID }
- { field: Name }
- { field: SharedNodeID }
- { field: BaseURL }
- { field: Status }
- { field: PairToken }
- { field: AuthToken }
- { field: CreatedBy }
- { field: UpdatedBy }
- { field: DeletedBy }
- { field: CreatedAt }
- { field: UpdatedAt }
- { field: DeletedAt }
lookups:
- fields: [ID]
description: |-
searches for shared federation node by ID
It returns shared federation node
search:
enablePaging: false
enableSorting: false
rdbms:
alias: fdn
table: federation_nodes
customFilterConverter: true
mapFields:
BaseURL: { column: base_url }
SharedNodeID: { column: shared_node_id }
+2
View File
@@ -18,6 +18,7 @@ package store
// - store/credentials.yaml
// - store/federation_exposed_modules.yaml
// - store/federation_module_mappings.yaml
// - store/federation_nodes.yaml
// - store/federation_shared_modules.yaml
// - store/labels.yaml
// - store/messaging_attachments.yaml
@@ -56,6 +57,7 @@ type (
Credentials
FederationExposedModules
FederationModuleMappings
FederationNodes
FederationSharedModules
Labels
MessagingAttachments
+363
View File
@@ -0,0 +1,363 @@
package rdbms
// This file is an auto-generated file
//
// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl
// Definitions: store/federation_nodes.yaml
//
// Changes to this file may cause incorrect behavior
// and will be lost if the code is regenerated.
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/store"
)
var _ = errors.Is
// SearchFederationNodes returns all matching rows
//
// This function calls convertFederationNodeFilter with the given
// types.NodeFilter and expects to receive a working squirrel.SelectBuilder
func (s Store) SearchFederationNodes(ctx context.Context, f types.NodeFilter) (types.NodeSet, types.NodeFilter, error) {
var (
err error
set []*types.Node
q squirrel.SelectBuilder
)
q, err = s.convertFederationNodeFilter(f)
if err != nil {
return nil, f, err
}
return set, f, s.config.ErrorHandler(func() error {
set, _, _, err = s.QueryFederationNodes(ctx, q, f.Check)
return err
}())
}
// QueryFederationNodes queries the database, converts and checks each row and
// returns collected set
//
// Fn also returns total number of fetched items and last fetched item so that the caller can construct cursor
// for next page of results
func (s Store) QueryFederationNodes(
ctx context.Context,
q squirrel.Sqlizer,
check func(*types.Node) (bool, error),
) ([]*types.Node, uint, *types.Node, error) {
var (
set = make([]*types.Node, 0, DefaultSliceCapacity)
res *types.Node
// Query rows with
rows, err = s.Query(ctx, q)
fetched uint
)
if err != nil {
return nil, 0, nil, err
}
defer rows.Close()
for rows.Next() {
fetched++
if err = rows.Err(); err == nil {
res, err = s.internalFederationNodeRowScanner(rows)
}
if err != nil {
return nil, 0, nil, err
}
// If check function is set, call it and act accordingly
if check != nil {
if chk, err := check(res); err != nil {
return nil, 0, nil, err
} else if !chk {
// did not pass the check
// go with the next row
continue
}
}
set = append(set, res)
}
return set, fetched, res, rows.Err()
}
// LookupFederationNodeByID searches for shared federation node by ID
//
// It returns shared federation node
func (s Store) LookupFederationNodeByID(ctx context.Context, id uint64) (*types.Node, error) {
return s.execLookupFederationNode(ctx, squirrel.Eq{
s.preprocessColumn("fdn.id", ""): s.preprocessValue(id, ""),
})
}
// CreateFederationNode creates one or more rows in federation_nodes table
func (s Store) CreateFederationNode(ctx context.Context, rr ...*types.Node) (err error) {
for _, res := range rr {
err = s.checkFederationNodeConstraints(ctx, res)
if err != nil {
return err
}
err = s.execCreateFederationNodes(ctx, s.internalFederationNodeEncoder(res))
if err != nil {
return err
}
}
return
}
// UpdateFederationNode updates one or more existing rows in federation_nodes
func (s Store) UpdateFederationNode(ctx context.Context, rr ...*types.Node) error {
return s.config.ErrorHandler(s.partialFederationNodeUpdate(ctx, nil, rr...))
}
// partialFederationNodeUpdate updates one or more existing rows in federation_nodes
func (s Store) partialFederationNodeUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Node) (err error) {
for _, res := range rr {
err = s.checkFederationNodeConstraints(ctx, res)
if err != nil {
return err
}
err = s.execUpdateFederationNodes(
ctx,
squirrel.Eq{
s.preprocessColumn("fdn.id", ""): s.preprocessValue(res.ID, ""),
},
s.internalFederationNodeEncoder(res).Skip("id").Only(onlyColumns...))
if err != nil {
return s.config.ErrorHandler(err)
}
}
return
}
// UpsertFederationNode updates one or more existing rows in federation_nodes
func (s Store) UpsertFederationNode(ctx context.Context, rr ...*types.Node) (err error) {
for _, res := range rr {
err = s.checkFederationNodeConstraints(ctx, res)
if err != nil {
return err
}
err = s.config.ErrorHandler(s.execUpsertFederationNodes(ctx, s.internalFederationNodeEncoder(res)))
if err != nil {
return err
}
}
return nil
}
// DeleteFederationNode Deletes one or more rows from federation_nodes table
func (s Store) DeleteFederationNode(ctx context.Context, rr ...*types.Node) (err error) {
for _, res := range rr {
err = s.execDeleteFederationNodes(ctx, squirrel.Eq{
s.preprocessColumn("fdn.id", ""): s.preprocessValue(res.ID, ""),
})
if err != nil {
return s.config.ErrorHandler(err)
}
}
return nil
}
// DeleteFederationNodeByID Deletes row from the federation_nodes table
func (s Store) DeleteFederationNodeByID(ctx context.Context, ID uint64) error {
return s.execDeleteFederationNodes(ctx, squirrel.Eq{
s.preprocessColumn("fdn.id", ""): s.preprocessValue(ID, ""),
})
}
// TruncateFederationNodes Deletes all rows from the federation_nodes table
func (s Store) TruncateFederationNodes(ctx context.Context) error {
return s.config.ErrorHandler(s.Truncate(ctx, s.federationNodeTable()))
}
// execLookupFederationNode prepares FederationNode query and executes it,
// returning types.Node (or error)
func (s Store) execLookupFederationNode(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Node, err error) {
var (
row rowScanner
)
row, err = s.QueryRow(ctx, s.federationNodesSelectBuilder().Where(cnd))
if err != nil {
return
}
res, err = s.internalFederationNodeRowScanner(row)
if err != nil {
return
}
return res, nil
}
// execCreateFederationNodes updates all matched (by cnd) rows in federation_nodes with given data
func (s Store) execCreateFederationNodes(ctx context.Context, payload store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.federationNodeTable()).SetMap(payload)))
}
// execUpdateFederationNodes updates all matched (by cnd) rows in federation_nodes with given data
func (s Store) execUpdateFederationNodes(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.federationNodeTable("fdn")).Where(cnd).SetMap(set)))
}
// execUpsertFederationNodes inserts new or updates matching (by-primary-key) rows in federation_nodes with given data
func (s Store) execUpsertFederationNodes(ctx context.Context, set store.Payload) error {
upsert, err := s.config.UpsertBuilder(
s.config,
s.federationNodeTable(),
set,
"id",
)
if err != nil {
return err
}
return s.config.ErrorHandler(s.Exec(ctx, upsert))
}
// execDeleteFederationNodes Deletes all matched (by cnd) rows in federation_nodes with given data
func (s Store) execDeleteFederationNodes(ctx context.Context, cnd squirrel.Sqlizer) error {
return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.federationNodeTable("fdn")).Where(cnd)))
}
func (s Store) internalFederationNodeRowScanner(row rowScanner) (res *types.Node, err error) {
res = &types.Node{}
if _, has := s.config.RowScanners["federationNode"]; has {
scanner := s.config.RowScanners["federationNode"].(func(_ rowScanner, _ *types.Node) error)
err = scanner(row, res)
} else {
err = row.Scan(
&res.ID,
&res.Name,
&res.SharedNodeID,
&res.BaseURL,
&res.Status,
&res.PairToken,
&res.AuthToken,
&res.CreatedBy,
&res.UpdatedBy,
&res.DeletedBy,
&res.CreatedAt,
&res.UpdatedAt,
&res.DeletedAt,
)
}
if err == sql.ErrNoRows {
return nil, store.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("could not scan db row for FederationNode: %w", err)
} else {
return res, nil
}
}
// QueryFederationNodes returns squirrel.SelectBuilder with set table and all columns
func (s Store) federationNodesSelectBuilder() squirrel.SelectBuilder {
return s.SelectBuilder(s.federationNodeTable("fdn"), s.federationNodeColumns("fdn")...)
}
// federationNodeTable name of the db table
func (Store) federationNodeTable(aa ...string) string {
var alias string
if len(aa) > 0 {
alias = " AS " + aa[0]
}
return "federation_nodes" + alias
}
// FederationNodeColumns returns all defined table columns
//
// With optional string arg, all columns are returned aliased
func (Store) federationNodeColumns(aa ...string) []string {
var alias string
if len(aa) > 0 {
alias = aa[0] + "."
}
return []string{
alias + "id",
alias + "name",
alias + "shared_node_id",
alias + "base_url",
alias + "status",
alias + "pair_token",
alias + "auth_token",
alias + "created_by",
alias + "updated_by",
alias + "deleted_by",
alias + "created_at",
alias + "updated_at",
alias + "deleted_at",
}
}
// {true true false false true}
// internalFederationNodeEncoder encodes fields from types.Node to store.Payload (map)
//
// Encoding is done by using generic approach or by calling encodeFederationNode
// func when rdbms.customEncoder=true
func (s Store) internalFederationNodeEncoder(res *types.Node) store.Payload {
return store.Payload{
"id": res.ID,
"name": res.Name,
"shared_node_id": res.SharedNodeID,
"base_url": res.BaseURL,
"status": res.Status,
"pair_token": res.PairToken,
"auth_token": res.AuthToken,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
}
}
// checkFederationNodeConstraints performs lookups (on valid) resource to check if any of the values on unique fields
// already exists in the store
//
// Using built-in constraint checking would be more performant but unfortunately we can not rely
// on the full support (MySQL does not support conditional indexes)
func (s *Store) checkFederationNodeConstraints(ctx context.Context, res *types.Node) error {
// Consider resource valid when all fields in unique constraint check lookups
// have valid (non-empty) value
//
// Only string and uint64 are supported for now
// feel free to add additional types if needed
var valid = true
if !valid {
return nil
}
return nil
}
+28
View File
@@ -0,0 +1,28 @@
package rdbms
import (
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/filter"
"strings"
)
func (s Store) convertFederationNodeFilter(f types.NodeFilter) (query squirrel.SelectBuilder, err error) {
query = s.federationNodesSelectBuilder()
query = filter.StateCondition(query, "fdn.deleted_at", f.Deleted)
if f.Query != "" {
q := "%" + strings.ToLower(f.Query) + "%"
query = query.Where(squirrel.Or{
squirrel.Like{"LOWER(fdn.name)": q},
squirrel.Like{"LOWER(fdn.base_url)": q},
})
}
if f.Status != "" {
query = query.Where(squirrel.Eq{"fdn.status": strings.ToLower(f.Status)})
}
return
}
@@ -0,0 +1,10 @@
package tests
import (
"github.com/cortezaproject/corteza-server/store"
"testing"
)
func testFederationExposedModules(t *testing.T, s store.FederationExposedModules) {
// @todo
}
@@ -0,0 +1,10 @@
package tests
import (
"github.com/cortezaproject/corteza-server/store"
"testing"
)
func testFederationModuleMappings(t *testing.T, s store.FederationModuleMappings) {
// @todo
}
+144
View File
@@ -0,0 +1,144 @@
package tests
import (
"context"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/cortezaproject/corteza-server/pkg/rand"
"github.com/cortezaproject/corteza-server/store"
"github.com/stretchr/testify/require"
"testing"
"time"
)
func testFederationNodes(t *testing.T, s store.FederationNodes) {
var (
ctx = context.Background()
req = require.New(t)
makeNew = func(name string) *types.Node {
// minimum data set for new FederationNode
return &types.Node{
ID: id.Next(),
CreatedAt: time.Now(),
Name: name,
}
}
truncAndCreate = func(t *testing.T) (*require.Assertions, *types.Node) {
req := require.New(t)
req.NoError(s.TruncateFederationNodes(ctx))
res := makeNew(string(rand.Bytes(10)))
req.NoError(s.CreateFederationNode(ctx, res))
return req, res
}
)
t.Run("create", func(t *testing.T) {
FederationNode := makeNew("FederationNodeCRUD")
req.NoError(s.CreateFederationNode(ctx, FederationNode))
})
t.Run("lookup by ID", func(t *testing.T) {
req, FederationNode := truncAndCreate(t)
fetched, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.NoError(err)
req.Equal(FederationNode.Name, fetched.Name)
req.Equal(FederationNode.ID, fetched.ID)
req.NotNil(fetched.CreatedAt)
req.Nil(fetched.UpdatedAt)
req.Nil(fetched.DeletedAt)
})
t.Run("update", func(t *testing.T) {
req, FederationNode := truncAndCreate(t)
FederationNode.Name = "FederationNodeCRUD+2"
req.NoError(s.UpdateFederationNode(ctx, FederationNode))
updated, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.NoError(err)
req.Equal(FederationNode.Name, updated.Name)
})
t.Run("upsert", func(t *testing.T) {
t.Run("existing", func(t *testing.T) {
req, FederationNode := truncAndCreate(t)
FederationNode.Name = "FederationNodeCRUD+2"
req.NoError(s.UpsertFederationNode(ctx, FederationNode))
updated, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.NoError(err)
req.Equal(FederationNode.Name, updated.Name)
})
t.Run("new", func(t *testing.T) {
FederationNode := makeNew("upsert me")
FederationNode.Name = "ComposeChartCRUD+2"
req.NoError(s.UpsertFederationNode(ctx, FederationNode))
upserted, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.NoError(err)
req.Equal(FederationNode.Name, upserted.Name)
})
})
t.Run("delete", func(t *testing.T) {
t.Run("by FederationNode", func(t *testing.T) {
req, FederationNode := truncAndCreate(t)
req.NoError(s.DeleteFederationNode(ctx, FederationNode))
_, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.EqualError(err, store.ErrNotFound.Error())
})
t.Run("by ID", func(t *testing.T) {
req, FederationNode := truncAndCreate(t)
req.NoError(s.DeleteFederationNodeByID(ctx, FederationNode.ID))
_, err := s.LookupFederationNodeByID(ctx, FederationNode.ID)
req.EqualError(err, store.ErrNotFound.Error())
})
})
t.Run("search", func(t *testing.T) {
prefill := []*types.Node{
makeNew("/one-one"),
makeNew("/one-two"),
makeNew("/two-one"),
makeNew("/two-two"),
makeNew("/two-deleted"),
}
count := len(prefill)
prefill[4].DeletedAt = &prefill[4].CreatedAt
valid := count - 1
req.NoError(s.TruncateFederationNodes(ctx))
req.NoError(s.CreateFederationNode(ctx, prefill...))
// search for all valid
set, f, err := s.SearchFederationNodes(ctx, types.NodeFilter{})
req.NoError(err)
req.Len(set, valid) // we've deleted one
// search for ALL
set, f, err = s.SearchFederationNodes(ctx, types.NodeFilter{Deleted: filter.StateInclusive})
req.NoError(err)
req.Len(set, count) // we've deleted one
// search for deleted only
set, f, err = s.SearchFederationNodes(ctx, types.NodeFilter{Deleted: filter.StateExclusive})
req.NoError(err)
req.Len(set, 1) // we've deleted one
// find all prefixed
set, f, err = s.SearchFederationNodes(ctx, types.NodeFilter{Query: "/two"})
req.NoError(err)
req.Len(set, 2)
_ = f // dummy
})
}
@@ -0,0 +1,10 @@
package tests
import (
"github.com/cortezaproject/corteza-server/store"
"testing"
)
func testFederationSharedModules(t *testing.T, s store.FederationSharedModules) {
// @todo
}
+6
View File
@@ -16,6 +16,7 @@ package tests
// - store/credentials.yaml
// - store/federation_exposed_modules.yaml
// - store/federation_module_mappings.yaml
// - store/federation_nodes.yaml
// - store/federation_shared_modules.yaml
// - store/labels.yaml
// - store/messaging_attachments.yaml
@@ -114,6 +115,11 @@ func testAllGenerated(t *testing.T, s store.Storer) {
testFederationModuleMappings(t, s)
})
// Run generated tests for FederationNodes
t.Run("FederationNodes", func(t *testing.T) {
testFederationNodes(t, s)
})
// Run generated tests for FederationSharedModules
t.Run("FederationSharedModules", func(t *testing.T) {
testFederationSharedModules(t, s)