mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
basic list
This commit is contained in:
parent
4275a01bc2
commit
26a2f947e8
@ -504,6 +504,7 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
|
||||
if err := s.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO:
|
||||
|
||||
rsp, err := s.backend.PrepareList(ctx, req)
|
||||
// Status???
|
||||
|
@ -377,9 +377,9 @@ func (b *backend) Read(ctx context.Context, req *resource.ReadRequest) (*resourc
|
||||
}
|
||||
|
||||
readReq := sqlResourceReadRequest{
|
||||
SQLTemplate: sqltemplate.New(b.sqlDialect),
|
||||
Request: req,
|
||||
readResponseSet: new(readResponseSet),
|
||||
SQLTemplate: sqltemplate.New(b.sqlDialect),
|
||||
Request: req,
|
||||
readResponse: new(readResponse),
|
||||
}
|
||||
|
||||
sr := sqlResourceRead
|
||||
@ -399,12 +399,40 @@ func (b *backend) Read(ctx context.Context, req *resource.ReadRequest) (*resourc
|
||||
}
|
||||
|
||||
func (b *backend) PrepareList(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) {
|
||||
_, span := b.tracer.Start(ctx, "storage_server.List")
|
||||
_, span := b.tracer.Start(ctx, trace_prefix+"List")
|
||||
defer span.End()
|
||||
|
||||
fmt.Printf("TODO, LIST: %+v", req.Options.Key)
|
||||
readReq := sqlResourceListRequest{
|
||||
SQLTemplate: sqltemplate.New(b.sqlDialect),
|
||||
Request: req,
|
||||
Response: new(resource.ResourceWrapper),
|
||||
}
|
||||
query, err := sqltemplate.Execute(sqlResourceList, readReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute SQL template to list resources: %w", err)
|
||||
}
|
||||
|
||||
return nil, ErrNotImplementedYet
|
||||
rows, err := b.sqlDB.QueryContext(ctx, query, readReq.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list resources: %w", err)
|
||||
}
|
||||
|
||||
out := &resource.ListResponse{
|
||||
Items: make([]*resource.ResourceWrapper, req.Limit),
|
||||
ResourceVersion: 0, // TODO
|
||||
|
||||
}
|
||||
for i := 1; rows.Next(); i++ {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if err := rows.Scan(readReq.GetScanDest()...); err != nil {
|
||||
return nil, fmt.Errorf("scan row #%d: %w", i, err)
|
||||
}
|
||||
rw := *readReq.Response
|
||||
out.Items = append(out.Items, &rw)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (b *backend) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) {
|
||||
@ -491,6 +519,9 @@ func (b *backend) poll(ctx context.Context, since int64, stream chan<- *resource
|
||||
|
||||
// resourceVersionAtomicInc atomically increases the version of a kind within a
|
||||
// transaction.
|
||||
// TODO: Ideally we should attempt to update the RV in the resource and resource_history tables
|
||||
// in a single roundtrip. This would reduce the latency of the operation, and also increase the
|
||||
// throughput of the system. This is a good candidate for a future optimization.
|
||||
func resourceVersionAtomicInc(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, key *resource.ResourceKey) (newVersion int64, err error) {
|
||||
|
||||
// 1. Increment the resource version
|
||||
|
@ -11,14 +11,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
"github.com/zeebo/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testsuite.Run(m)
|
||||
}
|
||||
|
||||
func TestBackendCRUDLW(t *testing.T) {
|
||||
func TestBackendHappyPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dbstore := db.InitTestDB(t)
|
||||
|
||||
@ -34,11 +34,11 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
stream, err := store.WatchWriteEvents(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("WriteEvent Add 3 objects", func(t *testing.T) {
|
||||
t.Run("Add 3 resources", func(t *testing.T) {
|
||||
for i := 1; i <= 3; i++ {
|
||||
rv, err := store.WriteEvent(ctx, resource.WriteEvent{
|
||||
Type: resource.WatchEvent_ADDED,
|
||||
Value: []byte("initial value"),
|
||||
Value: []byte("initial value " + strconv.Itoa(i)),
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "namespace",
|
||||
Group: "group",
|
||||
@ -51,7 +51,7 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WriteEvent Update item2", func(t *testing.T) {
|
||||
t.Run("Update item2", func(t *testing.T) {
|
||||
rv, err := store.WriteEvent(ctx, resource.WriteEvent{
|
||||
Type: resource.WatchEvent_MODIFIED,
|
||||
Value: []byte("updated value"),
|
||||
@ -66,7 +66,7 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
assert.Equal(t, int64(4), rv)
|
||||
})
|
||||
|
||||
t.Run("WriteEvent Delete item1", func(t *testing.T) {
|
||||
t.Run("Delete item1", func(t *testing.T) {
|
||||
rv, err := store.WriteEvent(ctx, resource.WriteEvent{
|
||||
Type: resource.WatchEvent_DELETED,
|
||||
Key: &resource.ResourceKey{
|
||||
@ -80,7 +80,7 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
assert.Equal(t, int64(5), rv)
|
||||
})
|
||||
|
||||
t.Run("Read latest", func(t *testing.T) {
|
||||
t.Run("Read latest item 2", func(t *testing.T) {
|
||||
resp, err := store.Read(ctx, &resource.ReadRequest{
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "namespace",
|
||||
@ -94,7 +94,7 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
assert.Equal(t, "updated value", string(resp.Value))
|
||||
})
|
||||
|
||||
t.Run("Read early verions", func(t *testing.T) {
|
||||
t.Run("Read early verion of item2", func(t *testing.T) {
|
||||
resp, err := store.Read(ctx, &resource.ReadRequest{
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "namespace",
|
||||
@ -106,32 +106,40 @@ func TestBackendCRUDLW(t *testing.T) {
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), resp.ResourceVersion)
|
||||
assert.Equal(t, "initial value", string(resp.Value))
|
||||
assert.Equal(t, "initial value 2", string(resp.Value))
|
||||
})
|
||||
|
||||
t.Run("PrepareList latest", func(t *testing.T) {
|
||||
resp, err := store.PrepareList(ctx, &resource.ListRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp.Items, 2)
|
||||
assert.Equal(t, "updated value", string(resp.Items[0].Value))
|
||||
assert.Equal(t, "initial value 3", string(resp.Items[1].Value))
|
||||
})
|
||||
|
||||
t.Run("Watch events", func(t *testing.T) {
|
||||
event := <-stream
|
||||
assert.Equal(t, "item1", event.Key.Name)
|
||||
assert.Equal(t, 1, event.ResourceVersion)
|
||||
assert.Equal(t, int64(1), event.ResourceVersion)
|
||||
assert.Equal(t, resource.WatchEvent_ADDED, event.Type)
|
||||
event = <-stream
|
||||
assert.Equal(t, "item2", event.Key.Name)
|
||||
assert.Equal(t, 2, event.ResourceVersion)
|
||||
assert.Equal(t, int64(2), event.ResourceVersion)
|
||||
assert.Equal(t, resource.WatchEvent_ADDED, event.Type)
|
||||
|
||||
event = <-stream
|
||||
assert.Equal(t, "item3", event.Key.Name)
|
||||
assert.Equal(t, 3, event.ResourceVersion)
|
||||
assert.Equal(t, int64(3), event.ResourceVersion)
|
||||
assert.Equal(t, resource.WatchEvent_ADDED, event.Type)
|
||||
|
||||
event = <-stream
|
||||
assert.Equal(t, "item2", event.Key.Name)
|
||||
assert.Equal(t, 4, event.ResourceVersion)
|
||||
assert.Equal(t, int64(4), event.ResourceVersion)
|
||||
assert.Equal(t, resource.WatchEvent_MODIFIED, event.Type)
|
||||
|
||||
event = <-stream
|
||||
assert.Equal(t, "item1", event.Key.Name)
|
||||
assert.Equal(t, 5, event.ResourceVersion)
|
||||
assert.Equal(t, int64(5), event.ResourceVersion)
|
||||
assert.Equal(t, resource.WatchEvent_DELETED, event.Type)
|
||||
})
|
||||
}
|
||||
|
20
pkg/storage/unified/sql/data/resource_list.sql
Normal file
20
pkg/storage/unified/sql/data/resource_list.sql
Normal file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
{{ .Ident "resource_version" | .Into .Response.ResourceVersion }},
|
||||
{{ .Ident "value" | .Into .Response.Value }}
|
||||
FROM {{ .Ident "resource" }}
|
||||
WHERE 1 = 1
|
||||
{{ if and .Request.Options .Request.Options.Key }}
|
||||
{{ if .Request.Options.Key.Namespace }}
|
||||
AND {{ .Ident "namespace" }} = {{ .Arg .Request.Options.Key.Namespace }}
|
||||
{{ end }}
|
||||
{{ if .Request.Options.Key.Group }}
|
||||
AND {{ .Ident "group" }} = {{ .Arg .Request.Options.Key.Group }}
|
||||
{{ end }}
|
||||
{{ if .Request.Options.Key.Resource }}
|
||||
AND {{ .Ident "resource" }} = {{ .Arg .Request.Options.Key.Resource }}
|
||||
{{ end }}
|
||||
{{ if .Request.Options.Key.Name }}
|
||||
AND {{ .Ident "name" }} = {{ .Arg .Request.Options.Key.Name }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
;
|
@ -37,6 +37,7 @@ var (
|
||||
sqlResourceInsert = mustTemplate("resource_insert.sql")
|
||||
sqlResourceUpdate = mustTemplate("resource_update.sql")
|
||||
sqlResourceRead = mustTemplate("resource_read.sql")
|
||||
sqlResourceList = mustTemplate("resource_list.sql")
|
||||
sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql")
|
||||
sqlResourceHistoryRead = mustTemplate("resource_history_read.sql")
|
||||
sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql")
|
||||
@ -97,8 +98,6 @@ func (r sqlResourceRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// sqlResourceHistoryReadRequest can be used to retrieve a row fromthe "resource_history" tables.
|
||||
|
||||
type historyPollResponse struct {
|
||||
Key resource.ResourceKey
|
||||
ResourceVersion int64
|
||||
@ -122,24 +121,43 @@ func (r sqlResourceHistoryPollRequest) Validate() error {
|
||||
|
||||
// sqlResourceReadRequest can be used to retrieve a row fromthe "resource" tables.
|
||||
|
||||
type readResponseSet struct {
|
||||
type key struct {
|
||||
resource.ReadResponse
|
||||
}
|
||||
|
||||
func (r *readResponseSet) Results() (*readResponseSet, error) {
|
||||
type readRequest struct {
|
||||
resource.ReadResponse
|
||||
}
|
||||
|
||||
type readResponse struct {
|
||||
resource.ReadResponse
|
||||
}
|
||||
|
||||
func (r *readResponse) Results() (*readResponse, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type sqlResourceReadRequest struct {
|
||||
*sqltemplate.SQLTemplate
|
||||
Request *resource.ReadRequest
|
||||
*readResponseSet
|
||||
*readResponse
|
||||
}
|
||||
|
||||
func (r sqlResourceReadRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// List
|
||||
type sqlResourceListRequest struct {
|
||||
*sqltemplate.SQLTemplate
|
||||
Request *resource.ListRequest
|
||||
Response *resource.ResourceWrapper
|
||||
}
|
||||
|
||||
func (r sqlResourceListRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// update RV
|
||||
|
||||
type sqlResourceUpdateRVRequest struct {
|
||||
|
@ -160,7 +160,7 @@ func TestQueries(t *testing.T) {
|
||||
Request: &resource.ReadRequest{
|
||||
Key: &resource.ResourceKey{},
|
||||
},
|
||||
readResponseSet: new(readResponseSet),
|
||||
readResponse: new(readResponse),
|
||||
},
|
||||
Expected: expected{
|
||||
"resource_read_mysql_sqlite.sql": dialects{
|
||||
@ -170,6 +170,28 @@ func TestQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlResourceList: {
|
||||
{
|
||||
Name: "filter on namespace",
|
||||
Data: &sqlResourceListRequest{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Request: &resource.ListRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
Response: new(resource.ResourceWrapper),
|
||||
},
|
||||
Expected: expected{
|
||||
"resource_list_mysql_sqlite.sql": dialects{
|
||||
sqltemplate.MySQL,
|
||||
sqltemplate.SQLite,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlResourceUpdateRV: {
|
||||
{
|
||||
Name: "single path",
|
||||
@ -189,6 +211,11 @@ func TestQueries(t *testing.T) {
|
||||
Name: "single path",
|
||||
Data: &sqlResourceReadRequest{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Request: &resource.ReadRequest{
|
||||
ResourceVersion: 123,
|
||||
Key: &resource.ResourceKey{},
|
||||
},
|
||||
readResponse: new(readResponse),
|
||||
},
|
||||
Expected: expected{
|
||||
"resource_history_read_mysql_sqlite.sql": dialects{
|
||||
@ -317,282 +344,3 @@ func TestQueries(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// func TestReadEntity(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // readonly, shared data for all subtests
|
||||
// expectedEntity := newEmptyEntity()
|
||||
// testdataJSON(t, `grpc-res-entity.json`, expectedEntity)
|
||||
// key, err := grafanaregistry.ParseKey(expectedEntity.Key)
|
||||
// require.NoErrorf(t, err, "provided key: %#v", expectedEntity)
|
||||
|
||||
// t.Run("happy path - entity table, optimistic locking", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// x := expectReadEntity(t, mock, cloneEntity(expectedEntity))
|
||||
// x(ctx, db)
|
||||
// })
|
||||
|
||||
// t.Run("happy path - entity table, no optimistic locking", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // test declarations
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||
// SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||
// Key: new(grafanaregistry.Key),
|
||||
// returnsEntitySet: newReturnsEntitySet(),
|
||||
// }
|
||||
// readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||
// results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||
|
||||
// // setup expectations
|
||||
// results.AddCurrentData()
|
||||
// mock.ExpectQuery(`select from entity where !resource_version update`).
|
||||
// WillReturnRows(results.Rows())
|
||||
|
||||
// // execute and assert
|
||||
// e, err := readEntity(ctx, db, sqltemplate.MySQL, key, 0, false, true)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, expectedEntity, e.Entity)
|
||||
// })
|
||||
|
||||
// t.Run("happy path - entity_history table", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // test declarations
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||
// SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||
// Key: new(grafanaregistry.Key),
|
||||
// returnsEntitySet: newReturnsEntitySet(),
|
||||
// }
|
||||
// readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||
// results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||
|
||||
// // setup expectations
|
||||
// results.AddCurrentData()
|
||||
// mock.ExpectQuery(`select from entity_history where resource_version !update`).
|
||||
// WillReturnRows(results.Rows())
|
||||
|
||||
// // execute and assert
|
||||
// e, err := readEntity(ctx, db, sqltemplate.MySQL, key,
|
||||
// expectedEntity.ResourceVersion, false, false)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, expectedEntity, e.Entity)
|
||||
// })
|
||||
|
||||
// t.Run("entity table, optimistic locking failed", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// x := expectReadEntity(t, mock, nil)
|
||||
// x(ctx, db)
|
||||
// })
|
||||
|
||||
// t.Run("entity_history table, entity not found", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // test declarations
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||
// SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||
// Key: new(grafanaregistry.Key),
|
||||
// returnsEntitySet: newReturnsEntitySet(),
|
||||
// }
|
||||
// results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||
|
||||
// // setup expectations
|
||||
// mock.ExpectQuery(`select from entity_history where resource_version !update`).
|
||||
// WillReturnRows(results.Rows())
|
||||
|
||||
// // execute and assert
|
||||
// e, err := readEntity(ctx, db, sqltemplate.MySQL, key,
|
||||
// expectedEntity.ResourceVersion, false, false)
|
||||
// require.Nil(t, e)
|
||||
// require.Error(t, err)
|
||||
// require.ErrorIs(t, err, ErrNotFound)
|
||||
// })
|
||||
|
||||
// t.Run("entity_history table, entity was deleted = not found", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // test declarations
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||
// SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||
// Key: new(grafanaregistry.Key),
|
||||
// returnsEntitySet: newReturnsEntitySet(),
|
||||
// }
|
||||
// readReq.Entity.Entity = cloneEntity(expectedEntity)
|
||||
// readReq.Entity.Entity.Action = entity.Entity_DELETED
|
||||
// results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||
|
||||
// // setup expectations
|
||||
// results.AddCurrentData()
|
||||
// mock.ExpectQuery(`select from entity_history where resource_version !update`).
|
||||
// WillReturnRows(results.Rows())
|
||||
|
||||
// // execute and assert
|
||||
// e, err := readEntity(ctx, db, sqltemplate.MySQL, key,
|
||||
// expectedEntity.ResourceVersion, false, false)
|
||||
// require.Nil(t, e)
|
||||
// require.Error(t, err)
|
||||
// require.ErrorIs(t, err, ErrNotFound)
|
||||
// })
|
||||
// }
|
||||
|
||||
// // expectReadEntity arranges test expectations so that it's easier to reuse
|
||||
// // across tests that need to call `readEntity`. If you provide a non-nil
|
||||
// // *entity.Entity, that will be returned by `readEntity`. If it's nil, then
|
||||
// // `readEntity` will return ErrOptimisticLockingFailed. It returns the function
|
||||
// // to execute the actual test and assert the expectations that were set.
|
||||
// func expectReadEntity(t *testing.T, mock sqlmock.Sqlmock, e *entity.Entity) func(ctx context.Context, db db.DB) {
|
||||
// t.Helper()
|
||||
|
||||
// // test declarations
|
||||
// readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||
// SQLTemplate: sqltemplate.New(sqltemplate.MySQL),
|
||||
// Key: new(grafanaregistry.Key),
|
||||
// returnsEntitySet: newReturnsEntitySet(),
|
||||
// }
|
||||
// results := newMockResults(t, mock, sqlEntityRead, readReq)
|
||||
// if e != nil {
|
||||
// readReq.Entity.Entity = cloneEntity(e)
|
||||
// }
|
||||
|
||||
// // setup expectations
|
||||
// results.AddCurrentData()
|
||||
// mock.ExpectQuery(`select from entity where !resource_version update`).
|
||||
// WillReturnRows(results.Rows())
|
||||
|
||||
// // execute and assert
|
||||
// if e != nil {
|
||||
// return func(ctx context.Context, db db.DB) {
|
||||
// ent, err := readEntity(ctx, db, sqltemplate.MySQL, readReq.Key,
|
||||
// e.ResourceVersion, true, true)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, e, ent.Entity)
|
||||
// }
|
||||
// }
|
||||
|
||||
// return func(ctx context.Context, db db.DB) {
|
||||
// ent, err := readEntity(ctx, db, sqltemplate.MySQL, readReq.Key, 1, true,
|
||||
// true)
|
||||
// require.Nil(t, ent)
|
||||
// require.Error(t, err)
|
||||
// require.ErrorIs(t, err, ErrOptimisticLockingFailed)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestKindVersionAtomicInc(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// t.Run("happy path - row locked", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// // test declarations
|
||||
// const curVersion int64 = 1
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
|
||||
// // setup expectations
|
||||
// mock.ExpectQuery(`select resource_version from resource_version where group resource update`).
|
||||
// WillReturnRows(mock.NewRows([]string{"resource_version"}).AddRow(curVersion))
|
||||
// mock.ExpectExec("update resource_version set resource_version updated_at where group resource").
|
||||
// WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
// // execute and assert
|
||||
// gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname")
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, curVersion+1, gotVersion)
|
||||
// })
|
||||
|
||||
// t.Run("happy path - row created", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// x := expectKindVersionAtomicInc(t, mock, false)
|
||||
// x(ctx, db)
|
||||
// })
|
||||
|
||||
// t.Run("fail to create row", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// ctx := testutil.NewDefaultTestContext(t)
|
||||
// db, mock := newMockDBMatchWords(t)
|
||||
// x := expectKindVersionAtomicInc(t, mock, true)
|
||||
// x(ctx, db)
|
||||
// })
|
||||
// }
|
||||
|
||||
// // expectKindVersionAtomicInc arranges test expectations so that it's easier to
|
||||
// // reuse across tests that need to call `kindVersionAtomicInc`. If you the test
|
||||
// // shuld fail, it will do so with `errTest`, and it will return resource version
|
||||
// // 1 otherwise. It returns the function to execute the actual test and assert
|
||||
// // the expectations that were set.
|
||||
// func expectKindVersionAtomicInc(t *testing.T, mock sqlmock.Sqlmock, shouldFail bool) func(ctx context.Context, db db.DB) {
|
||||
// t.Helper()
|
||||
|
||||
// // setup expectations
|
||||
// mock.ExpectQuery(`select resource_version from resource_version where group resource update`).
|
||||
// WillReturnRows(mock.NewRows([]string{"resource_version"}))
|
||||
// call := mock.ExpectExec("insert resource_version resource_version")
|
||||
|
||||
// // execute and assert
|
||||
// if shouldFail {
|
||||
// call.WillReturnError(errTest)
|
||||
|
||||
// return func(ctx context.Context, db db.DB) {
|
||||
// gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname")
|
||||
// require.Zero(t, gotVersion)
|
||||
// require.Error(t, err)
|
||||
// require.ErrorIs(t, err, errTest)
|
||||
// }
|
||||
// }
|
||||
// call.WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
// return func(ctx context.Context, db db.DB) {
|
||||
// gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname")
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, int64(1), gotVersion)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestMustTemplate(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
// require.Panics(t, func() {
|
||||
// mustTemplate("non existent file")
|
||||
// })
|
||||
// }
|
||||
|
||||
// // Debug provides greater detail about the SQL error. It is defined on the same
|
||||
// // struct but on a test file so that the intention that its results should not
|
||||
// // be used in runtime code is very clear. The results could include PII or
|
||||
// // otherwise regulated information, hence this method is only available in
|
||||
// // tests, so that it can be used in local debugging only. Note that the error
|
||||
// // information may still be available through other means, like using the
|
||||
// // "reflect" package, so care must be taken not to ever expose these information
|
||||
// // in production.
|
||||
// func (e SQLError) Debug() string {
|
||||
// scanDestStr := "(none)"
|
||||
// if len(e.ScanDest) > 0 {
|
||||
// format := "[%T" + strings.Repeat(", %T", len(e.ScanDest)-1) + "]"
|
||||
// scanDestStr = fmt.Sprintf(format, e.ScanDest...)
|
||||
// }
|
||||
|
||||
// return fmt.Sprintf("%s: %s: %v\n\tArguments (%d): %#v\n\tReturn Value "+
|
||||
// "Types (%d): %s\n\tExecuted Query: %s\n\tRaw SQL Template Output: %s",
|
||||
// e.TemplateName, e.CallType, e.Err, len(e.arguments), e.arguments,
|
||||
// len(e.ScanDest), scanDestStr, e.Query, e.RawQuery)
|
||||
// }
|
||||
|
@ -1,3 +0,0 @@
|
||||
INSERT INTO "entity_folder"
|
||||
("guid", "namespace", "name", "slug_path", "tree", "depth", "lft", "rgt", "detached")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
@ -1,5 +0,0 @@
|
||||
INSERT INTO "entity_folder"
|
||||
("guid", "namespace", "name", "slug_path", "tree", "depth", "lft", "rgt", "detached")
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?),
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?);
|
@ -1,5 +0,0 @@
|
||||
SELECT e."guid", e."resource_version", e."key", e."group", e."group_version", e."resource", e."namespace", e."name", e."folder", e."meta", e."body", e."status", e."size", e."etag", e."created_at", e."created_by", e."updated_at", e."updated_by", e."origin", e."origin_key", e."origin_ts", e."title", e."slug", e."description", e."message", e."labels", e."fields", e."errors", e."action"
|
||||
FROM "entity_history" AS e
|
||||
WHERE 1 = 1 AND "namespace" = ? AND "group" = ? AND "resource" = ? AND "name" = ? AND "resource_version" <= ?
|
||||
ORDER BY "resource_version" DESC
|
||||
LIMIT 1 FOR UPDATE NOWAIT;
|
@ -1 +0,0 @@
|
||||
DELETE FROM "entity_labels" WHERE 1 = 1 AND "guid" = ? AND "label" NOT IN (?);
|
@ -1 +0,0 @@
|
||||
DELETE FROM "entity_labels" WHERE 1 = 1 AND "guid" = ? AND "label" NOT IN (?, ?);
|
@ -1,2 +0,0 @@
|
||||
INSERT INTO "entity_labels" ("guid", "label", "value")
|
||||
VALUES (?, ?, ?);
|
@ -1,3 +0,0 @@
|
||||
INSERT INTO "entity_labels" ("guid", "label", "value") VALUES
|
||||
(?, ?, ?),
|
||||
(?, ?, ?);
|
@ -1,5 +1,6 @@
|
||||
SELECT "resource_version", "value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1 AND "namespace" = ? AND "group" = ? AND "resource" = ? AND "name" = ? AND "resource_version" <= ?
|
||||
ORDER BY "resource_version" DESC
|
||||
ORDER BY "resource_version" DESC
|
||||
LIMIT 1
|
||||
;
|
||||
|
3
pkg/storage/unified/sql/testdata/resource_list_mysql_sqlite.sql
vendored
Normal file
3
pkg/storage/unified/sql/testdata/resource_list_mysql_sqlite.sql
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
SELECT "resource_version", "value"
|
||||
FROM "resource"
|
||||
WHERE 1 = 1 AND "namespace" = ?;
|
Loading…
Reference in New Issue
Block a user