Skip to content

Commit c085d19

Browse files
authored
Merge pull request #38 from scoquelin/jl-keys-part1
2 parents a185105 + 64dae09 commit c085d19

File tree

4 files changed

+688
-10
lines changed

4 files changed

+688
-10
lines changed

modules/api/src/main/scala/com/github/scoquelin/arugula/commands/RedisKeyAsyncCommands.scala

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,181 @@ package com.github.scoquelin.arugula.commands
33
import scala.concurrent.Future
44
import scala.concurrent.duration.FiniteDuration
55

6+
import com.github.scoquelin.arugula.commands.RedisBaseAsyncCommands.{InitialCursor, ScanResults}
7+
8+
import java.time.Instant
9+
610
/**
711
* Asynchronous commands for manipulating/querying Keys
812
*
913
* @tparam K The key type
1014
* @tparam V The value type
1115
*/
1216
trait RedisKeyAsyncCommands[K, V] {
17+
18+
/**
19+
* Copy a key to another key
20+
* @param srcKey The key to copy
21+
* @param destKey The key to copy to
22+
* @return True if the key was copied, false otherwise
23+
*/
24+
def copy(srcKey: K, destKey: K): Future[Boolean]
25+
26+
/**
27+
* Copy a key to another key with additional arguments
28+
* @param srcKey The key to copy
29+
* @param destKey The key to copy to
30+
* @param args Additional arguments for the copy operation
31+
*/
32+
def copy(srcKey: K, destKey: K, args: RedisKeyAsyncCommands.CopyArgs): Future[Unit]
33+
34+
/**
35+
* Delete one or more keys
36+
* @param key The key(s) to delete
37+
* @return The number of keys that were removed
38+
*/
1339
def del(key: K*): Future[Long]
40+
41+
/**
42+
* Unlink one or more keys. (non-blocking version of DEL)
43+
* @param key The key(s) to unlink
44+
* @return The number of keys that were unlinked
45+
*/
46+
def unlink(key: K*): Future[Long]
47+
48+
/**
49+
* Serialize a key
50+
* @param key The key to serialize
51+
* @return The serialized value of the key
52+
*/
53+
def dump(key: K): Future[Array[Byte]]
54+
55+
/**
56+
* Determine if a key exists
57+
* @param key The key to check
58+
* @return True if the key exists, false otherwise
59+
*/
1460
def exists(key: K*): Future[Boolean]
61+
62+
/**
63+
* Set a key's time to live. The key will be automatically deleted after the timeout.
64+
* Implementations may round the timeout to the nearest second if necessary
65+
* but could set a more precise timeout if the underlying Redis client supports it.
66+
* @param key The key to set the expiration for
67+
* @param expiresIn The duration until the key expires
68+
* @return True if the timeout was set, false otherwise
69+
*/
1570
def expire(key: K, expiresIn: FiniteDuration): Future[Boolean]
71+
72+
/**
73+
* Set the expiration for a key as an Instant
74+
* @param key The key to set the expiration for
75+
* @param timestamp The point in time when the key should expire
76+
* @return True if the timeout was set, false otherwise
77+
*/
78+
def expireAt(key: K, timestamp: Instant): Future[Boolean]
79+
80+
/**
81+
* Get the time to live for a key as an Instant
82+
* @param key The key to get the expiration for
83+
* @return The time to live as a point in time, or None if the key does not exist or does not have an expiration
84+
*/
85+
def expireTime(key: K): Future[Option[Instant]]
86+
87+
/**
88+
* Find all keys matching the given pattern
89+
* To match all keys, use "*"
90+
* @param pattern The pattern to match
91+
* @return The keys that match the pattern
92+
*/
93+
def keys(pattern: K): Future[List[K]]
94+
95+
/**
96+
* Move a key to a different database
97+
* @param key The key to move
98+
* @param db The database to move the key to
99+
* @return True if the key was moved, false otherwise
100+
*/
101+
def move(key: K, db: Int): Future[Boolean]
102+
103+
/**
104+
* Rename a key
105+
* @param key The key to rename
106+
* @param newKey The new name for the key
107+
*/
108+
def rename(key: K, newKey: K): Future[Unit]
109+
110+
/**
111+
* Rename a key, but only if the new key does not already exist
112+
* @param key The key to rename
113+
* @param newKey The new name for the key
114+
* @return True if the key was renamed, false otherwise
115+
*/
116+
def renameNx(key: K, newKey: K): Future[Boolean]
117+
118+
/**
119+
* Restore a key from its serialized form
120+
* @param key The key to restore
121+
* @param serializedValue The serialized value of the key
122+
* @param args Additional arguments for the restore operation
123+
*/
124+
def restore(key: K, serializedValue: Array[Byte], args: RedisKeyAsyncCommands.RestoreArgs = RedisKeyAsyncCommands.RestoreArgs()): Future[Unit]
125+
126+
/**
127+
* Scan the keyspace
128+
* @param cursor The cursor to start scanning from
129+
* @param matchPattern An optional pattern to match keys against
130+
* @param limit An optional limit on the number of keys to return
131+
* @return The keys that were scanned
132+
*/
133+
def scan(cursor: String = InitialCursor, matchPattern: Option[String] = None, limit: Option[Int] = None): Future[ScanResults[List[K]]]
134+
135+
/**
136+
* Get the time to live for a key.
137+
* Implementations may return a more precise time to live if the underlying Redis client supports it.
138+
* Rather than expose the underlying Redis client's API, this method returns a FiniteDuration which can
139+
* be rounded to the nearest second if necessary.
140+
* @param key The key to get the expiration for
141+
* @return The time to live, or None if the key does not exist or does not have an expiration
142+
*/
16143
def ttl(key: K): Future[Option[FiniteDuration]]
144+
145+
/**
146+
* Alters the last access time of a key(s). A key is ignored if it does not exist.
147+
* @param key The key(s) to touch
148+
* @return The number of keys that were touched
149+
*/
150+
def touch(key: K*): Future[Long]
151+
152+
/**
153+
* Get the type of a key
154+
* @param key The key to get the type of
155+
* @return The type of the key
156+
*/
157+
def `type`(key: K): Future[String]
158+
}
159+
160+
object RedisKeyAsyncCommands {
161+
case class CopyArgs(replace: Boolean = false, destinationDb: Option[Int] = None)
162+
163+
case class RestoreArgs(
164+
replace: Boolean = false,
165+
idleTime: Option[FiniteDuration] = None,
166+
ttl: Option[FiniteDuration] = None,
167+
absTtl: Option[Instant] = None,
168+
frequency: Option[Long] = None,
169+
){
170+
def isEmpty: Boolean = !replace && idleTime.isEmpty && frequency.isEmpty && ttl.isEmpty && absTtl.isEmpty
171+
}
17172
}
173+
174+
// Commands to be Implemented:
175+
//migrate
176+
//objectEncoding
177+
//objectFreq
178+
//objectIdletime
179+
//objectRefcount
180+
//randomkey
181+
//sort
182+
//sortReadOnly
183+
//sortStore

modules/core/src/main/scala/com/github/scoquelin/arugula/commands/LettuceRedisKeyAsyncCommands.scala

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
package com.github.scoquelin.arugula.commands
22

3+
import scala.collection.convert.ImplicitConversions.`collection AsScalaIterable`
34
import scala.concurrent.Future
45
import scala.concurrent.duration.FiniteDuration
6+
7+
import com.github.scoquelin.arugula.commands.RedisBaseAsyncCommands.InitialCursor
58
import com.github.scoquelin.arugula.internal.LettuceRedisCommandDelegation
9+
import io.lettuce.core.{CopyArgs, ScanCursor}
610

11+
import java.time.Instant
712
import java.util.concurrent.TimeUnit
813

914
private[arugula] trait LettuceRedisKeyAsyncCommands[K, V] extends RedisKeyAsyncCommands[K, V] with LettuceRedisCommandDelegation[K, V] {
1015
import LettuceRedisKeyAsyncCommands.toFiniteDuration
1116

17+
override def copy(srcKey: K, destKey: K): Future[Boolean] =
18+
delegateRedisClusterCommandAndLift(_.copy(srcKey, destKey)).map(Boolean2boolean)
19+
20+
override def copy(srcKey: K, destKey: K, args: RedisKeyAsyncCommands.CopyArgs): Future[Unit] = {
21+
val copyArgs: CopyArgs = CopyArgs.Builder.replace(args.replace)
22+
args.destinationDb.foreach(copyArgs.destinationDb(_))
23+
delegateRedisClusterCommandAndLift(_.copy(srcKey, destKey, copyArgs)).map(_ => ())
24+
}
25+
1226
override def del(key: K*): Future[Long] =
1327
delegateRedisClusterCommandAndLift(_.del(key: _*)).map(Long2long)
1428

29+
override def unlink(key: K*): Future[Long] =
30+
delegateRedisClusterCommandAndLift(_.unlink(key: _*)).map(Long2long)
31+
32+
override def dump(key: K): Future[Array[Byte]] =
33+
delegateRedisClusterCommandAndLift(_.dump(key))
34+
1535
override def exists(key: K*): Future[Boolean] =
1636
delegateRedisClusterCommandAndLift(_.exists(key: _*)).map(_ == key.size.toLong)
1737

@@ -23,8 +43,74 @@ private[arugula] trait LettuceRedisKeyAsyncCommands[K, V] extends RedisKeyAsyncC
2343
delegateRedisClusterCommandAndLift(_.expire(key, expiresIn.toSeconds))
2444
}).map(Boolean2boolean)
2545

46+
47+
override def expireAt(key: K, timestamp: Instant): Future[Boolean] =
48+
delegateRedisClusterCommandAndLift(_.pexpireat(key, timestamp.toEpochMilli)).map(Boolean2boolean)
49+
50+
override def expireTime(key: K): Future[Option[Instant]] = {
51+
delegateRedisClusterCommandAndLift(_.pexpiretime(key)).map {
52+
case d if d < 0 => None
53+
case d => Some(Instant.ofEpochMilli(d))
54+
}
55+
}
56+
57+
override def keys(pattern: K): Future[List[K]] =
58+
delegateRedisClusterCommandAndLift(_.keys(pattern)).map(_.toList)
59+
60+
override def move(key: K, db: Int): Future[Boolean] =
61+
delegateRedisClusterCommandAndLift(_.move(key, db)).map(Boolean2boolean)
62+
63+
override def rename(key: K, newKey: K): Future[Unit] =
64+
delegateRedisClusterCommandAndLift(_.rename(key, newKey)).map(_ => ())
65+
66+
override def renameNx(key: K, newKey: K): Future[Boolean] =
67+
delegateRedisClusterCommandAndLift(_.renamenx(key, newKey)).map(Boolean2boolean)
68+
69+
override def restore(key: K, serializedValue: Array[Byte], args: RedisKeyAsyncCommands.RestoreArgs = RedisKeyAsyncCommands.RestoreArgs()): Future[Unit] = {
70+
val restoreArgs = new io.lettuce.core.RestoreArgs()
71+
args.ttl.foreach { duration =>
72+
restoreArgs.ttl(duration.toMillis)
73+
}
74+
args.idleTime.foreach { duration =>
75+
restoreArgs.idleTime(duration.toMillis)
76+
}
77+
args.frequency.foreach { frequency =>
78+
restoreArgs.frequency(frequency)
79+
}
80+
if(args.replace) restoreArgs.replace()
81+
args.absTtl.foreach{ instant =>
82+
restoreArgs.absttl(true)
83+
restoreArgs.ttl(instant.toEpochMilli)
84+
}
85+
delegateRedisClusterCommandAndLift(_.restore(key, serializedValue, restoreArgs)).map(_ => ())
86+
}
87+
88+
override def scan(cursor: String = InitialCursor, matchPattern: Option[String] = None, limit: Option[Int] = None): Future[RedisBaseAsyncCommands.ScanResults[List[K]]] = {
89+
val scanArgs = (matchPattern, limit) match {
90+
case (Some(pattern), Some(count)) => Some(io.lettuce.core.ScanArgs.Builder.matches(pattern).limit(count))
91+
case (Some(pattern), None) => Some(io.lettuce.core.ScanArgs.Builder.matches(pattern))
92+
case (None, Some(count)) => Some(io.lettuce.core.ScanArgs.Builder.limit(count))
93+
case _ => None
94+
}
95+
val result = scanArgs match {
96+
case Some(args) => delegateRedisClusterCommandAndLift(_.scan(ScanCursor.of(cursor), args))
97+
case None => delegateRedisClusterCommandAndLift(_.scan(ScanCursor.of(cursor)))
98+
}
99+
result.map { scanResult =>
100+
RedisBaseAsyncCommands.ScanResults(scanResult.getCursor, scanResult.isFinished, scanResult.getKeys.toList)
101+
}
102+
}
103+
26104
override def ttl(key: K): Future[Option[FiniteDuration]] =
27-
delegateRedisClusterCommandAndLift(_.ttl(key)).map(toFiniteDuration(TimeUnit.SECONDS))
105+
delegateRedisClusterCommandAndLift(_.pttl(key)).map(toFiniteDuration(TimeUnit.MILLISECONDS))
106+
107+
override def touch(key: K*): Future[Long] = {
108+
delegateRedisClusterCommandAndLift(_.touch(key: _*)).map(Long2long)
109+
}
110+
111+
override def `type`(key: K): Future[String] = {
112+
delegateRedisClusterCommandAndLift(_.`type`(key))
113+
}
28114
}
29115

30116
private[this] object LettuceRedisKeyAsyncCommands {

0 commit comments

Comments
 (0)