Skip to content

Commit 7b7f9d6

Browse files
authored
Add SetArgs command (#1662)
* Add SetWithArgs command * Add tests for SetWithArgs command * Replace Makefile stable version by 6.2-rc3 version * Increase threshold because there are more commands * Reduce the SetWithArgs command doc comment * Rename SetWithArgs to SetArgs * Rename ExpireAt to TTL * Add KeepTTL field * Add ExpireAt field as time.Time type * Improve comments readability * Add more tests for ExpireAt field * Fix typo * Fix multiple if/else chain lint error
1 parent 9467d56 commit 7b7f9d6

File tree

3 files changed

+292
-3
lines changed

3 files changed

+292
-3
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ bench: testdeps
1515

1616
testdata/redis:
1717
mkdir -p $@
18-
wget -qO- http://download.redis.io/redis-stable.tar.gz | tar xvz --strip-components=1 -C $@
18+
wget -qO- https://download.redis.io/releases/redis-6.2-rc3.tar.gz | tar xvz --strip-components=1 -C $@
1919

2020
testdata/redis/src/redis-server: testdata/redis
2121
cd $< && make all

commands.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,55 @@ func (c cmdable) Set(ctx context.Context, key string, value interface{}, expirat
788788
return cmd
789789
}
790790

791+
// SetArgs provides arguments for the SetArgs function.
792+
type SetArgs struct {
793+
// Mode can be `NX` or `XX` or empty.
794+
Mode string
795+
796+
// Zero `TTL` or `Expiration` means that the key has no expiration time.
797+
TTL time.Duration
798+
ExpireAt time.Time
799+
800+
// When Get is true, the command returns the old value stored at key, or nil when key did not exist.
801+
Get bool
802+
803+
// KeepTTL is a Redis KEEPTTL option to keep existing TTL.
804+
KeepTTL bool
805+
}
806+
807+
// SetArgs supports all the options that the SET command supports.
808+
// It is the alternative to the Set function when you want
809+
// to have more control over the options.
810+
func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a *SetArgs) *StatusCmd {
811+
args := []interface{}{"set", key, value}
812+
813+
if a.KeepTTL {
814+
args = append(args, "keepttl")
815+
}
816+
817+
if !a.ExpireAt.IsZero() && !a.KeepTTL {
818+
args = append(args, "exat", a.ExpireAt.Unix())
819+
} else if a.TTL > 0 && !a.KeepTTL {
820+
if usePrecise(a.TTL) {
821+
args = append(args, "px", formatMs(ctx, a.TTL))
822+
} else {
823+
args = append(args, "ex", formatSec(ctx, a.TTL))
824+
}
825+
}
826+
827+
if a.Mode != "" {
828+
args = append(args, a.Mode)
829+
}
830+
831+
if a.Get {
832+
args = append(args, "get")
833+
}
834+
835+
cmd := NewStatusCmd(ctx, args...)
836+
_ = c(ctx, cmd)
837+
return cmd
838+
}
839+
791840
// Redis `SETEX key expiration value` command.
792841
func (c cmdable) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
793842
cmd := NewStatusCmd(ctx, "setex", key, formatSec(ctx, expiration), value)

commands_test.go

Lines changed: 242 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ var _ = Describe("Commands", func() {
247247
It("should Command", func() {
248248
cmds, err := client.Command(ctx).Result()
249249
Expect(err).NotTo(HaveOccurred())
250-
Expect(len(cmds)).To(BeNumerically("~", 200, 20))
250+
Expect(len(cmds)).To(BeNumerically("~", 200, 25))
251251

252252
cmd := cmds["mget"]
253253
Expect(cmd.Name).To(Equal("mget"))
@@ -1160,6 +1160,246 @@ var _ = Describe("Commands", func() {
11601160
Expect(mSetNX.Val()).To(Equal(false))
11611161
})
11621162

1163+
It("should SetWithArgs with TTL", func() {
1164+
args := &redis.SetArgs{
1165+
TTL: 500 * time.Millisecond,
1166+
}
1167+
err := client.SetArgs(ctx, "key", "hello", args).Err()
1168+
Expect(err).NotTo(HaveOccurred())
1169+
1170+
val, err := client.Get(ctx, "key").Result()
1171+
Expect(err).NotTo(HaveOccurred())
1172+
Expect(val).To(Equal("hello"))
1173+
1174+
Eventually(func() error {
1175+
return client.Get(ctx, "key").Err()
1176+
}, "2s", "100ms").Should(Equal(redis.Nil))
1177+
})
1178+
1179+
It("should SetWithArgs with expiration date", func() {
1180+
expireAt := time.Now().AddDate(1, 1, 1)
1181+
args := &redis.SetArgs{
1182+
ExpireAt: expireAt,
1183+
}
1184+
err := client.SetArgs(ctx, "key", "hello", args).Err()
1185+
Expect(err).NotTo(HaveOccurred())
1186+
1187+
val, err := client.Get(ctx, "key").Result()
1188+
Expect(err).NotTo(HaveOccurred())
1189+
Expect(val).To(Equal("hello"))
1190+
1191+
// check the key has an expiration date
1192+
// (so a TTL value different of -1)
1193+
ttl := client.TTL(ctx, "key")
1194+
Expect(ttl.Err()).NotTo(HaveOccurred())
1195+
Expect(ttl.Val()).ToNot(Equal(-1))
1196+
})
1197+
1198+
It("should SetWithArgs with negative expiration date", func() {
1199+
args := &redis.SetArgs{
1200+
ExpireAt: time.Now().AddDate(-3, 1, 1),
1201+
}
1202+
// redis accepts a timestamp less than the current date
1203+
// but returns nil when trying to get the key
1204+
err := client.SetArgs(ctx, "key", "hello", args).Err()
1205+
Expect(err).NotTo(HaveOccurred())
1206+
1207+
val, err := client.Get(ctx, "key").Result()
1208+
Expect(err).To(Equal(redis.Nil))
1209+
Expect(val).To(Equal(""))
1210+
})
1211+
1212+
It("should SetWithArgs with keepttl", func() {
1213+
// Set with ttl
1214+
argsWithTTL := &redis.SetArgs{
1215+
TTL: 5 * time.Second,
1216+
}
1217+
set := client.SetArgs(ctx, "key", "hello", argsWithTTL)
1218+
Expect(set.Err()).NotTo(HaveOccurred())
1219+
Expect(set.Result()).To(Equal("OK"))
1220+
1221+
// Set with keepttl
1222+
argsWithKeepTTL := &redis.SetArgs{
1223+
KeepTTL: true,
1224+
}
1225+
set = client.SetArgs(ctx, "key", "hello", argsWithKeepTTL)
1226+
Expect(set.Err()).NotTo(HaveOccurred())
1227+
Expect(set.Result()).To(Equal("OK"))
1228+
1229+
ttl := client.TTL(ctx, "key")
1230+
Expect(ttl.Err()).NotTo(HaveOccurred())
1231+
// set keepttl will Retain the ttl associated with the key
1232+
Expect(ttl.Val().Nanoseconds()).NotTo(Equal(-1))
1233+
})
1234+
1235+
It("should SetWithArgs with NX mode and key exists", func() {
1236+
err := client.Set(ctx, "key", "hello", 0).Err()
1237+
Expect(err).NotTo(HaveOccurred())
1238+
1239+
args := &redis.SetArgs{
1240+
Mode: "nx",
1241+
}
1242+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1243+
Expect(err).To(Equal(redis.Nil))
1244+
Expect(val).To(Equal(""))
1245+
})
1246+
1247+
It("should SetWithArgs with NX mode and key does not exist", func() {
1248+
args := &redis.SetArgs{
1249+
Mode: "nx",
1250+
}
1251+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1252+
Expect(err).NotTo(HaveOccurred())
1253+
Expect(val).To(Equal("OK"))
1254+
})
1255+
1256+
It("should SetWithArgs with NX mode and GET option", func() {
1257+
args := &redis.SetArgs{
1258+
Mode: "nx",
1259+
Get: true,
1260+
}
1261+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1262+
Expect(err).To(Equal(proto.RedisError("ERR syntax error")))
1263+
Expect(val).To(Equal(""))
1264+
})
1265+
1266+
It("should SetWithArgs with expiration, NX mode, and key does not exist", func() {
1267+
args := &redis.SetArgs{
1268+
TTL: 500 * time.Millisecond,
1269+
Mode: "nx",
1270+
}
1271+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1272+
Expect(err).NotTo(HaveOccurred())
1273+
Expect(val).To(Equal("OK"))
1274+
1275+
Eventually(func() error {
1276+
return client.Get(ctx, "key").Err()
1277+
}, "1s", "100ms").Should(Equal(redis.Nil))
1278+
})
1279+
1280+
It("should SetWithArgs with expiration, NX mode, and key exists", func() {
1281+
e := client.Set(ctx, "key", "hello", 0)
1282+
Expect(e.Err()).NotTo(HaveOccurred())
1283+
1284+
args := &redis.SetArgs{
1285+
TTL: 500 * time.Millisecond,
1286+
Mode: "nx",
1287+
}
1288+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1289+
Expect(err).To(Equal(redis.Nil))
1290+
Expect(val).To(Equal(""))
1291+
})
1292+
1293+
It("should SetWithArgs with expiration, NX mode, and GET option", func() {
1294+
args := &redis.SetArgs{
1295+
TTL: 500 * time.Millisecond,
1296+
Mode: "nx",
1297+
Get: true,
1298+
}
1299+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1300+
Expect(err).To(Equal(proto.RedisError("ERR syntax error")))
1301+
Expect(val).To(Equal(""))
1302+
})
1303+
1304+
It("should SetWithArgs with XX mode and key does not exist", func() {
1305+
args := &redis.SetArgs{
1306+
Mode: "xx",
1307+
}
1308+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1309+
Expect(err).To(Equal(redis.Nil))
1310+
Expect(val).To(Equal(""))
1311+
})
1312+
1313+
It("should SetWithArgs with XX mode and key exists", func() {
1314+
e := client.Set(ctx, "key", "hello", 0).Err()
1315+
Expect(e).NotTo(HaveOccurred())
1316+
1317+
args := &redis.SetArgs{
1318+
Mode: "xx",
1319+
}
1320+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1321+
Expect(err).NotTo(HaveOccurred())
1322+
Expect(val).To(Equal("OK"))
1323+
})
1324+
1325+
It("should SetWithArgs with XX mode and GET option, and key exists", func() {
1326+
e := client.Set(ctx, "key", "hello", 0).Err()
1327+
Expect(e).NotTo(HaveOccurred())
1328+
1329+
args := &redis.SetArgs{
1330+
Mode: "xx",
1331+
Get: true,
1332+
}
1333+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1334+
Expect(err).NotTo(HaveOccurred())
1335+
Expect(val).To(Equal("hello"))
1336+
})
1337+
1338+
It("should SetWithArgs with XX mode and GET option, and key does not exist", func() {
1339+
args := &redis.SetArgs{
1340+
Mode: "xx",
1341+
Get: true,
1342+
}
1343+
1344+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1345+
Expect(err).To(Equal(redis.Nil))
1346+
Expect(val).To(Equal(""))
1347+
})
1348+
1349+
It("should SetWithArgs with expiration, XX mode, GET option, and key does not exist", func() {
1350+
args := &redis.SetArgs{
1351+
TTL: 500 * time.Millisecond,
1352+
Mode: "xx",
1353+
Get: true,
1354+
}
1355+
1356+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1357+
Expect(err).To(Equal(redis.Nil))
1358+
Expect(val).To(Equal(""))
1359+
})
1360+
1361+
It("should SetWithArgs with expiration, XX mode, GET option, and key exists", func() {
1362+
e := client.Set(ctx, "key", "hello", 0)
1363+
Expect(e.Err()).NotTo(HaveOccurred())
1364+
1365+
args := &redis.SetArgs{
1366+
TTL: 500 * time.Millisecond,
1367+
Mode: "xx",
1368+
Get: true,
1369+
}
1370+
1371+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1372+
Expect(err).NotTo(HaveOccurred())
1373+
Expect(val).To(Equal("hello"))
1374+
1375+
Eventually(func() error {
1376+
return client.Get(ctx, "key").Err()
1377+
}, "1s", "100ms").Should(Equal(redis.Nil))
1378+
})
1379+
1380+
It("should SetWithArgs with Get and key does not exist yet", func() {
1381+
args := &redis.SetArgs{
1382+
Get: true,
1383+
}
1384+
1385+
val, err := client.SetArgs(ctx, "key", "hello", args).Result()
1386+
Expect(err).To(Equal(redis.Nil))
1387+
Expect(val).To(Equal(""))
1388+
})
1389+
1390+
It("should SetWithArgs with Get and key exists", func() {
1391+
e := client.Set(ctx, "key", "hello", 0)
1392+
Expect(e.Err()).NotTo(HaveOccurred())
1393+
1394+
args := &redis.SetArgs{
1395+
Get: true,
1396+
}
1397+
1398+
val, err := client.SetArgs(ctx, "key", "world", args).Result()
1399+
Expect(err).NotTo(HaveOccurred())
1400+
Expect(val).To(Equal("hello"))
1401+
})
1402+
11631403
It("should Set with expiration", func() {
11641404
err := client.Set(ctx, "key", "hello", 100*time.Millisecond).Err()
11651405
Expect(err).NotTo(HaveOccurred())
@@ -1169,7 +1409,7 @@ var _ = Describe("Commands", func() {
11691409
Expect(val).To(Equal("hello"))
11701410

11711411
Eventually(func() error {
1172-
return client.Get(ctx, "foo").Err()
1412+
return client.Get(ctx, "key").Err()
11731413
}, "1s", "100ms").Should(Equal(redis.Nil))
11741414
})
11751415

0 commit comments

Comments
 (0)