unified-storage: add BatchDelete support to sqlkv implementation (#115573)
This commit is contained in:
1 parent
19c9f21cc4
commit
338ae95ef5
4 files changed
+63
-41
No files matched your search
@@ -0,0 +1,7 @@
|
||||
DELETE
|
||||
FROM {{ .TableName }}
|
||||
WHERE {{ .Ident "key_path" }} IN (
|
||||
{{ range $id, $key_path := .KeyPaths }}
|
||||
{{ if ne $id 0 }}, {{ end }}{{ $.Arg $key_path }}
|
||||
{{ end }}
|
||||
);
|
||||
@@ -34,10 +34,11 @@ func mustTemplate(filename string) *template.Template {
|
||||
|
||||
// Templates.
|
||||
var (
|
||||
sqlKVKeys = mustTemplate("sqlkv_keys.sql")
|
||||
sqlKVGet = mustTemplate("sqlkv_get.sql")
|
||||
sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql")
|
||||
sqlKVDelete = mustTemplate("sqlkv_delete.sql")
|
||||
sqlKVKeys = mustTemplate("sqlkv_keys.sql")
|
||||
sqlKVGet = mustTemplate("sqlkv_get.sql")
|
||||
sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql")
|
||||
sqlKVDelete = mustTemplate("sqlkv_delete.sql")
|
||||
sqlKVBatchDelete = mustTemplate("sqlkv_batch_delete.sql")
|
||||
)
|
||||
|
||||
// sqlKVSection can be embedded in structs used when rendering query templates
|
||||
@@ -108,17 +109,17 @@ func (req sqlKVGetRequest) Results() ([]byte, error) {
|
||||
return req.Value, nil
|
||||
}
|
||||
|
||||
type sqlKVBatchGetRequest struct {
|
||||
type sqlKVBatchRequest struct {
|
||||
sqltemplate.SQLTemplate
|
||||
sqlKVSection
|
||||
Keys []string
|
||||
}
|
||||
|
||||
func (req sqlKVBatchGetRequest) Validate() error {
|
||||
func (req sqlKVBatchRequest) Validate() error {
|
||||
return req.sqlKVSection.Validate()
|
||||
}
|
||||
|
||||
func (req sqlKVBatchGetRequest) KeyPaths() []string {
|
||||
func (req sqlKVBatchRequest) KeyPaths() []string {
|
||||
result := make([]string, 0, len(req.Keys))
|
||||
for _, key := range req.Keys {
|
||||
result = append(result, req.Section+"/"+key)
|
||||
@@ -250,7 +251,7 @@ func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) ite
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchGetRequest{
|
||||
rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchRequest{
|
||||
SQLTemplate: sqltemplate.New(k.dialect),
|
||||
sqlKVSection: sqlKVSection{section},
|
||||
Keys: keys,
|
||||
@@ -322,7 +323,19 @@ func (k *sqlKV) Delete(ctx context.Context, section string, key string) error {
|
||||
}
|
||||
|
||||
func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) error {
|
||||
panic("not implemented!")
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := dbutil.Exec(ctx, k.db, sqlKVBatchDelete, sqlKVBatchRequest{
|
||||
SQLTemplate: sqltemplate.New(k.dialect),
|
||||
sqlKVSection: sqlKVSection{section},
|
||||
Keys: keys,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to batch delete keys: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *sqlKV) UnixTimestamp(ctx context.Context) (int64, error) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -786,35 +788,35 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
|
||||
func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second))
|
||||
section := nsPrefix + "-batchdelete"
|
||||
nsPrefix += "-batchdelete"
|
||||
|
||||
t.Run("batch delete existing keys", func(t *testing.T) {
|
||||
// Setup test data
|
||||
testData := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
namespacedKey(nsPrefix, "key1"): "value1",
|
||||
namespacedKey(nsPrefix, "key2"): "value2",
|
||||
namespacedKey(nsPrefix, "key3"): "value3",
|
||||
}
|
||||
|
||||
// Save test data
|
||||
for key, value := range testData {
|
||||
saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value))
|
||||
saveKVHelper(t, kv, ctx, testSection, key, strings.NewReader(value))
|
||||
}
|
||||
|
||||
// Verify keys exist before deletion
|
||||
for key := range testData {
|
||||
_, err := kv.Get(ctx, section, key)
|
||||
_, err := kv.Get(ctx, testSection, key)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Batch delete all keys
|
||||
keys := []string{"key1", "key2", "key3"}
|
||||
err := kv.BatchDelete(ctx, section, keys)
|
||||
keys := slices.Collect(maps.Keys(testData))
|
||||
err := kv.BatchDelete(ctx, testSection, keys)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify all keys are deleted
|
||||
for _, key := range keys {
|
||||
_, err := kv.Get(ctx, section, key)
|
||||
_, err := kv.Get(ctx, testSection, key)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, resource.ErrNotFound, err)
|
||||
}
|
||||
@@ -822,39 +824,40 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
|
||||
t.Run("batch delete with non-existent keys", func(t *testing.T) {
|
||||
// Setup some test data
|
||||
saveKVHelper(t, kv, ctx, section, "existing-key-1", strings.NewReader("value1"))
|
||||
saveKVHelper(t, kv, ctx, section, "existing-key-2", strings.NewReader("value2"))
|
||||
key1, key2 := namespacedKey(nsPrefix, "existing-key-1"), namespacedKey(nsPrefix, "existing-key-2")
|
||||
saveKVHelper(t, kv, ctx, testSection, key1, strings.NewReader("value1"))
|
||||
saveKVHelper(t, kv, ctx, testSection, key2, strings.NewReader("value2"))
|
||||
|
||||
// Batch delete with mix of existing and non-existent keys
|
||||
keys := []string{"existing-key-1", "non-existent-1", "existing-key-2", "non-existent-2"}
|
||||
err := kv.BatchDelete(ctx, section, keys)
|
||||
keys := []string{key1, namespacedKey(nsPrefix, "non-existent-1"), key2, namespacedKey(nsPrefix, "non-existent-2")}
|
||||
err := kv.BatchDelete(ctx, testSection, keys)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify existing keys are deleted
|
||||
_, err = kv.Get(ctx, section, "existing-key-1")
|
||||
assert.Error(t, err)
|
||||
_, err = kv.Get(ctx, testSection, key1)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, resource.ErrNotFound, err)
|
||||
|
||||
_, err = kv.Get(ctx, section, "existing-key-2")
|
||||
assert.Error(t, err)
|
||||
_, err = kv.Get(ctx, testSection, key2)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, resource.ErrNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("batch delete with all non-existent keys", func(t *testing.T) {
|
||||
// Batch delete keys that don't exist
|
||||
keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"}
|
||||
err := kv.BatchDelete(ctx, section, keys)
|
||||
keys := namespacedKeys(nsPrefix, []string{"non-existent-1", "non-existent-2", "non-existent-3"})
|
||||
err := kv.BatchDelete(ctx, testSection, keys)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("batch delete with empty keys list", func(t *testing.T) {
|
||||
keys := []string{}
|
||||
err := kv.BatchDelete(ctx, section, keys)
|
||||
err := kv.BatchDelete(ctx, testSection, keys)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("batch delete with empty section", func(t *testing.T) {
|
||||
keys := []string{"some-key"}
|
||||
keys := namespacedKeys(nsPrefix, []string{"some-key"})
|
||||
err := kv.BatchDelete(ctx, "", keys)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "section is required")
|
||||
@@ -862,27 +865,27 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
|
||||
t.Run("batch delete preserves other keys", func(t *testing.T) {
|
||||
// Setup test data
|
||||
saveKVHelper(t, kv, ctx, section, "keep-key-1", strings.NewReader("keep-value-1"))
|
||||
saveKVHelper(t, kv, ctx, section, "delete-key-1", strings.NewReader("delete-value-1"))
|
||||
saveKVHelper(t, kv, ctx, section, "keep-key-2", strings.NewReader("keep-value-2"))
|
||||
saveKVHelper(t, kv, ctx, section, "delete-key-2", strings.NewReader("delete-value-2"))
|
||||
saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "keep-key-1"), strings.NewReader("keep-value-1"))
|
||||
saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "delete-key-1"), strings.NewReader("delete-value-1"))
|
||||
saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "keep-key-2"), strings.NewReader("keep-value-2"))
|
||||
saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "delete-key-2"), strings.NewReader("delete-value-2"))
|
||||
|
||||
// Batch delete specific keys
|
||||
keys := []string{"delete-key-1", "delete-key-2"}
|
||||
err := kv.BatchDelete(ctx, section, keys)
|
||||
keys := namespacedKeys(nsPrefix, []string{"delete-key-1", "delete-key-2"})
|
||||
err := kv.BatchDelete(ctx, testSection, keys)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify deleted keys are gone
|
||||
_, err = kv.Get(ctx, section, "delete-key-1")
|
||||
_, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "delete-key-1"))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, resource.ErrNotFound, err)
|
||||
|
||||
_, err = kv.Get(ctx, section, "delete-key-2")
|
||||
_, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "delete-key-2"))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, resource.ErrNotFound, err)
|
||||
|
||||
// Verify kept keys still exist
|
||||
reader, err := kv.Get(ctx, section, "keep-key-1")
|
||||
reader, err := kv.Get(ctx, testSection, namespacedKey(nsPrefix, "keep-key-1"))
|
||||
require.NoError(t, err)
|
||||
value, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
@@ -890,7 +893,7 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
err = reader.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
reader, err = kv.Get(ctx, section, "keep-key-2")
|
||||
reader, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "keep-key-2"))
|
||||
require.NoError(t, err)
|
||||
value, err = io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -50,7 +50,6 @@ func TestSQLKV(t *testing.T) {
|
||||
TestKVSave: true,
|
||||
TestKVConcurrent: true,
|
||||
TestKVUnixTimestamp: true,
|
||||
TestKVBatchDelete: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user