Skip to content

Commit 9dc61cf

Browse files
Merge branch 'unstable' into unstable
2 parents 14fd750 + 74084cc commit 9dc61cf

File tree

8 files changed

+284
-4
lines changed

8 files changed

+284
-4
lines changed

src/cluster/slot_migrate.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ Status SlotMigrator::migrateComplexKey(const rocksdb::Slice &key, const Metadata
790790
if (metadata.Type() > RedisTypeNames.size()) {
791791
return {Status::NotOK, "unknown key type: " + std::to_string(metadata.Type())};
792792
}
793-
return {Status::NotOK, "unsupported complex key type: " + RedisTypeNames[metadata.Type()]};
793+
return {Status::NotOK, "unsupported complex key type: " + metadata.TypeName()};
794794
}
795795
}
796796

src/commands/cmd_key.cc

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,32 @@ class CommandSort : public Commander {
581581
SortArgument sort_argument_;
582582
};
583583

584+
class CommandKMetadata : public Commander {
585+
public:
586+
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
587+
redis::Database redis(srv->storage, conn->GetNamespace());
588+
std::string &key = args_[1];
589+
std::string nskey = redis.AppendNamespacePrefix(key);
590+
591+
Metadata metadata(kRedisNone, false);
592+
auto s = redis.GetMetadata(ctx, RedisTypes::All(), nskey, &metadata);
593+
if (!s.ok()) return {Status::RedisExecErr, s.ToString()};
594+
595+
if (metadata.IsSingleKVType()) {
596+
*output = conn->Map({{redis::BulkString("type"), redis::BulkString(metadata.TypeName())},
597+
{redis::BulkString("expire"), redis::Integer(metadata.expire)},
598+
{redis::BulkString("flags"), redis::Integer(metadata.flags)}});
599+
} else {
600+
*output = conn->Map({{redis::BulkString("type"), redis::BulkString(metadata.TypeName())},
601+
{redis::BulkString("size"), redis::Integer(metadata.size)},
602+
{redis::BulkString("expire"), redis::Integer(metadata.expire)},
603+
{redis::BulkString("flags"), redis::Integer(metadata.flags)},
604+
{redis::BulkString("version"), redis::Integer(metadata.version)}});
605+
}
606+
return Status::OK();
607+
}
608+
};
609+
584610
REDIS_REGISTER_COMMANDS(Key, MakeCmdAttr<CommandTTL>("ttl", 2, "read-only", 1, 1, 1),
585611
MakeCmdAttr<CommandPTTL>("pttl", 2, "read-only", 1, 1, 1),
586612
MakeCmdAttr<CommandType>("type", 2, "read-only", 1, 1, 1),
@@ -602,6 +628,7 @@ REDIS_REGISTER_COMMANDS(Key, MakeCmdAttr<CommandTTL>("ttl", 2, "read-only", 1, 1
602628
MakeCmdAttr<CommandRenameNX>("renamenx", 3, "write", 1, 2, 1),
603629
MakeCmdAttr<CommandCopy>("copy", -3, "write", 1, 2, 1),
604630
MakeCmdAttr<CommandSort<false>>("sort", -2, "write slow", 1, 1, 1),
605-
MakeCmdAttr<CommandSort<true>>("sort_ro", -2, "read-only slow", 1, 1, 1))
631+
MakeCmdAttr<CommandSort<true>>("sort_ro", -2, "read-only slow", 1, 1, 1),
632+
MakeCmdAttr<CommandKMetadata>("kmetadata", 2, "read-only", 1, 1, 1))
606633

607634
} // namespace redis

src/commands/cmd_search.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ class CommandFTInfo : public Commander {
436436
output->append(redis::SimpleString("index_definition"));
437437
output->append(redis::MultiLen(4));
438438
output->append(redis::SimpleString("key_type"));
439-
output->append(redis::BulkString(RedisTypeNames[(size_t)info->metadata.on_data_type]));
439+
output->append(redis::BulkString(info->metadata.OnDataTypeName()));
440440
output->append(redis::SimpleString("prefixes"));
441441
output->append(redis::ArrayOfBulkStrings(info->prefixes.prefixes));
442442

src/search/search_encoding.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class IndexMetadata {
4040
uint8_t flag = 0; // all reserved
4141
IndexOnDataType on_data_type;
4242

43+
const std::string &OnDataTypeName() const { return RedisTypeNames[(size_t)on_data_type]; }
44+
4345
void Encode(std::string *dst) const {
4446
PutFixed8(dst, flag);
4547
PutFixed8(dst, uint8_t(on_data_type));

src/storage/redis_db.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ rocksdb::Status Database::Dump(engine::Context &ctx, const Slice &user_key, std:
488488
infos->emplace_back("namespace");
489489
infos->emplace_back(namespace_);
490490
infos->emplace_back("type");
491-
infos->emplace_back(RedisTypeNames[metadata.Type()]);
491+
infos->emplace_back(metadata.TypeName());
492492
infos->emplace_back("version");
493493
infos->emplace_back(std::to_string(metadata.version));
494494
infos->emplace_back("expire");

src/storage/redis_metadata.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ bool Metadata::operator==(const Metadata &that) const {
222222

223223
RedisType Metadata::Type() const { return static_cast<RedisType>(flags & METADATA_TYPE_MASK); }
224224

225+
const std::string &Metadata::TypeName() const { return RedisTypeNames[Type()]; }
226+
225227
size_t Metadata::GetOffsetAfterExpire(uint8_t flags) {
226228
if (flags & METADATA_64BIT_ENCODING_MASK) {
227229
return 1 + 8;

src/storage/redis_metadata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class Metadata {
176176
void PutExpire(std::string *dst) const;
177177

178178
RedisType Type() const;
179+
const std::string &TypeName() const;
179180
size_t CommonEncodedSize() const;
180181
int64_t TTL() const;
181182
timeval Time() const;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package kmetadata
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"testing"
26+
27+
"github.com/redis/go-redis/v9"
28+
"github.com/stretchr/testify/assert"
29+
"github.com/stretchr/testify/require"
30+
31+
"github.com/apache/kvrocks/tests/gocase/util"
32+
)
33+
34+
type kMetadataResponse struct {
35+
expire int64 `redis:"expire"`
36+
size int64 `redis:"size"`
37+
ktype string `redis:"type"`
38+
flags int64 `redis:"flags"`
39+
version int64 `redis:"version"`
40+
}
41+
42+
func toInt64(val interface{}) (int64, error) {
43+
switch v := val.(type) {
44+
case int64:
45+
return v, nil
46+
case int:
47+
return int64(v), nil
48+
case float64:
49+
return int64(v), nil
50+
default:
51+
return 0, fmt.Errorf("value is not a number, got %T", val)
52+
}
53+
}
54+
55+
func ExtractKMetadataResponse(result interface{}) (*kMetadataResponse, error) {
56+
resultMap, ok := result.(map[interface{}]interface{})
57+
if !ok {
58+
return nil, fmt.Errorf("expected map[interface{}]interface{}, got %T", result)
59+
}
60+
61+
response := &kMetadataResponse{}
62+
63+
// Convert numeric fields
64+
for field, target := range map[string]*int64{
65+
"expire": &response.expire,
66+
"size": &response.size,
67+
"flags": &response.flags,
68+
"version": &response.version,
69+
} {
70+
if val, ok := resultMap[field]; ok {
71+
converted, err := toInt64(val)
72+
if err != nil {
73+
return nil, fmt.Errorf("%s: %v", field, err)
74+
}
75+
*target = converted
76+
}
77+
}
78+
79+
// Extract Type field
80+
if val, ok := resultMap["type"]; ok {
81+
if strVal, ok := val.(string); ok {
82+
response.ktype = strVal
83+
} else {
84+
return nil, fmt.Errorf("type is not a string, got %T", val)
85+
}
86+
}
87+
88+
return response, nil
89+
}
90+
91+
func TestKMetadata(t *testing.T) {
92+
configOptions := []util.ConfigOptions{
93+
{
94+
Name: "resp3-enabled",
95+
Options: []string{"yes"},
96+
ConfigType: util.YesNo,
97+
},
98+
}
99+
configsMatrix, err := util.GenerateConfigsMatrix(configOptions)
100+
require.NoError(t, err)
101+
for _, configs := range configsMatrix {
102+
testKMetadata(t, configs)
103+
}
104+
}
105+
106+
var testKMetadata = func(t *testing.T, configs util.KvrocksServerConfigs) {
107+
srv := util.StartServer(t, configs)
108+
defer srv.Close()
109+
ctx := context.Background()
110+
rdb := srv.NewClient()
111+
defer func() { require.NoError(t, rdb.Close()) }()
112+
113+
t.Run("Test KMetadata for String type", func(t *testing.T) {
114+
key := "__avoid_collisions__" + "_KMetadataString_" + util.RandString(1, 10, util.Alpha)
115+
val := "__avoid_collisions__" + "_KMetadataString_" + util.RandString(1, 10, util.Alpha)
116+
rdb.Set(ctx, key, val, 0)
117+
r := rdb.Do(ctx, "kmetadata", key)
118+
result, err := r.Result()
119+
if err != nil {
120+
t.Fatalf("Command failed: %v", err)
121+
}
122+
metaResponse, err := ExtractKMetadataResponse(result)
123+
if err != nil {
124+
t.Fatalf("Failed to extract response: %v", err)
125+
}
126+
assert.Equal(t, "string", metaResponse.ktype)
127+
assert.Equal(t, int64(0), metaResponse.version)
128+
assert.Equal(t, int64(0), metaResponse.size)
129+
})
130+
131+
t.Run("Test KMetadata for hash type", func(t *testing.T) {
132+
key := "__avoid_collisions__" + "_kMetadataHash_" + util.RandString(1, 10, util.Alpha)
133+
f1 := "__avoid_collisions__" + "_kMetadataHash_" + util.RandString(1, 10, util.Alpha)
134+
v1 := "__avoid_collisions__" + "_kMetadataHash_" + util.RandString(1, 10, util.Alpha)
135+
f2 := "__avoid_collisions__" + "_kMetadataHash_" + util.RandString(1, 10, util.Alpha)
136+
v2 := "__avoid_collisions__" + "_kMetadataHash_" + util.RandString(1, 10, util.Alpha)
137+
rdb.HSet(ctx, key, f1, v1, f2, v2)
138+
r := rdb.Do(ctx, "kmetadata", key)
139+
result, err := r.Result()
140+
if err != nil {
141+
t.Fatalf("Command failed: %v", err)
142+
}
143+
metaResponse, err := ExtractKMetadataResponse(result)
144+
if err != nil {
145+
t.Fatalf("Failed to extract response: %v", err)
146+
}
147+
assert.Equal(t, "hash", metaResponse.ktype)
148+
assert.NotEqual(t, int64(0), metaResponse.version)
149+
assert.Equal(t, int64(2), metaResponse.size)
150+
})
151+
152+
t.Run("Test KMetadata for set type", func(t *testing.T) {
153+
setName := "__avoid_collisions__" + "_kMetadataSet_" + util.RandString(1, 10, util.Alpha)
154+
item1 := "__avoid_collisions__" + "_kMetadataSet_" + util.RandString(1, 10, util.Alpha)
155+
item2 := "__avoid_collisions__" + "_kMetadataSet_" + util.RandString(1, 10, util.Alpha)
156+
item3 := "__avoid_collisions__" + "_kMetadataSet_" + util.RandString(1, 10, util.Alpha)
157+
item4 := "__avoid_collisions__" + "_kMetadataSet_" + util.RandString(1, 10, util.Alpha)
158+
rdb.SAdd(ctx, setName, item1, item2, item3, item4)
159+
r := rdb.Do(ctx, "kmetadata", setName)
160+
result, err := r.Result()
161+
if err != nil {
162+
t.Fatalf("Command failed: %v", err)
163+
}
164+
metaResponse, err := ExtractKMetadataResponse(result)
165+
if err != nil {
166+
t.Fatalf("Failed to extract response: %v", err)
167+
}
168+
assert.Equal(t, "set", metaResponse.ktype)
169+
assert.NotEqual(t, int64(0), metaResponse.version)
170+
assert.Equal(t, int64(4), metaResponse.size)
171+
})
172+
173+
t.Run("Test KMetadata for zset type", func(t *testing.T) {
174+
zsetName := "__avoid_collisions__" + "_kMetadataZSet_" + util.RandString(1, 10, util.Alpha)
175+
members := []redis.Z{
176+
{
177+
Score: 1.0,
178+
Member: "__avoid_collisions__" + "_kMetadataZSet_" + util.RandString(1, 10, util.Alpha),
179+
},
180+
{
181+
Score: 2.0,
182+
Member: "__avoid_collisions__" + "_kMetadataZSet_" + util.RandString(1, 10, util.Alpha),
183+
},
184+
{
185+
Score: 3.0,
186+
Member: "__avoid_collisions__" + "_kMetadataZSet_" + util.RandString(1, 10, util.Alpha),
187+
},
188+
}
189+
rdb.ZAdd(ctx, zsetName, members...)
190+
r := rdb.Do(ctx, "kmetadata", zsetName)
191+
result, err := r.Result()
192+
if err != nil {
193+
t.Fatalf("Command failed: %v", err)
194+
}
195+
metaResponse, err := ExtractKMetadataResponse(result)
196+
if err != nil {
197+
t.Fatalf("Failed to extract response: %v", err)
198+
}
199+
assert.Equal(t, "zset", metaResponse.ktype)
200+
assert.NotEqual(t, int64(0), metaResponse.version)
201+
assert.Equal(t, int64(3), metaResponse.size)
202+
})
203+
204+
t.Run("Test KMetadata for Bitmap type", func(t *testing.T) {
205+
bitMapKey := "__avoid_collisions__" + "_kMetadataBitMap_" + util.RandString(1, 10, util.Alpha)
206+
rdb.SetBit(ctx, bitMapKey, 0, 1)
207+
r := rdb.Do(ctx, "kmetadata", bitMapKey)
208+
result, err := r.Result()
209+
if err != nil {
210+
t.Fatalf("Command failed: %v", err)
211+
}
212+
metaResponse, err := ExtractKMetadataResponse(result)
213+
if err != nil {
214+
t.Fatalf("Failed to extract response: %v", err)
215+
}
216+
assert.Equal(t, "bitmap", metaResponse.ktype)
217+
assert.NotEqual(t, int64(0), metaResponse.version)
218+
assert.Equal(t, int64(1), metaResponse.size)
219+
})
220+
221+
t.Run("Test KMetadata for List type", func(t *testing.T) {
222+
listKey := "__avoid_collisions__" + "_kMetadataList_" + util.RandString(1, 10, util.Alpha)
223+
item1 := "__avoid_collisions__" + "_kMetadataList_" + util.RandString(1, 10, util.Alpha)
224+
item2 := "__avoid_collisions__" + "_kMetadataList_" + util.RandString(1, 10, util.Alpha)
225+
rdb.RPush(ctx, listKey, item1, item2)
226+
r := rdb.Do(ctx, "kmetadata", listKey)
227+
result, err := r.Result()
228+
if err != nil {
229+
t.Fatalf("Command failed: %v", err)
230+
}
231+
metaResponse, err := ExtractKMetadataResponse(result)
232+
if err != nil {
233+
t.Fatalf("Failed to extract response: %v", err)
234+
}
235+
assert.Equal(t, "list", metaResponse.ktype)
236+
assert.NotEqual(t, int64(0), metaResponse.version)
237+
assert.Equal(t, int64(2), metaResponse.size)
238+
})
239+
240+
t.Run("Test Key not present", func(t *testing.T) {
241+
notFoundKey := "__avoid_collisions__" + "_kMetadataNotFound_" + util.RandString(1, 10, util.Alpha)
242+
r := rdb.Do(ctx, "kmetadata", notFoundKey)
243+
val := r.Val()
244+
assert.Equal(t, nil, val)
245+
assert.Error(t, r.Err())
246+
})
247+
248+
}

0 commit comments

Comments
 (0)