Skip to content

Commit 087a597

Browse files
authored
Added conformance tests for DeleteWithPrefix (dapr#3288)
Signed-off-by: ItalyPaleAle <[email protected]> Signed-off-by: Alessandro (Ale) Segala <[email protected]>
1 parent 1aa44e9 commit 087a597

File tree

6 files changed

+124
-17
lines changed

6 files changed

+124
-17
lines changed

state/requests.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ type DeleteWithPrefixRequest struct {
7676
}
7777

7878
func (r *DeleteWithPrefixRequest) Validate() error {
79-
if r.Prefix == "" {
79+
if r.Prefix == "" || r.Prefix == "||" {
8080
return fmt.Errorf("a prefix is required for deleteWithPrefix request")
8181
}
8282
if !strings.HasSuffix(r.Prefix, "||") {

state/sqlite/sqlite.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (s *SQLiteStore) Multi(ctx context.Context, request *state.TransactionalSta
101101
return s.dbaccess.ExecuteMulti(ctx, request.Operations)
102102
}
103103

104-
// DeleteWithPrefix deletes an actor's state
104+
// DeleteWithPrefix deletes objects with a prefix.
105105
func (s *SQLiteStore) DeleteWithPrefix(ctx context.Context, req state.DeleteWithPrefixRequest) (state.DeleteWithPrefixResponse, error) {
106106
return s.dbaccess.DeleteWithPrefix(ctx, req)
107107
}

state/sqlite/sqlite_dbaccess.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -421,21 +421,17 @@ func (a *sqliteDBAccess) Delete(ctx context.Context, req *state.DeleteRequest) e
421421
}
422422

423423
func (a *sqliteDBAccess) DeleteWithPrefix(ctx context.Context, req state.DeleteWithPrefixRequest) (state.DeleteWithPrefixResponse, error) {
424-
if req.Prefix == "" {
425-
return state.DeleteWithPrefixResponse{}, fmt.Errorf("missing prefix in delete with prefix operation")
426-
}
427-
ctx, cancel := context.WithTimeout(ctx, a.metadata.Timeout)
428-
defer cancel()
429-
430424
err := req.Validate()
431425
if err != nil {
432426
return state.DeleteWithPrefixResponse{}, err
433427
}
434428

429+
ctx, cancel := context.WithTimeout(ctx, a.metadata.Timeout)
430+
defer cancel()
431+
435432
// Concatenation is required for table name because sql.DB does not substitute parameters for table names.
436433
//nolint:gosec
437-
result, err := a.db.ExecContext(ctx, "DELETE FROM "+a.metadata.TableName+" WHERE prefix = ?",
438-
req.Prefix)
434+
result, err := a.db.ExecContext(ctx, "DELETE FROM "+a.metadata.TableName+" WHERE prefix = ?", req.Prefix)
439435
if err != nil {
440436
return state.DeleteWithPrefixResponse{}, err
441437
}

state/store.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import (
2121
"github.com/dapr/components-contrib/metadata"
2222
)
2323

24+
// ErrPingNotImplemented is returned by Ping if the state store does not implement the Pinger interface
25+
var ErrPingNotImplemented = errors.New("ping is not implemented by this state store")
26+
2427
// Store is an interface to perform operations on store.
2528
type Store interface {
2629
metadata.ComponentWithMetadata
@@ -58,11 +61,11 @@ func Ping(ctx context.Context, store Store) error {
5861
if storeWithPing, ok := store.(health.Pinger); ok {
5962
return storeWithPing.Ping(ctx)
6063
} else {
61-
return errors.New("ping is not implemented by this state store")
64+
return ErrPingNotImplemented
6265
}
6366
}
6467

65-
// DeleteWithPrefix is an interface to delete objects with a prefix.
68+
// DeleteWithPrefix is an optional interface to delete objects with a prefix.
6669
type DeleteWithPrefix interface {
6770
DeleteWithPrefix(ctx context.Context, req DeleteWithPrefixRequest) (DeleteWithPrefixResponse, error)
6871
}

tests/config/state/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Supported operations: transaction, etag, first-write, query, ttl
1+
# Supported operations: transaction, etag, first-write, query, ttl, delete-with-prefix
22
# Supported config:
33
# - badEtag: string containing a value for the bad etag, for exaple if the component uses numeric etags (default: "bad-etag")
44
componentType: state
@@ -55,7 +55,7 @@ components:
5555
# This component requires etags to be UUIDs
5656
badEtag: "e9b9e142-74b1-4a2e-8e90-3f4ffeea2e70"
5757
- component: sqlite
58-
operations: [ "transaction", "etag", "first-write", "ttl" ]
58+
operations: [ "transaction", "etag", "first-write", "ttl", "delete-with-prefix" ]
5959
- component: mysql.mysql
6060
operations: [ "transaction", "etag", "first-write", "ttl" ]
6161
- component: mysql.mariadb

tests/conformance/state/state.go

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
337337
// so will only assert require.NoError(t, err) finally, i.e. when current implementation
338338
// implements ping in existing stable components
339339
if err != nil {
340-
require.EqualError(t, err, "ping is not implemented by this state store")
340+
require.ErrorIs(t, err, state.ErrPingNotImplemented)
341341
} else {
342342
require.NoError(t, err)
343343
}
@@ -575,7 +575,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
575575
}
576576

577577
transactionStore, ok := statestore.(state.TransactionalStore)
578-
assert.True(t, ok)
578+
require.True(t, ok)
579579
sort.Ints(transactionGroups)
580580
for _, transactionGroup := range transactionGroups {
581581
t.Logf("Testing transaction #%d", transactionGroup)
@@ -704,7 +704,12 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
704704
}
705705
})
706706
} else {
707-
t.Run("transactional feature not present", func(t *testing.T) {
707+
t.Run("component does not implement TransactionalStore interface", func(t *testing.T) {
708+
_, ok := statestore.(state.TransactionalStore)
709+
require.False(t, ok)
710+
})
711+
712+
t.Run("Transactional feature not present", func(t *testing.T) {
708713
features := statestore.Features()
709714
assert.False(t, state.FeatureTransactional.IsPresent(features))
710715
})
@@ -1302,6 +1307,109 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
13021307
})
13031308
})
13041309
}
1310+
1311+
if config.HasOperation("delete-with-prefix") {
1312+
keys := map[string]bool{
1313+
"prefix||key1": true,
1314+
"prefix||key2": true,
1315+
"prefix||prefix2||key3": true,
1316+
"other-prefix||key1": true,
1317+
"no-prefix": true,
1318+
}
1319+
validateFn := func() func(t *testing.T) {
1320+
return func(t *testing.T) {
1321+
for key, exists := range keys {
1322+
res, err := statestore.Get(context.Background(), &state.GetRequest{Key: key})
1323+
require.NoErrorf(t, err, "Error retrieving key '%s'", key)
1324+
if exists {
1325+
require.NotEmptyf(t, res.Data, "Expected key '%s' to be not empty", key)
1326+
} else {
1327+
require.Emptyf(t, res.Data, "Expected key '%s' to be empty, but contained data: %s", key, string(res.Data))
1328+
}
1329+
}
1330+
}
1331+
}
1332+
1333+
var statestoreDeleteWithPrefix state.DeleteWithPrefix
1334+
t.Run("component implements DeleteWithPrefix interface", func(t *testing.T) {
1335+
var ok bool
1336+
statestoreDeleteWithPrefix, ok = statestore.(state.DeleteWithPrefix)
1337+
require.True(t, ok)
1338+
})
1339+
1340+
t.Run("DeleteWithPrefix feature present", func(t *testing.T) {
1341+
features := statestore.Features()
1342+
require.True(t, state.FeatureDeleteWithPrefix.IsPresent(features))
1343+
})
1344+
1345+
t.Run("set test data", func(t *testing.T) {
1346+
err := statestore.BulkSet(context.Background(), []state.SetRequest{
1347+
{Key: "prefix||key1", Value: []byte("Ovid, Metamorphoseon")},
1348+
{Key: "prefix||key2", Value: []byte("In nova fert animus mutatas dicere formas")},
1349+
{Key: "prefix||prefix2||key3", Value: []byte("corpora; di, coeptis (nam vos mutastis et illas)")},
1350+
{Key: "other-prefix||key1", Value: []byte("adspirate meis primaque ab origine mundi")}, // Note this still has "prefix||" but not at the start of the string
1351+
{Key: "no-prefix", Value: []byte("ad mea perpetuum deducite tempora carmen.")},
1352+
}, state.BulkStoreOpts{})
1353+
require.NoError(t, err)
1354+
1355+
t.Run("all keys are set", validateFn())
1356+
})
1357+
1358+
require.False(t, t.Failed(), "Cannot continue if previous test failed")
1359+
1360+
t.Run("delete with prefix", func(t *testing.T) {
1361+
res, err := statestoreDeleteWithPrefix.DeleteWithPrefix(context.Background(), state.DeleteWithPrefixRequest{
1362+
// Does not delete "prefix||prefix2||key3"
1363+
Prefix: "prefix||",
1364+
})
1365+
require.NoError(t, err)
1366+
assert.Equal(t, int64(2), res.Count)
1367+
1368+
keys["prefix||key1"] = false
1369+
keys["prefix||key2"] = false
1370+
1371+
t.Run("validate keys present", validateFn())
1372+
})
1373+
1374+
t.Run("delete with prefix appends ||", func(t *testing.T) {
1375+
res, err := statestoreDeleteWithPrefix.DeleteWithPrefix(context.Background(), state.DeleteWithPrefixRequest{
1376+
// Appends || automatically
1377+
Prefix: "other-prefix",
1378+
})
1379+
require.NoError(t, err)
1380+
assert.Equal(t, int64(1), res.Count)
1381+
1382+
keys["other-prefix||key1"] = false
1383+
1384+
t.Run("validate keys present", validateFn())
1385+
})
1386+
1387+
t.Run("error when prefix is empty", func(t *testing.T) {
1388+
_, err := statestoreDeleteWithPrefix.DeleteWithPrefix(context.Background(), state.DeleteWithPrefixRequest{
1389+
Prefix: "",
1390+
})
1391+
require.Error(t, err)
1392+
require.ErrorContains(t, err, "prefix is required")
1393+
})
1394+
1395+
t.Run("error when prefix is ||", func(t *testing.T) {
1396+
_, err := statestoreDeleteWithPrefix.DeleteWithPrefix(context.Background(), state.DeleteWithPrefixRequest{
1397+
Prefix: "||",
1398+
})
1399+
require.Error(t, err)
1400+
require.ErrorContains(t, err, "prefix is required")
1401+
})
1402+
} else {
1403+
t.Run("component does not implement DeleteWithPrefix interface", func(t *testing.T) {
1404+
_, ok := statestore.(state.DeleteWithPrefix)
1405+
require.False(t, ok)
1406+
})
1407+
1408+
t.Run("DeleteWithPrefix feature not present", func(t *testing.T) {
1409+
features := statestore.Features()
1410+
require.False(t, state.FeatureDeleteWithPrefix.IsPresent(features))
1411+
})
1412+
}
13051413
}
13061414

13071415
func assertEquals(t *testing.T, value any, res *state.GetResponse) {

0 commit comments

Comments
 (0)