diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8001fd61c0..287c4ffe04 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,26 +1,21 @@ -Lettuce 6.0.0 GA RELEASE NOTES +Lettuce 6.1 M1 RELEASE NOTES ============================== -The Lettuce team is delighted to announce the availability of Lettuce 6. +The Lettuce team is delighted to announce the availability of the first Lettuce 6.1 milestone. -Lettuce 6 aligns with Redis 6 in terms of API and protocol changes. Both protocols, RESP and RESP3 are supported side-by-side defaulting to RESP. +This is a massive release thanks to all the community contributions. Most notable changes that ship with this release are: -Most notable changes that ship with this release are: +* Support for most Redis 6.2 commands and command changes +* Micrometer integration +* `CommandListeners` API to intercept Redis commands +* extended Keep-Alive options +* Coroutine variant of `ScanStream` +* TCP NoDelay enabled by default +* Experimental support for io_uring -* RESP3 support -* ACL Authentication with username/password -* Asynchronous Cluster Topology Refresh -* Client-side caching support -* Registration of push message listeners -* Configuration files for GraalVM Native Image compilation -* Kotlin Coroutine API -* Redesign command latency metrics publishing -* API cleanups/Breaking Changes +Lettuce 6 supports Redis 2.6+ up to Redis 6.x. In terms of Java runtime, Lettuce requires at least Java 8 and works with Java 16. -Lettuce 6 supports Redis 2.6+ up to Redis 6.x. In terms of Java runtime, Lettuce requires at least Java 8 and works with Java 15. - -Thanks to all contributors who made Lettuce 6.0.0.RELEASE possible. -Lettuce requires a minimum of Java 8 to build and run and is compatible with Java 15. It is tested continuously against the latest Redis source-build. +Thanks to all contributors who made Lettuce 6.1.0.M1 possible. If you need any support, meet Lettuce at @@ -28,313 +23,167 @@ If you need any support, meet Lettuce at * Stack Overflow (Questions): https://stackoverflow.com/questions/tagged/lettuce * Join the chat at https://gitter.im/lettuce-io/Lobby for general discussion * GitHub Issues (Bug reports, feature requests): https://github.com/lettuce-io/lettuce-core/issues -* Documentation: https://lettuce.io/core/6.0.0.RELEASE/reference/ -* Javadoc: https://lettuce.io/core/6.0.0.RELEASE/api/ +* Documentation: https://lettuce.io/core/6.1.0.M1/reference/ +* Javadoc: https://lettuce.io/core/6.1.0.M1/api/ -RESP3 Support -------------- -Redis 6 ships with support for a new protocol version. RESP3 brings support for additional data types to distinguish better between responses. The following response types were introduced with RESP3: +Micrometer Integration +---------------------- -* Null: a single `null` value replacing RESP v2 `*-1` and `$-1` null values. -* Double: a floating-point number. -* Boolean: `true` or `false`. -* Blob error: binary-safe error code and message. -* Verbatim string: a binary-safe string that is typically used as user message without any escaping or filtering. -* Map: an ordered collection of key-value pairs. Keys and values can be any other RESP3 type. -* Set: an unordered collection of N other types. -* Attribute: Like the Map type, but the client should keep reading the reply ignoring the attribute type, and return it to the client as additional information. -* Push: Out-of-band data. -* Streamed strings: A large response using chunked transfer. -* Hello: Like the Map type, but is sent only when the connection between the client and the server is established, in order to welcome the client with different information like the name of the server, its version, and so forth. -* Big number: a large number non-representable by the Number type +Lettuce ships an integration for Micrometer. Commands are tracked by using two Micrometer Times: `lettuce.command.firstresponse` and `lettuce.command.completion`. The following tags are attached to each timer: -Lettuce supports all response types except attributes. Push messages are only supported for Pub/Sub messages. +* `command`: Name of the command (`GET`, `SET`, …) +* `local`: Local socket (localhost/127.0.0.1:45243 or ANY when local distinction is disabled, which is the default behavior) +* `remote`: Remote socket (localhost/127.0.0.1:6379) -The protocol version can be changed through `ClientOptions` which disables protocol discovery: +To enable Micrometer, create `MicrometerCommandLatencyRecorder` from `MeterRegistry` and register it in `ClientResources`: ```java -ClientOptions options = ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build(); -``` - -Future versions are going to discover the protocol version as part of the connection handshake and use the newest available protocol version. - -ACL Authentication ------------------- - -Redis 6 supports authentication using username and password. Lettuce's `RedisURI` adapts to this change by allowing to specify a username: +MeterRegistry meterRegistry = …; +MicrometerOptions options = MicrometerOptions.create(); +ClientResources resources = ClientResources.builder().commandLatencyRecorder(new MicrometerCommandLatencyRecorder(meterRegistry, options)).build(); -`redis://username:password@host:port/database` - -Using RESP3 or PING on connect authenticates the connection during the handshake phase. Already connected connections may switch the user context by issuing an `AUTH` command with username and password: - -```java -StatefulRedisConnection connection = client.connect(); -RedisCommands commands = connection.sync(); -commands.auth("username", "password"); +RedisClient client = RedisClient.create(resources); ``` -Asynchronous Cluster Topology Refresh -------------------------------------- +Make sure to have Micrometer on your class path (example from a Maven `pom.xml`): -Cluster Topology Refresh was in Lettuce 4 and 5 a blocking and fully synchronous task that required a worker thread. A side-effect of the topology refresh was that command timeouts could be delayed as the worker thread pool was used for timeout tasks and the topology refresh. Lettuce 6 ships with a fully non-blocking topology refresh mechanism which is basically a reimplementation of the previous refresh mechanism but using non-blocking components instead. -Server-assisted Client-side caching support -------------------------------------------- +```xml + + io.micrometer + micrometer-core + ${micrometer.version} + +``` -Redis can notify clients about cache invalidations when you use Redis as a read-through cache. -That is when applications keep a local copy of the cached value and use Redis to back up the local cache before. -When a cache values gets changed (that were fetched from Redis), then Redis notifies interested clients so they can invalidate their near-cache and potentially fetch the changed value. +See also: https://github.com/lettuce-io/lettuce-core/wiki/Command-Latency-Metrics#command.latency.metrics.micrometer -Redis 6 allows for tracking clients and sending push messages using RESP3 push messages. -Lettuce provides a `CacheFrontend` that can be used to interact with a cache. -Client-side caching assumes a near cache that can be queried, updated and evicted for individual keys. -Cache values are represented as strings and the entire functionality is exposed through a `CacheFrontend`. +CommandListeners +---------------- -See the following example that uses a `ConcurrentHashMap` as near cache and outlines the interaction between involved parties: +Command listeners allow intercepting Redis Command calls before the command is sent and upon completion (success/failure). A `CommandListener` can be registered with `RedisClient` and `RedisClusterClient`: ```java -Map clientCache = new ConcurrentHashMap<>(); - -RedisCommands otherParty = redisClient.connect().sync(); - -// creates a key -otherParty.set(key, value); - -StatefulRedisConnection myself = redisClient.connect(); -CacheFrontend frontend = ClientSideCaching.enable(CacheAccessor.forMap(clientCache), myself, - TrackingArgs.Builder.enabled().noloop()); - -// Read-through into Redis -String cachedValue = frontend.get(key); -assertThat(cachedValue).isNotNull(); - -// client-side cache holds the same value -assertThat(clientCache).hasSize(1); - -// now, the key expires -commands.pexpire(key, 1); - -// a while later -Thread.sleep(200); - -// the expiration reflects in the client-side cache -assertThat(clientCache).isEmpty(); - -assertThat(frontend.get(key)).isNull() -``` +RedisClient client = …; -Lettuce ships out of the box with a `CacheAccessor` for the `Map` interface. -You can implement a `CacheAccessor` for your cache if it doesn't implement the `Map` interface. -Client-side caching support is only supported when using RESP3. The Pub/Sub mode isn't supported through `ClientSideCaching` and we don't support Pub/Sub redirection. -Since push messages are node-local, client-side caching is supported only on Redis Standalone setups. -Master/Replica or Clustering operating modes are not supported as multi-node operations and connection failover impose severe complexity onto key tracking. - -Read more: https://redis.io/topics/client-side-caching - -Registration of push message listeners --------------------------------------- - -Registration of push message listeners completes Lettuce's RESP3 support. -You can register `PushMessage` listeners on Standalone and Redis Cluster connections by implementing `PushListener` respective `RedisClusterPushListener`: +client.addListener(new CommandListener() { + @Override + public void commandStarted(CommandStartedEvent event) { + Map context = event.getContext(); + context.put(…); + } -```java -connection.addListener(message -> { + @Override + public void commandSucceeded(CommandSucceededEvent event) { + Map context = event.getContext(); + } - if (message.getType().equals("invalidate")) { - invalidations.addAll((List) message.getContent(StringCodec.UTF8::decodeKey).get(1)); + @Override + public void commandFailed(CommandFailedEvent event) { + CommandListener.super.commandFailed(event); } }); -``` - -Using push messages with Redis Cluster are subject to node-locality, therefore `RedisClusterPushListener` provides access to the `RedisClusterNode` from which the push message originated. - -The content of push messages may consist of arbitrary data structures and vary across various push message types. -`PushMessage` exposes the message type and access to its content as `List`. Bulk content can be decoded into more specific data types. - -Kotlin Coroutine API --------------------- - -Kotlin users can now use a Coroutine API that exposes suspended methods and uses `Flow` where appropriate. -The API can be obtained through an extension function for Standalone, Cluster, and Sentinel connections exposing the appropriate API. -```kotlin -val api: RedisCoroutinesCommands = connection.coroutines() -val foo1 = api.set("foo", "bar") -val foo2 = api.keys("fo*") -``` - -Additionally, we ship two extensions that simplify transactional usage by providing a `multi` closure: - -```kotlin -val result: TransactionResult = connection.async().multi { - set("foo", "bar") - get("foo") -} +StatefulRedisConnection connection = client.connect(); ``` +Command events allow attaching contextual details that can be accessed upon completion. -The API is marked experimental and requires opt-in through `@ExperimentalLettuceCoroutinesApi` to avoid Compiler warnings. -We expect further evolution of the API towards a more `Flow`-oriented API where now `List` is returned to enable streaming of large responses. - -Redesign command latency metrics publishing -------------------------------------------- +Experimental support for io_uring +--------------------------------- -This is a mostly internal change that switches from `CommandLatencyCollector` to the newly introduced `CommandLatencyRecorder` interface. -Unless you're implementing or configuring `CommandLatencyCollector` yourself, you should not see any changes. +We've adopted Netty's experimental io_uring support for Linux-based systems to allow participating in improved I/O performance. -This change is motivated by support for libraries that provide latency observability without publishing metrics to the `EventBus`. +io_uring will be enabled automatically if you're running on Linux and you have the dependency on your classpath: +```xml + + io.netty.incubator + netty-incubator-transport-native-io_uring + ${netty.transport-native-io_uring.version} + linux-x86_64 + +``` -API cleanups/Breaking Changes ------------------------------ - -With this release, we took the opportunity to introduce a series of changes that put the API into a cleaner shape. - -* Redesign command latency metrics publishing #1409 -* Remove JavaRuntime class and move LettuceStrings to internal package #1329 -* Remove Spring support classes #1358 -* Replace io.lettuce.core.resource.Futures utility with Netty's PromiseCombiner #1283 -* XGROUP DELCONSUMER should return pending message count #1377 (xgroupDelconsumer(…) now returns `Long`) -* Change hgetall return type from Mono to Flux #1434 -* Script Commands: `eval`, `digest`, `scriptLoad` methods now only accept `String` and `byte[]` argument types. Previously `digest` and `scriptLoad` accepted the script contents as Codec value type which caused issues especially when marshalling values using JSON or Java Serialization. The script charset can be configured via `ClientOptions` (`ClientOptions.builder().scriptCharset(StandardCharsets.US_ASCII).build();`), defaulting to UTF-8. -* Connection: Removal of deprecated timeout methods accepting `TimeUnit`. Use methods accepting `Duration` instead. -* Async Commands: `RedisAsyncCommands.select(…)` and `.auth(…)` methods return now futures instead if being blocking methods. -* Asynchronous API Usage: Connection and Queue failures now no longer throw an exception but properly associate the failure with the Future handle. -* Master/Replica API: Move implementation classes from `io.lettuce.core.masterslave` to `io.lettuce.core.masterreplica` package. -* Internal: Removal of the internal `LettuceCharsets` utility class. -* Internal: Reduced visibility of several `protected` fields in `AbstractRedisClient` (`eventLoopGroups`, `genericWorkerPool`, `timer`, `clientResources`, `clientOptions`, `defaultTimeout`). -* Internal: Consolidation of Future synchronization utilities (`LettuceFutures`, `RefreshFutures`, `Futures`). -* Deprecate reactive StreamChannel methods #1434 -* Rename StatefulRedisConnection.suspendable() to .coroutines() and command interfaces accordingly #1436 +When having both native transports enabled (io_uring and epoll), then io_uring has precedence over epoll. +We'd love to hear from you how io_uring works out for you. Commands ------------------------------ -* Add support for STRALGO #1280 -* Add support for LPOS #1320 +------------ +* Add support for `SET … GET` option #1442 +* Add support for `(B)LMOVE` source destination LEFT|RIGHT LEFT|RIGHT command #1448 +* Add support for `ZMSCORE key member [member ...]` command #1449 +* Add support for `ZINTER`/`ZUNION` commands #1450 +* Add support for `SMISMEMBER key member [member ...]` command #1452 +* Support `NOMKSTREAM` option in `XADD` command #1502 +* Support option `CREATECONSUMER` in `XGROUP` command #1505 (Thanks to @dengliming) +* Add support for `ZRANGESTORE` command #1506 +* Add support for `ZDIFF` and `ZDIFFSTORE` commands #1507 +* Add support for `COPY` command #1508 +* Add support for local addr in `CLIENT KILL` #1536 +* Add support for `LPOP` and `RPOP` with `COUNT` #1545 +* Missing support for `TYPE` parameter of SCAN command #1559 (Thanks to @mvmn) +* Add support for `GEOSEARCH` and `GEOSEARCHSTORE` #1561 +* Add support for `ReadFrom.subnet` #1569 (Thanks to @yueki1993) +* Add exclusive range query to `XPENDING` #1585 +* Add support for `PXAT`/`EXAT` arguments to `SET` command #1607 +* Add support for `ZADD` `GT`/`LT` options #1451 Enhancements ------------ -* Use channel thread to enqueue commands #617 -* Redesign connection activation #697 -* Add support for RESP3 #964 -* Consolidate Future utils #1039 -* Make RedisAsyncCommands.select() and auth() async #1118 (Thanks to @ikkyuland) -* Allow client to pick a specific TLS version and introduce PEM-based configuration #1167 (Thanks to @amohtashami12307) -* Optimization of BITFIELD args generation #1175 (Thanks to @ianpojman) -* Add mutate() to SocketOptions #1193 -* Add CLIENT ID command #1197 -* Lettuce not able to reconnect automatically to SSL+authenticated ElastiCache node #1201 (Thanks to @chadlwilson) -* Add support for AUTH with user + password introduced in Redis 6 #1202 (Thanks to @tgrall) -* HMSET deprecated in version 4.0.0 #1217 (Thanks to @hodur) -* Allow selection of Heap or Direct buffers for CommandHandler.buffer #1223 (Thanks to @dantheperson) -* Use domain specific value object as return type for xpending. #1229 (Thanks to @christophstrobl) -* Support JUSTID flag of XCLAIM command #1233 (Thanks to @christophstrobl) -* Add support for KEEPTTL with SET #1234 -* Add support for RxJava 3 #1235 -* Retrieve username from URI when RedisURI is built from URL #1242 (Thanks to @gkorland) -* Introduce ThreadFactoryProvider to DefaultEventLoopGroupProvider for easier customization #1243 (Thanks to @apilling6317) -* Add template method for EventLoopGroup creation #1273 (Thanks to @konstantin-grits) -* Add support for Client-side caching #1281 -* Registration of push message listeners #1284 -* Add charset option to ScanArgs.match(…) #1285 (Thanks to @gejun123456) -* Allow for more customisation of the tracing span #1303 (Thanks to @JaidenAshmore) -* Support for GraalVM Native Images #1316 (Thanks to @ilopmar) -* Consider topology updates for default Cluster connections #1317 (Thanks to @be-hase) -* SSL handshake doesn't respect timeouts #1326 (Thanks to @feliperuiz) -* Reduce RedisStateMachine bytecode size #1332 (Thanks to @hellyguo) -* Feature request: add a cluster-capable version of `flushallAsync` #1359 (Thanks to @jchambers) -* BoundedAsyncPool object is ready to be manipulated with even though a connection is not created yet #1363 (Thanks to @little-fish) -* Introduce DecodeBufferPolicy to reduce memory usage #1314 (Thanks to @Shaphan) -* Kotlin Coroutine API #1387 (Thanks to @SokoMishaLov) -* Add support for aarch64 #1396 (Thanks to @odidev) +* Add Micrometer integration #795 +* Add support for `CommandListeners` #1382 (Thanks to @sokomishalov) +* Provide a Coroutine variant of ScanStream/ScanIterator #1435 +* Introduce extended Keep-Alive options #1437 +* Add anyReplica setting for `ReadFrom` #1444 (Thanks to @omer-cilingir) +* Option to configure SSL/TLS verification level #1460 (Thanks to @Lucas3oo) +* netty's asynchronous DNS resolver seems not to be used #1498 (Thanks to @yueki1993) +* Extend `CommandDetailParser` to include ACL details #1503 +* Consider reinstating master-replica wording #1518 (Thanks to @perlun) +* Add support for io_uring #1522 +* Provide `VoidOutput` for Fire&Forget command usage #1529 (Thanks to @jaredpetersen) +* Improve unsupported error logging for `CommandOutput` #1532 +* Allow providing custom ClusterTopologyRefresh implementation. #1598 (Thanks to @alessandrosimi-sa) +* Introduce `LettuceStrings.isEmpty(String)` overload with optimized isEmpty checking #1609 Fixes ----- -* Commands Timeout ignored/not working during refresh #1107 (Thanks to @pendula95) -* StackOverflowError in RedisPublisher #1140 (Thanks to @csunwold) -* Incorrect access on io.lettuce.core.ReadFrom.isOrderSensitive() #1145 (Thanks to @orclev) -* Consider ReadFrom.isOrderSensitive() in cluster scan command #1146 -* Improve log message for nodes that cannot be reached during reconnect/topology refresh #1152 (Thanks to @drewcsillag) -* BoundedAsyncPool doesn't work with a negative maxTotal #1181 (Thanks to @sguillope) -* TLS setup fails to a master reported by sentinel #1209 (Thanks to @ae6rt) -* Lettuce metrics creates lots of long arrays, and gives out of memory error. #1210 (Thanks to @omjego) -* CommandSegments.StringCommandType does not implement hashCode()/equals() #1211 -* Unclear documentation about quiet time for RedisClient#shutdown #1212 (Thanks to @LychakGalina) -* StreamReadOutput in Lettuce 6 creates body entries containing the stream id #1216 -* Write race condition while migrating/importing a slot #1218 (Thanks to @phyok) -* randomkey return V not K #1240 (Thanks to @hosunrise) -* ConcurrentModificationException iterating over partitions #1252 (Thanks to @johnny-costanzo) -* Replayed activation commands may fail because of their execution sequence #1255 (Thanks to @robertvazan) -* Fix infinite command timeout #1260 -* Connection leak using pingBeforeActivateConnection when PING fails #1262 (Thanks to @johnny-costanzo) -* Lettuce blocked when connecting to Redis #1269 (Thanks to @jbyjby1) -* Stream commands are not considered for ReadOnly routing #1271 (Thanks to @redviper) -* Write race condition while migrating/importing a slot #1218 (Thanks to @phyok) -* PauseDetector acquisition hang in DefaultCommandLatencyCollector #1300 (Thanks to @ackerL) -* NullPointerException thrown during AbstractRedisAsyncCommands.flushCommands #1301 (Thanks to @mruki) -* xpending(K, Consumer, Range, Limit) fails with ERR syntax error using Limit.unlimited() #1302 (Thanks to @nagaran1) -* Remove duplicated command on asking #1304 (Thanks to @koisyu) -* ArrayOutput stops response parsing on empty nested arrays #1327 (Thanks to @TheCycoONE) -* Synchronous dispatch of MULTI returns null #1335 (Thanks to @tzxyz) -* RedisAdvancedClusterAsyncCommandsImpl scriptKill is incorrectly calling scriptFlush #1340 (Thanks to @azhukayak) -* RedisAdvancedClusterAsyncCommands.scriptKill now calls scriptKill instead of scriptFlush #1341 (Thanks to @dengliming) -* Lingering topology refresh connections when using dynamic refresh sources #1342 (Thanks to @tpf1994) -* Wrong cast in StringCodec may lead to IndexOutOfBoundsException #1367 (Thanks to @dmandalidis) -* xpending(key, group) fails without pending messages #1378 -* Sentinel authentication failed when using the pingBeforeActivateConnection parameter #1401 (Thanks to @viniciusxyz) -* RedisURI.toString() should not reveal password #1405 -* MasterReplica.connect(…) doesn't consider username with Redis 6 #1406 -* LPOS command sends FIRST instead of RANK #1410 (Thanks to @christophstrobl) -* Fix **/ for closing comments #1416 -* Correctly report isDone if Command completed with completeExceptionally #1433 +* Fix `EXEC` without `MULTI` when using coroutines over async #1441 (Thanks to @sokomishalov) +* Lettuce with Tracing enabled fails to connect to a Redis Sentinel #1470 (Thanks to @jsonwan) +* Lettuce doesn't handle deleted stream items (`NullPointerException`) #1474 (Thanks to @chemist777) +* LettuceStrings does not handle `-nan` which is returned by `FT.INFO` in redisearch #1482 (Thanks to @krm1312) +* Improperly decoding command responses #1512 (Thanks to @johnny-costanzo) +* Fix timeout parameter for nanoseconds in `RedisURI` #1528 (Thanks to @izeye) +* Fix build break when missing netty-dns-resolver #1546 (Thanks to @yueki1993) +* Sentinel lookup connection leaks if no master address reported #1558 (Thanks to @wwwjinlong) +* Lettuce 6.0.1 fails with GraalVM 20.3 #1562 (Thanks to @atrianac) +* Reactive stream spec violation when using command timeout #1576 (Thanks to @martin-tarjanyi) +* Fix copyright replace bug for Kotlin api generator #1588 (Thanks to @dengliming) +* Fix NullPointerException in BoundedAsyncPool.createIdle() when at capacity #1611 Other ----- -* Refactor script content argument types to String and byte[] instead of V (value type) #1010 (Thanks to @danielsomekh) -* Render Redis.toString() to a Redis URI #1040 -* Pass Allocator as RedisStateMachine constructor argument #1053 -* Simplify condition to invoke "resolveCodec" method in AnnotationRedisCodecResolver #1149 (Thanks to @machi1990) -* Encode database in RedisURI in path when possible #1155 -* Remove LettuceCharsets #1156 -* Move SocketAddress resolution from RedisURI to SocketAddressResolver #1157 -* Remove deprecated timeout methods accepting TimeUnit #1158 -* Upgrade to RxJava 2.2.13 #1162 -* Add ByteBuf.touch(…) to aid buffer leak investigation #1164 -* Add warning log if MasterReplica(…, Iterable) contains multiple Sentinel URIs #1165 -* Adapt GEOHASH tests to 10 chars #1196 -* Migrate Master/Replica support to the appropriate package #1199 -* Disable RedisURIBuilderUnitTests failing on Windows OS #1204 (Thanks to @kshchepanovskyi) -* Provide a default port(DEFAULT_REDIS_PORT) to RedisURI's Builder #1205 (Thanks to @hepin1989) -* Update code for pub/sub to listen on the stateful connection object. #1207 (Thanks to @judepereira) -* Un-deprecate ClientOptions.pingBeforeActivateConnection #1208 -* Use consistently a shutdown timeout of 2 seconds in all AbstractRedisClient.shutdown methods #1214 -* Upgrade dependencies (netty to 4.1.49.Final) #1161, #1224, #1225, #1239, #1259 -* RedisURI class does not parse password when using redis-sentinel #1232 (Thanks to @kyrogue) -* Reduce log level to DEBUG for native library logging #1238 (Thanks to @DevJoey) -* Reduce visibility of fields in AbstractRedisClient #1241 -* Upgrade to stunnel 5.56 #1246 -* Add build profiles for multiple Java versions #1247 -* Replace outdated Sonatype parent POM with plugin definitions #1258 -* Upgrade to RxJava 3.0.2 #1261 -* Enable Sentinel tests after Redis fixes RESP3 handshake #1266 -* Consolidate exception translation and bubbling #1275 -* Reduce min thread count to 2 #1278 -* Upgrade dependencies #1305 -* Add FAQ section to reference docs #1307 -* Rename master branch to main #1308 -* Consistently use Javadoc wording in BoundedPoolConfig.Builder #1337 (Thanks to @maestroua) -* Upgrade to Reactor Core 3.3.8.RELEASE #1353 -* Upgrade to netty 4.1.51.Final #1354 -* Consistently translate execution exceptions #1370 -* Upgrade to RxJava 3.0.5 #1374 -* Upgrade to Commons Pool 2.8.1 #1375 -* Upgrade to Reactor 3.3.9.RELEASE #1384 -* Upgrade to Project Reactor 3.3.10.RELEASE #1411 -* Upgrade to netty 4.1.52.Final #1412 -* Upgrade test/optional dependencies #1413 -* Explicit AWS and AZURE compatibility on README #1425 (Thanks to @raphaelauv) -* Revisit synchronized blocks #1429 -* Upgrade dependencies #1431 +* Fix integration test password #1445 +* Un-Deprecate `io.lettuce.core.LettuceFutures` #1453 (Thanks to @andrewsensus) +* Switch to `Flux/Mono.expand(…)` for `ScanStream` #1458 +* Enable TCP NoDelay by default #1462 +* Update contrib guide #1472 +* Adapt tests to changed Redis response #1473 +* Upgrade dependencies #1476 +* Remove JUnit 4 dependency management #1477 +* Replace `ClusterRule` #1478 +* Remove Redis Command retrieval for Redis Cluster Connections #1481 +* Implement `set(double)` in `NestedMultiOutput` #1486 (Thanks to @jruaux) +* Start `HashWheelTimer` in `ClientResources` to avoid blocking calls in EventLoop #1489 +* `DefaultClientResources`: fix typos #1497 (Thanks to @perlun) +* API generator problems #1499 (Thanks to @sokomishalov) +* Reduce build matrix to Java 8, 11, 15, and EA #1519 +* Upgrade to Netty 4.1.54.Final #1541 +* netty 4.1.56 #1556 (Thanks to @sullis) +* Move Mailing list forum to GitHub discussions #1557 +* Let coroutines `dispatch`-method be flowable #1567 (Thanks to @sokomishalov) +* Update copyright years to 2021 #1573 +* Upgrade to netty 4.1.58.Final #1610 diff --git a/pom.xml b/pom.xml index 7326df52e2..53e922181b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.lettuce lettuce-core - 6.1.0.BUILD-SNAPSHOT + 6.1.0-BUILD-SNAPSHOT jar Lettuce @@ -60,13 +60,13 @@ 5.7.0 2.2 2.1.12 - 1.3.72 - 1.3.8 + 1.4.30 + 1.4.2 2.0.3 2.13.3 1.6.0 3.5.15 - 4.1.56.Final + 4.1.58.Final 2.0.19 3.3.11.RELEASE 1.3.8 @@ -231,7 +231,7 @@ io.netty.incubator netty-incubator-transport-native-io_uring - 0.0.1.Final + 0.0.3.Final linux-x86_64 true diff --git a/src/main/asciidoc/ha-sharding.asciidoc b/src/main/asciidoc/ha-sharding.asciidoc index 625b53bd06..aacd855bf3 100644 --- a/src/main/asciidoc/ha-sharding.asciidoc +++ b/src/main/asciidoc/ha-sharding.asciidoc @@ -11,9 +11,9 @@ [[master-slave]] [[master-replica]] [[upstream-replica]] -=== Upstream/Replica +=== Master/Replica -include::{ext-doc}/Upstream-Replica.asciidoc[leveloffset=+2] +include::{ext-doc}/Master-Replica.asciidoc[leveloffset=+2] [[redis-sentinel]] === Redis Sentinel diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index c2e018073d..aadb5c886c 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -11,6 +11,7 @@ * Configuration of extended Keep-Alive options through `KeepAliveOptions` (only available for some transports/Java versions). * Configuration of netty's `AddressResolverGroup` through `ClientResources`. Uses `DnsAddressResolverGroup` when `netty-resolver-dns` is on the classpath. +* Add support for Redis ACL commands. [[new-features.6-0-0]] == What's new in Lettuce 6.0 diff --git a/src/main/asciidoc/overview.asciidoc b/src/main/asciidoc/overview.asciidoc index 083fada7fc..f7f016c216 100644 --- a/src/main/asciidoc/overview.asciidoc +++ b/src/main/asciidoc/overview.asciidoc @@ -74,7 +74,7 @@ If you encounter a bug or want to suggest an improvement, please create a ticket === Where to go from here * Head to <> if you feel like jumping straight into the code. -* Go to <> for Upstream/Replica ("Master/Slave"), Redis Sentinel and Redis Cluster topics. +* Go to <> for Master/Replica ("Master/Slave"), Redis Sentinel and Redis Cluster topics. * In order to dig deeper into the core features of Reactor: ** If you’re looking for client configuration options, performance related behavior and how to use various transports, go to <>. ** See <> for extending Lettuce with codecs or integrate it in your CDI/Spring application. diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index 6e45ed2447..d245520ba1 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -18,6 +18,7 @@ import static io.lettuce.core.protocol.CommandType.*; import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Map; @@ -56,11 +57,12 @@ * @author Andrey Shlykov */ @SuppressWarnings("unchecked") -public abstract class AbstractRedisAsyncCommands implements RedisHashAsyncCommands, RedisKeyAsyncCommands, - RedisStringAsyncCommands, RedisListAsyncCommands, RedisSetAsyncCommands, - RedisSortedSetAsyncCommands, RedisScriptingAsyncCommands, RedisServerAsyncCommands, - RedisHLLAsyncCommands, BaseRedisAsyncCommands, RedisTransactionalAsyncCommands, - RedisGeoAsyncCommands, RedisClusterAsyncCommands { +public abstract class AbstractRedisAsyncCommands implements RedisAclAsyncCommands, + RedisHashAsyncCommands, RedisKeyAsyncCommands, RedisStringAsyncCommands, + RedisListAsyncCommands, RedisSetAsyncCommands, RedisSortedSetAsyncCommands, + RedisScriptingAsyncCommands, RedisServerAsyncCommands, RedisHLLAsyncCommands, + BaseRedisAsyncCommands, RedisTransactionalAsyncCommands, RedisGeoAsyncCommands, + RedisClusterAsyncCommands { private final StatefulConnection connection; @@ -77,6 +79,81 @@ public AbstractRedisAsyncCommands(StatefulConnection connection, RedisCode this.commandBuilder = new RedisCommandBuilder<>(codec); } + @Override + public RedisFuture> aclCat() { + return dispatch(commandBuilder.aclCat()); + } + + @Override + public RedisFuture> aclCat(AclCategory category) { + return dispatch(commandBuilder.aclCat(category)); + } + + @Override + public RedisFuture aclDeluser(String... usernames) { + return dispatch(commandBuilder.aclDeluser(usernames)); + } + + @Override + public RedisFuture aclGenpass() { + return dispatch(commandBuilder.aclGenpass()); + } + + @Override + public RedisFuture aclGenpass(int bits) { + return dispatch(commandBuilder.aclGenpass(bits)); + } + + @Override + public RedisFuture> aclGetuser(String username) { + return dispatch(commandBuilder.aclGetuser(username)); + } + + @Override + public RedisFuture> aclList() { + return dispatch(commandBuilder.aclList()); + } + + @Override + public RedisFuture aclLoad() { + return dispatch(commandBuilder.aclLoad()); + } + + @Override + public RedisFuture>> aclLog() { + return dispatch(commandBuilder.aclLog()); + } + + @Override + public RedisFuture>> aclLog(int count) { + return dispatch(commandBuilder.aclLog(count)); + } + + @Override + public RedisFuture aclLogReset() { + return dispatch(commandBuilder.aclLogReset()); + } + + @Override + public RedisFuture aclSave() { + return dispatch(commandBuilder.aclSave()); + } + + @Override + public RedisFuture aclSetuser(String username, AclSetuserArgs args) { + return dispatch(commandBuilder.aclSetuser(username, args)); + } + + @Override + public RedisFuture> aclUsers() { + return dispatch(commandBuilder.aclUsers()); + } + + @Override + public RedisFuture aclWhoami() { + return dispatch(commandBuilder.aclWhoami()); + } + @Override public RedisFuture append(K key, V value) { return dispatch(commandBuilder.append(key, value)); @@ -592,8 +669,9 @@ public RedisFuture expire(K key, long seconds) { } @Override - public RedisFuture expireat(K key, Date timestamp) { - return expireat(key, timestamp.getTime() / 1000); + public RedisFuture expire(K key, Duration seconds) { + LettuceAssert.notNull(seconds, "Timeout must not be null"); + return expire(key, seconds.toMillis() / 1000); } @Override @@ -601,6 +679,18 @@ public RedisFuture expireat(K key, long timestamp) { return dispatch(commandBuilder.expireat(key, timestamp)); } + @Override + public RedisFuture expireat(K key, Date timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + return expireat(key, timestamp.getTime() / 1000); + } + + @Override + public RedisFuture expireat(K key, Instant timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + return expireat(key, timestamp.toEpochMilli() / 1000); + } + @Override public void flushCommands() { connection.flushCommands(); @@ -703,6 +793,23 @@ protected RedisFuture>> georadiusbymember_ro(K key, V member, return dispatch(commandBuilder.georadiusbymember(GEORADIUSBYMEMBER_RO, key, member, distance, unit.name(), geoArgs)); } + @Override + public RedisFuture> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate) { + return dispatch(commandBuilder.geosearch(key, reference, predicate)); + } + + @Override + public RedisFuture>> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, + GeoArgs geoArgs) { + return dispatch(commandBuilder.geosearch(key, reference, predicate, geoArgs)); + } + + @Override + public RedisFuture geosearchstore(K destination, K key, GeoSearch.GeoRef reference, + GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist) { + return dispatch(commandBuilder.geosearchstore(destination, key, reference, predicate, geoArgs, storeDist)); + } + @Override public RedisFuture get(K key) { return dispatch(commandBuilder.get(key)); @@ -1071,11 +1178,24 @@ public RedisFuture pexpire(K key, long milliseconds) { return dispatch(commandBuilder.pexpire(key, milliseconds)); } + @Override + public RedisFuture pexpire(K key, Duration milliseconds) { + LettuceAssert.notNull(milliseconds, "Timeout must not be null"); + return pexpire(key, milliseconds.toMillis()); + } + @Override public RedisFuture pexpireat(K key, Date timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); return pexpireat(key, timestamp.getTime()); } + @Override + public RedisFuture pexpireat(K key, Instant timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + return pexpireat(key, timestamp.toEpochMilli()); + } + @Override public RedisFuture pexpireat(K key, long timestamp) { return dispatch(commandBuilder.pexpireat(key, timestamp)); diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index 7507ed2e40..37713bf93f 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -18,9 +18,11 @@ import static io.lettuce.core.protocol.CommandType.*; import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import reactor.core.publisher.Flux; @@ -51,7 +53,6 @@ import io.lettuce.core.tracing.Tracing; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.ImmediateEventExecutor; - /** * A reactive and thread-safe API for a Redis connection. * @@ -64,11 +65,12 @@ * @author Andrey Shlykov * @since 4.0 */ -public abstract class AbstractRedisReactiveCommands implements RedisHashReactiveCommands, - RedisKeyReactiveCommands, RedisStringReactiveCommands, RedisListReactiveCommands, - RedisSetReactiveCommands, RedisSortedSetReactiveCommands, RedisScriptingReactiveCommands, - RedisServerReactiveCommands, RedisHLLReactiveCommands, BaseRedisReactiveCommands, - RedisTransactionalReactiveCommands, RedisGeoReactiveCommands, RedisClusterReactiveCommands { +public abstract class AbstractRedisReactiveCommands implements RedisAclReactiveCommands, + RedisHashReactiveCommands, RedisKeyReactiveCommands, RedisStringReactiveCommands, + RedisListReactiveCommands, RedisSetReactiveCommands, RedisSortedSetReactiveCommands, + RedisScriptingReactiveCommands, RedisServerReactiveCommands, RedisHLLReactiveCommands, + BaseRedisReactiveCommands, RedisTransactionalReactiveCommands, RedisGeoReactiveCommands, + RedisClusterReactiveCommands { private final StatefulConnection connection; @@ -109,6 +111,81 @@ private EventExecutorGroup getScheduler() { return this.scheduler = schedulerToUse; } + @Override + public Mono> aclCat() { + return createMono(commandBuilder::aclCat); + } + + @Override + public Mono> aclCat(AclCategory category) { + return createMono(() -> commandBuilder.aclCat(category)); + } + + @Override + public Mono aclDeluser(String... usernames) { + return createMono(() -> commandBuilder.aclDeluser(usernames)); + } + + @Override + public Mono aclGenpass() { + return createMono(commandBuilder::aclGenpass); + } + + @Override + public Mono aclGenpass(int bits) { + return createMono(() -> commandBuilder.aclGenpass(bits)); + } + + @Override + public Mono> aclGetuser(String username) { + return createMono(() -> commandBuilder.aclGetuser(username)); + } + + @Override + public Flux aclList() { + return createDissolvingFlux(commandBuilder::aclList); + } + + @Override + public Mono aclLoad() { + return createMono(commandBuilder::aclLoad); + } + + @Override + public Flux> aclLog() { + return createDissolvingFlux(commandBuilder::aclLog); + } + + @Override + public Flux> aclLog(int count) { + return createDissolvingFlux(() -> commandBuilder.aclLog(count)); + } + + @Override + public Mono aclLogReset() { + return createMono(commandBuilder::aclLogReset); + } + + @Override + public Mono aclSave() { + return createMono(commandBuilder::aclSave); + } + + @Override + public Mono aclSetuser(String username, AclSetuserArgs args) { + return createMono(() -> commandBuilder.aclSetuser(username, args)); + } + + @Override + public Flux aclUsers() { + return createDissolvingFlux(commandBuilder::aclUsers); + } + + @Override + public Mono aclWhoami() { + return createMono(commandBuilder::aclWhoami); + } + @Override public Mono append(K key, V value) { return createMono(() -> commandBuilder.append(key, value)); @@ -649,6 +726,12 @@ public Mono expire(K key, long seconds) { return createMono(() -> commandBuilder.expire(key, seconds)); } + @Override + public Mono expire(K key, Duration seconds) { + LettuceAssert.notNull(seconds, "Timeout must not be null"); + return expire(key, seconds.toMillis() / 1000); + } + @Override public Mono expireat(K key, long timestamp) { return createMono(() -> commandBuilder.expireat(key, timestamp)); @@ -656,9 +739,16 @@ public Mono expireat(K key, long timestamp) { @Override public Mono expireat(K key, Date timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); return expireat(key, timestamp.getTime() / 1000); } + @Override + public Mono expireat(K key, Instant timestamp) { + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + return expireat(key, timestamp.toEpochMilli() / 1000); + } + @Override public void flushCommands() { connection.flushCommands(); @@ -764,6 +854,23 @@ protected Flux> georadiusbymember_ro(K key, V member, double distan () -> commandBuilder.georadiusbymember(GEORADIUSBYMEMBER_RO, key, member, distance, unit.name(), geoArgs)); } + @Override + public Flux geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate) { + return createDissolvingFlux(() -> commandBuilder.geosearch(key, reference, predicate)); + } + + @Override + public Flux> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, + GeoArgs geoArgs) { + return createDissolvingFlux(() -> commandBuilder.geosearch(key, reference, predicate, geoArgs)); + } + + @Override + public Mono geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, + GeoArgs geoArgs, boolean storeDist) { + return createMono(() -> commandBuilder.geosearchstore(destination, key, reference, predicate, geoArgs, storeDist)); + } + @Override public Mono get(K key) { return createMono(() -> commandBuilder.get(key)); @@ -1137,8 +1244,9 @@ public Mono pexpire(K key, long milliseconds) { } @Override - public Mono pexpireat(K key, Date timestamp) { - return pexpireat(key, timestamp.getTime()); + public Mono pexpire(K key, Duration milliseconds) { + LettuceAssert.notNull(milliseconds, "Timeout must not be null"); + return pexpire(key, milliseconds.toMillis()); } @Override @@ -1146,6 +1254,16 @@ public Mono pexpireat(K key, long timestamp) { return createMono(() -> commandBuilder.pexpireat(key, timestamp)); } + @Override + public Mono pexpireat(K key, Date timestamp) { + return pexpireat(key, timestamp.getTime()); + } + + @Override + public Mono pexpireat(K key, Instant timestamp) { + return pexpireat(key, timestamp.toEpochMilli()); + } + @Override public Mono pfadd(K key, V... values) { return createMono(() -> commandBuilder.pfadd(key, values)); diff --git a/src/main/java/io/lettuce/core/AclCategory.java b/src/main/java/io/lettuce/core/AclCategory.java new file mode 100644 index 0000000000..db4066d6b3 --- /dev/null +++ b/src/main/java/io/lettuce/core/AclCategory.java @@ -0,0 +1,115 @@ +package io.lettuce.core; + +/** + * Enum object describing Redis ACL categories. + * + * @author Mikhael Sokolov + * @since 6.1 + */ +public enum AclCategory { + + /** + * command affects keyspace + */ + KEYSPACE, + + /** + * read command + */ + READ, + + /** + * write command + */ + WRITE, + + /** + * command for sets + */ + SET, + + /** + * command for sorted sets + */ + SORTEDSET, + + /** + * command for lists + */ + LIST, + + /** + * command for hash ops + */ + HASH, + + /** + * command for strings + */ + STRING, + + /** + * command for bitmaps + */ + BITMAP, + + /** + * command for hyperloglog + */ + HYPERLOGLOG, + + /** + * geo command + */ + GEO, + + /** + * streaming command + */ + STREAM, + + /** + * pubsub command + */ + PUBSUB, + + /** + * admin command + */ + ADMIN, + + /** + * fast command + */ + FAST, + + /** + * slow command + */ + SLOW, + + /** + * blocking command + */ + BLOCKING, + + /** + * dangerous command + */ + DANGEROUS, + + /** + * connection-establishing command + */ + CONNECTION, + + /** + * transactional command + */ + TRANSACTION, + + /** + * scripting command + */ + SCRIPTING +} diff --git a/src/main/java/io/lettuce/core/AclSetuserArgs.java b/src/main/java/io/lettuce/core/AclSetuserArgs.java new file mode 100644 index 0000000000..b03570632a --- /dev/null +++ b/src/main/java/io/lettuce/core/AclSetuserArgs.java @@ -0,0 +1,713 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core; + +import static io.lettuce.core.protocol.CommandKeyword.*; + +import java.util.ArrayList; +import java.util.List; + +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.ProtocolKeyword; + +/** + * Argument list builder for the Redis ACL SETUSER command. + *

+ * {@link AclSetuserArgs} is a mutable object and instances should be used only once to avoid shared mutable state. + * + * @author Mikhael Sokolov + * @since 6.1 + */ +public class AclSetuserArgs implements CompositeArgument { + + private boolean active; + + private List keyPatterns; + + private boolean allKeys; + + private boolean resetKeys; + + private List channelPatterns; + + private boolean allChannels; + + private boolean resetChannels; + + private List addCommands; + + private boolean allCommands; + + private List removeCommands; + + private boolean noCommands; + + private List addCategories; + + private List removeCategories; + + private boolean nopass; + + private List addPasswords; + + private List addHashedPasswords; + + private List removePasswords; + + private List removeHashedPasswords; + + private boolean reset; + + + /** + * Builder entry points for {@link AclSetuserArgs}. + */ + public static class Builder { + + /** + * Utility constructor. + */ + private Builder() { + } + + /** + * Creates new {@link AclSetuserArgs} and set user active. + * + * @return new {@link AclSetuserArgs} and set user active. + * @see AclSetuserArgs#on() + */ + public static AclSetuserArgs on() { + return new AclSetuserArgs().on(); + } + + /** + * Creates new {@link AclSetuserArgs} and set user inactive. + * + * @return new {@link AclSetuserArgs} and set user inactive. + * @see AclSetuserArgs#off() + */ + public static AclSetuserArgs off() { + return new AclSetuserArgs().off(); + } + + /** + * Creates new {@link AclSetuserArgs} and adds accessible key pattern. + * + * @param keyPattern accessible key pattern + * @return new {@link AclSetuserArgs} and adds accessible key pattern. + * @see AclSetuserArgs#keyPattern(String) + */ + public static AclSetuserArgs keyPattern(String keyPattern) { + return new AclSetuserArgs().keyPattern(keyPattern); + } + + /** + * Creates new {@link AclSetuserArgs} and allows the user to access all the keys. + * + * @return new {@link AclSetuserArgs} and allows the user to access all the keys. + * @see AclSetuserArgs#allKeys() + */ + public static AclSetuserArgs allKeys() { + return new AclSetuserArgs().allKeys(); + } + + /** + * Creates new {@link AclSetuserArgs} and removes all the key patterns from the list of key patterns the user can access. + * + * @return new {@link AclSetuserArgs} and removes all the key patterns from the list of key patterns the user can access. + * @see AclSetuserArgs#resetKeys() + */ + public static AclSetuserArgs resetKeys() { + return new AclSetuserArgs().resetKeys(); + } + + /** + * Creates new {@link AclSetuserArgs} and adds accessible channel pattern. + * + * @param channelPattern accessible channel pattern + * @return new {@link AclSetuserArgs} and adds accessible channel pattern. + * @see AclSetuserArgs#channelPattern(String) + */ + public static AclSetuserArgs channelPattern(String channelPattern) { + return new AclSetuserArgs().channelPattern(channelPattern); + } + + /** + * Creates new {@link AclSetuserArgs} and allows the user to access all the Pub/Sub channels. + * + * @return new {@link AclSetuserArgs} and allows the user to access all the Pub/Sub channels. + * @see AclSetuserArgs#allChannels() + */ + public static AclSetuserArgs allChannels() { + return new AclSetuserArgs().allChannels(); + } + + /** + * Creates new {@link AclSetuserArgs} and removes all channel patterns from the list of Pub/Sub channel patterns the user can access. + * + * @return new {@link AclSetuserArgs} and removes all channel patterns from the list of Pub/Sub channel patterns the user can access. + * @see AclSetuserArgs#resetChannels() + */ + public static AclSetuserArgs resetChannels() { + return new AclSetuserArgs().resetChannels(); + } + + /** + * Creates new {@link AclSetuserArgs} and adds this command to the list of the commands the user can call. + * + * @param command accessible command + * @return new {@link AclSetuserArgs} and adds this command to the list of the commands the user can call. + * @see AclSetuserArgs#addCommand(CommandType) + */ + public static AclSetuserArgs addCommand(CommandType command) { + return new AclSetuserArgs().addCommand(command); + } + + /** + * Creates new {@link AclSetuserArgs} and adds the specified command to the list of the commands the user can execute. + * + * @param command accessible command + * @param subCommand accessible subcommand + * @return new {@link AclSetuserArgs} and adds the specified command to the list of the commands the user can execute. + * @see AclSetuserArgs#addCommand(CommandType, ProtocolKeyword) + */ + public static AclSetuserArgs addCommand(CommandType command, ProtocolKeyword subCommand) { + return new AclSetuserArgs().addCommand(command, subCommand); + } + + /** + * Creates new {@link AclSetuserArgs} and adds all the commands there are in the server. + * + * @return new {@link AclSetuserArgs} and adds all the commands there are in the server. + * @see AclSetuserArgs#allCommands() + */ + public static AclSetuserArgs allCommands() { + return new AclSetuserArgs().allCommands(); + } + + /** + * Creates new {@link AclSetuserArgs} and removes this command to the list of the commands the user can call. + * + * @param command inaccessible command + * @return new {@link AclSetuserArgs} and removes this command to the list of the commands the user can call. + * @see AclSetuserArgs#removeCommand(CommandType) + */ + public static AclSetuserArgs removeCommand(CommandType command) { + return new AclSetuserArgs().removeCommand(command); + } + + /** + * Creates new {@link AclSetuserArgs} and removes the specified command to the list of the commands the user can execute. + * + * @param command inaccessible command + * @param subCommand inaccessible subcommand + * @return new {@link AclSetuserArgs} and removes the specified command to the list of the commands the user can execute. + * @see AclSetuserArgs#removeCommand(CommandType, ProtocolKeyword) + */ + public static AclSetuserArgs removeCommand(CommandType command, ProtocolKeyword subCommand) { + return new AclSetuserArgs().removeCommand(command, subCommand); + } + + /** + * Creates new {@link AclSetuserArgs} and removes all the commands the user can execute. + * + * @return new {@link AclSetuserArgs} and removes all the commands the user can execute. + * @see AclSetuserArgs#noCommands() + */ + public static AclSetuserArgs noCommands() { + return new AclSetuserArgs().noCommands(); + } + + /** + * Creates new {@link AclSetuserArgs} and adds all the commands in the specified category to the list of commands the user is able to execute. + * + * @param category specified category + * @return new {@link AclSetuserArgs} and adds all the commands in the specified category to the list of commands the user is able to execute. + * @see AclSetuserArgs#addCategory(AclCategory) + */ + public static AclSetuserArgs addCategory(AclCategory category) { + return new AclSetuserArgs().addCategory(category); + } + + /** + * Creates new {@link AclSetuserArgs} and removes all the commands in the specified category to the list of commands the user is able to execute. + * + * @param category specified category + * @return new {@link AclSetuserArgs} and removes all the commands in the specified category to the list of commands the user is able to execute. + * @see AclSetuserArgs#removeCategory(AclCategory) + */ + public static AclSetuserArgs removeCategory(AclCategory category) { + return new AclSetuserArgs().removeCategory(category); + } + + /** + * Creates new {@link AclSetuserArgs} and sets the user as a "no password". + * + * @return new {@link AclSetuserArgs} and sets the user as a "no password". + * @see AclSetuserArgs#nopass() + */ + public static AclSetuserArgs nopass() { + return new AclSetuserArgs().nopass(); + } + + /** + * Creates new {@link AclSetuserArgs} and adds the specified clear text password as an hashed password in the list of the users passwords. + * + * @param password clear text password + * @return new {@link AclSetuserArgs} and adds the specified clear text password as an hashed password in the list of the users passwords. + * @see AclSetuserArgs#addPassword(String) + */ + public static AclSetuserArgs addPassword(String password) { + return new AclSetuserArgs().addPassword(password); + } + + /** + * Creates new {@link AclSetuserArgs} and adds the specified hashed password to the list of user passwords. + * + * @param hashedPassword hashed password + * @return new {@link AclSetuserArgs} and adds the specified hashed password to the list of user passwords. + * @see AclSetuserArgs#addHashedPassword(String) + */ + public static AclSetuserArgs addHashedPassword(String hashedPassword) { + return new AclSetuserArgs().addHashedPassword(hashedPassword); + + } + + /** + * Creates new {@link AclSetuserArgs} and removes the specified clear text password as an hashed password in the list of the users passwords. + * + * @param password clear text password + * @return new {@link AclSetuserArgs} and removes the specified clear text password as an hashed password in the list of the users passwords. + * @see AclSetuserArgs#removePassword(String) + */ + public static AclSetuserArgs removePassword(String password) { + return new AclSetuserArgs().removePassword(password); + + } + + /** + * Creates new {@link AclSetuserArgs} and removes the specified hashed password to the list of user passwords. + * + * @param hashedPassword hashed password + * @return new {@link AclSetuserArgs} and removes the specified hashed password to the list of user passwords. + * @see AclSetuserArgs#removeHashedPassword(String) + */ + public static AclSetuserArgs removeHashedPassword(String hashedPassword) { + return new AclSetuserArgs().removeHashedPassword(hashedPassword); + + } + + /** + * Creates new {@link AclSetuserArgs} and removes any capability from the user. + * + * @return new {@link AclSetuserArgs} and removes any capability from the user. + * @see AclSetuserArgs#reset() + */ + public static AclSetuserArgs reset() { + return new AclSetuserArgs().reset(); + } + } + + /** + * Set user active. + * + * @return {@code this} + */ + public AclSetuserArgs on() { + this.active = true; + return this; + } + + /** + * Set user inactive. + * + * @return {@code this} + */ + public AclSetuserArgs off() { + this.active = false; + return this; + } + + /** + * Adds accessible key pattern. + * + * @param keyPattern accessible key pattern + * @return {@code this} + */ + public AclSetuserArgs keyPattern(String keyPattern) { + if (this.keyPatterns == null) { + this.keyPatterns = new ArrayList<>(); + } + this.keyPatterns.add(keyPattern); + return this; + } + + /** + * Allows the user to access all the keys. + * + * @return {@code this} + */ + public AclSetuserArgs allKeys() { + this.allKeys = true; + return this; + } + + /** + * Removes all the key patterns from the list of key patterns the user can access. + * + * @return {@code this} + */ + public AclSetuserArgs resetKeys() { + this.resetKeys = true; + return this; + } + + /** + * Adds accessible channel pattern. + * + * @param channelPattern accessible channel pattern + * @return {@code this} + */ + public AclSetuserArgs channelPattern(String channelPattern) { + if (this.channelPatterns == null) { + this.channelPatterns = new ArrayList<>(); + } + this.channelPatterns.add(channelPattern); + return this; + } + + /** + * Allows the user to access all the Pub/Sub channels. + * + * @return {@code this} + */ + public AclSetuserArgs allChannels() { + this.allChannels = true; + return this; + } + + /** + * Removes all channel patterns from the list of Pub/Sub channel patterns the user can access. + * + * @return {@code this} + */ + public AclSetuserArgs resetChannels() { + this.resetChannels = true; + return this; + } + + /** + * Adds this command to the list of the commands the user can call. + * + * @param command accessible command + * @return {@code this} + */ + public AclSetuserArgs addCommand(CommandType command) { + return addCommand(command, null); + } + + /** + * Adds all the commands there are in the server. + * + * @return {@code this} + */ + public AclSetuserArgs addCommand(CommandType command, ProtocolKeyword subCommand) { + if (this.addCommands == null) { + this.addCommands = new ArrayList<>(); + } + this.addCommands.add(new CommandSubcommandPair(command, subCommand)); + return this; + } + + /** + * Adds all the commands there are in the server. + * + * @return {@code this} + */ + public AclSetuserArgs allCommands() { + this.allCommands = true; + return this; + } + + /** + * Removes this command to the list of the commands the user can call. + * + * @param command inaccessible command + * @return {@code this} + */ + public AclSetuserArgs removeCommand(CommandType command) { + return removeCommand(command, null); + } + + /** + * Removes the specified command to the list of the commands the user can execute. + * + * @param command inaccessible command + * @param subCommand inaccessible subcommand + * @return {@code this} + */ + public AclSetuserArgs removeCommand(CommandType command, ProtocolKeyword subCommand) { + if (removeCommands == null) { + this.removeCommands = new ArrayList<>(); + } + this.removeCommands.add(new CommandSubcommandPair(command, subCommand)); + return this; + } + + /** + * Removes all the commands the user can execute. + * + * @return {@code this} + */ + public AclSetuserArgs noCommands() { + this.noCommands = false; + return this; + } + + /** + * Adds all the commands in the specified category to the list of commands the user is able to execute. + * + * @param category specified category + * @return {@code this} + */ + public AclSetuserArgs addCategory(AclCategory category) { + if (this.addCategories == null) { + this.addCategories = new ArrayList<>(); + } + this.addCategories.add(category); + return this; + } + + /** + * Removes all the commands in the specified category to the list of commands the user is able to execute. + * + * @param category specified category + * @return {@code this} + */ + public AclSetuserArgs removeCategory(AclCategory category) { + if (this.removeCategories == null) { + this.removeCategories = new ArrayList<>(); + } + this.removeCategories.add(category); + return this; + } + + /** + * Sets the user as a "no password". + * + * @return {@code this} + */ + public AclSetuserArgs nopass() { + this.nopass = true; + return this; + } + + /** + * Adds the specified clear text password as an hashed password in the list of the users passwords. + * + * @param password clear text password + * @return {@code this} + */ + public AclSetuserArgs addPassword(String password) { + if (this.addPasswords == null) { + this.addPasswords = new ArrayList<>(); + } + this.addPasswords.add(password); + return this; + } + + /** + * Adds the specified hashed password to the list of user passwords. + * + * @param hashedPassword hashed password + * @return {@code this} + */ + public AclSetuserArgs addHashedPassword(String hashedPassword) { + if (this.addHashedPasswords == null) { + this.addHashedPasswords = new ArrayList<>(); + } + this.addHashedPasswords.add(hashedPassword); + return this; + } + + /** + * Removes the specified clear text password as an hashed password in the list of the users passwords. + * + * @param password clear text password + * @return {@code this} + */ + public AclSetuserArgs removePassword(String password) { + if (this.removePasswords == null) { + this.removePasswords = new ArrayList<>(); + } + this.removePasswords.add(password); + return this; + } + + /** + * Removes the specified hashed password to the list of user passwords. + * + * @param hashedPassword hashed password + * @return {@code this} + */ + public AclSetuserArgs removeHashedPassword(String hashedPassword) { + if (this.removeHashedPasswords == null) { + this.removeHashedPasswords = new ArrayList<>(); + } + this.removeHashedPasswords.add(hashedPassword); + return this; + } + + /** + * Removes any capability from the user. + * + * @return {@code this} + */ + public AclSetuserArgs reset() { + this.reset = true; + return this; + } + + @Override + public void build(CommandArgs args) { + if (reset) { + args.add(RESET); + return; + } + + if (active) { + args.add(ON); + } else { + args.add(OFF); + } + + if (allKeys) { + args.add(ALLKEYS); + } + + if (resetKeys) { + args.add(RESETKEYS); + } + + if (keyPatterns != null) { + for (String glob : keyPatterns) { + args.add("~" + glob); + } + } + + if (allChannels) { + args.add(ALLCHANNELS); + } + + if (resetChannels) { + args.add(RESETCHANNELS); + } + + if (channelPatterns != null) { + for (String glob : channelPatterns) { + args.add("&" + glob); + } + } + + if (allCommands) { + args.add(ALLCOMMANDS); + } + + if (addCommands != null) { + for (CommandSubcommandPair command : addCommands) { + if (command.getSubCommand() == null) { + args.add("+" + command.getCommand().name()); + } else { + args.add("+" + command.getCommand().name() + "|" + command.getSubCommand().name()); + } + } + } + + if (noCommands) { + args.add(NOCOMMANDS); + } + + if (removeCommands != null) { + for (CommandSubcommandPair command : removeCommands) { + if (command.getSubCommand() == null) { + args.add("-" + command.getCommand().name()); + } else { + args.add("-" + command.getCommand().name() + "|" + command.getSubCommand().name()); + } + } + } + + if (removeCategories != null) { + for (AclCategory category : addCategories) { + args.add("+@" + category.name()); + } + } + + if (removeCategories != null) { + for (AclCategory category : removeCategories) { + args.add("-@" + category.name()); + } + } + + if (nopass) { + args.add(NOPASS); + } + + if (addPasswords != null) { + for (String password : addPasswords) { + args.add(">" + password); + } + } + + if (addHashedPasswords != null) { + for (String password : addHashedPasswords) { + args.add("#" + password); + } + } + + if (removePasswords != null) { + for (String password : removePasswords) { + args.add("<" + password); + } + } + + if (removeHashedPasswords != null) { + for (String password : removeHashedPasswords) { + args.add("!" + password); + } + } + } + + private static class CommandSubcommandPair { + + private final CommandType command; + private final ProtocolKeyword subCommand; + + private CommandSubcommandPair(CommandType command, ProtocolKeyword subCommand) { + this.command = command; + this.subCommand = subCommand; + } + + public CommandType getCommand() { + return command; + } + + public ProtocolKeyword getSubCommand() { + return subCommand; + } + } +} diff --git a/src/main/java/io/lettuce/core/ConcurrentLruCache.java b/src/main/java/io/lettuce/core/ConcurrentLruCache.java new file mode 100644 index 0000000000..6dad47229a --- /dev/null +++ b/src/main/java/io/lettuce/core/ConcurrentLruCache.java @@ -0,0 +1,182 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +import io.lettuce.core.internal.LettuceAssert; + +/** + * Simple LRU (Least Recently Used) cache, bounded by a specified cache limit. + * + *

+ * This implementation is backed by a {@code ConcurrentHashMap} for storing the cached values and a + * {@code ConcurrentLinkedDeque} for ordering the keys and choosing the least recently used key when the cache is at full + * capacity. + * + * @param the type of the key used for cache retrieval + * @param the type of the cached values + * @see #get + */ +class ConcurrentLruCache { + + private final int sizeLimit; + + private final Function generator; + + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + private final ConcurrentLinkedDeque queue = new ConcurrentLinkedDeque<>(); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private volatile int size; + + /** + * Create a new cache instance with the given limit and generator function. + * + * @param sizeLimit the maximum number of entries in the cache (0 indicates no caching, always generating a new value) + * @param generator a function to generate a new value for a given key + */ + public ConcurrentLruCache(int sizeLimit, Function generator) { + + LettuceAssert.isTrue(sizeLimit >= 0, "Cache size limit must not be negative"); + LettuceAssert.notNull(generator, "Generator function must not be null"); + + this.sizeLimit = sizeLimit; + this.generator = generator; + } + + /** + * Retrieve an entry from the cache, potentially triggering generation of the value. + * + * @param key the key to retrieve the entry for + * @return the cached or newly generated value + */ + public V get(K key) { + + if (this.sizeLimit == 0) { + return this.generator.apply(key); + } + + V cached = this.cache.get(key); + if (cached != null) { + if (this.size < this.sizeLimit) { + return cached; + } + this.lock.readLock().lock(); + try { + if (this.queue.removeLastOccurrence(key)) { + this.queue.offer(key); + } + return cached; + } finally { + this.lock.readLock().unlock(); + } + } + + this.lock.writeLock().lock(); + try { + // Retrying in case of concurrent reads on the same key + cached = this.cache.get(key); + if (cached != null) { + if (this.queue.removeLastOccurrence(key)) { + this.queue.offer(key); + } + return cached; + } + // Generate value first, to prevent size inconsistency + V value = this.generator.apply(key); + if (this.size == this.sizeLimit) { + K leastUsed = this.queue.poll(); + if (leastUsed != null) { + this.cache.remove(leastUsed); + } + } + this.queue.offer(key); + this.cache.put(key, value); + this.size = this.cache.size(); + return value; + } finally { + this.lock.writeLock().unlock(); + } + } + + /** + * Determine whether the given key is present in this cache. + * + * @param key the key to check for + * @return {@code true} if the key is present, {@code false} if there was no matching key + */ + public boolean contains(K key) { + return this.cache.containsKey(key); + } + + /** + * Immediately remove the given key and any associated value. + * + * @param key the key to evict the entry for + * @return {@code true} if the key was present before, {@code false} if there was no matching key + */ + public boolean remove(K key) { + this.lock.writeLock().lock(); + try { + boolean wasPresent = (this.cache.remove(key) != null); + this.queue.remove(key); + this.size = this.cache.size(); + return wasPresent; + } finally { + this.lock.writeLock().unlock(); + } + } + + /** + * Immediately remove all entries from this cache. + */ + public void clear() { + this.lock.writeLock().lock(); + try { + this.cache.clear(); + this.queue.clear(); + this.size = 0; + } finally { + this.lock.writeLock().unlock(); + } + } + + /** + * Return the current size of the cache. + * + * @see #sizeLimit() + */ + public int size() { + return this.size; + } + + /** + * Return the the maximum number of entries in the cache (0 indicates no caching, always generating a new value). + * + * @see #size() + */ + public int sizeLimit() { + return this.sizeLimit; + } + +} diff --git a/src/main/java/io/lettuce/core/CopyArgs.java b/src/main/java/io/lettuce/core/CopyArgs.java index aaaf3cf4d2..737b4d097e 100644 --- a/src/main/java/io/lettuce/core/CopyArgs.java +++ b/src/main/java/io/lettuce/core/CopyArgs.java @@ -25,7 +25,7 @@ * {@link CopyArgs} is a mutable object and instances should be used only once to avoid shared mutable state. * * @author Bartek Kowalczyk - * @since 6.2 + * @since 6.1 */ public class CopyArgs implements CompositeArgument { diff --git a/src/main/java/io/lettuce/core/GeoArgs.java b/src/main/java/io/lettuce/core/GeoArgs.java index d082cd558f..39ac0b113a 100644 --- a/src/main/java/io/lettuce/core/GeoArgs.java +++ b/src/main/java/io/lettuce/core/GeoArgs.java @@ -18,6 +18,7 @@ import io.lettuce.core.internal.LettuceAssert; import io.lettuce.core.protocol.CommandArgs; import io.lettuce.core.protocol.CommandKeyword; +import io.lettuce.core.protocol.ProtocolKeyword; /** * @@ -38,6 +39,8 @@ public class GeoArgs implements CompositeArgument { private Long count; + private boolean any; + private Sort sort = Sort.none; /** @@ -145,10 +148,24 @@ public GeoArgs withHash() { * @return {@code this} {@link GeoArgs}. */ public GeoArgs withCount(long count) { + return withCount(count, false); + } + + /** + * Limit results to {@code count} entries. + * + * @param count number greater 0. + * @param any whether to complete the command as soon as enough matches are found, so the results may not be the ones + * closest to the specified point. + * @return {@code this} {@link GeoArgs}. + * @since 6.1 + */ + public GeoArgs withCount(long count, boolean any) { LettuceAssert.isTrue(count > 0, "Count must be greater 0"); this.count = count; + this.any = any; return this; } @@ -232,7 +249,7 @@ public enum Sort { /** * Supported geo unit. */ - public enum Unit { + public enum Unit implements ProtocolKeyword { /** * meter. @@ -253,6 +270,17 @@ public enum Unit { * mile. */ mi; + + private final byte[] asBytes; + + Unit() { + asBytes = name().getBytes(); + } + + @Override + public byte[] getBytes() { + return asBytes; + } } public void build(CommandArgs args) { @@ -275,6 +303,10 @@ public void build(CommandArgs args) { if (count != null) { args.add(CommandKeyword.COUNT).add(count); + + if (any) { + args.add("ANY"); + } } } diff --git a/src/main/java/io/lettuce/core/GeoSearch.java b/src/main/java/io/lettuce/core/GeoSearch.java new file mode 100644 index 0000000000..50486c96d5 --- /dev/null +++ b/src/main/java/io/lettuce/core/GeoSearch.java @@ -0,0 +1,159 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core; + +import io.lettuce.core.internal.LettuceAssert; +import io.lettuce.core.protocol.CommandArgs; + +/** + * Utility to create {@link GeoPredicate} and {@link GeoRef} objects to be used with {@code GEOSEARCH}. + * + * @author Mark Paluch + * @since 6.1 + */ +public final class GeoSearch { + + /** + * Create a {@link GeoRef} from a Geo set {@code member}. + * + * @param member the Geo set member to use as search reference starting point. + * @return the {@link GeoRef}. + */ + public static GeoRef fromMember(K member) { + LettuceAssert.notNull(member, "Reference member must not be null"); + return new FromMember<>(member); + } + + /** + * Create a {@link GeoRef} from WGS84 coordinates {@code longitude} and {@code latitude}. + * + * @param longitude the longitude coordinate according to WGS84. + * @param latitude the latitude coordinate according to WGS84. + * @return the {@link GeoRef}. + */ + public static GeoRef fromCoordinates(double longitude, double latitude) { + return (GeoRef) new FromCoordinates(longitude, latitude); + } + + /** + * Create a {@link GeoPredicate} by specifying a radius {@code distance} and {@link GeoArgs.Unit}. + * + * @param distance the radius. + * @param unit size unit. + * @return the {@link GeoPredicate} for the specified radius. + */ + public static GeoPredicate byRadius(double distance, GeoArgs.Unit unit) { + return new Radius(distance, unit); + } + + /** + * Create a {@link GeoPredicate} by specifying a box of the size {@code width}, {@code height} and {@link GeoArgs.Unit}. + * + * @param width box width. + * @param height box height. + * @param unit size unit. + * @return the {@link GeoPredicate} for the specified box. + */ + public static GeoPredicate byBox(double width, double height, GeoArgs.Unit unit) { + return new Box(width, height, unit); + } + + /** + * Geo reference specifying a search starting point. + * + * @param + */ + public interface GeoRef extends CompositeArgument { + + } + + static class FromMember implements GeoRef { + + final K member; + + public FromMember(K member) { + this.member = member; + } + + @Override + @SuppressWarnings("unchecked") + public void build(CommandArgs args) { + args.add("FROMMEMBER").addKey((K) member); + } + + } + + static class FromCoordinates implements GeoRef { + + final double longitude, latitude; + + public FromCoordinates(double longitude, double latitude) { + this.longitude = longitude; + this.latitude = latitude; + } + + @Override + public void build(CommandArgs args) { + args.add("FROMLONLAT").add(longitude).add(latitude); + } + + } + + /** + * Geo predicate specifying a search scope. + */ + public interface GeoPredicate extends CompositeArgument { + + } + + static class Radius implements GeoPredicate { + + final double distance; + + final GeoArgs.Unit unit; + + public Radius(double distance, GeoArgs.Unit unit) { + this.distance = distance; + this.unit = unit; + } + + @Override + public void build(CommandArgs args) { + args.add("BYRADIUS").add(distance).add(unit); + } + + } + + static class Box implements GeoPredicate { + + final double width, height; + + final GeoArgs.Unit unit; + + public Box(double width, double height, GeoArgs.Unit unit) { + this.width = width; + this.height = height; + this.unit = unit; + } + + @Override + public void build(CommandArgs args) { + args.add("BYBOX").add(width).add(height).add(unit); + } + + } + +} diff --git a/src/main/java/io/lettuce/core/ReadFrom.java b/src/main/java/io/lettuce/core/ReadFrom.java index 1b779f8d0e..165434d031 100644 --- a/src/main/java/io/lettuce/core/ReadFrom.java +++ b/src/main/java/io/lettuce/core/ReadFrom.java @@ -16,6 +16,7 @@ package io.lettuce.core; import java.util.List; +import java.util.regex.Pattern; import io.lettuce.core.internal.LettuceStrings; import io.lettuce.core.models.role.RedisNodeDescription; @@ -26,24 +27,19 @@ * @author Mark Paluch * @author Ryosuke Hasebe * @author Omer Cilingir + * @author Yohei Ueki * @since 4.0 */ public abstract class ReadFrom { /** * Setting to read from the upstream only. - * - * @deprecated since 6.0 in favor of {@link #UPSTREAM}. */ - @Deprecated public static final ReadFrom MASTER = new ReadFromImpl.ReadFromUpstream(); /** * Setting to read preferred from the upstream and fall back to a replica if the master is not available. - * - * @deprecated since 6.0 in favor of {@link #UPSTREAM_PREFERRED}. */ - @Deprecated public static final ReadFrom MASTER_PREFERRED = new ReadFromImpl.ReadFromUpstreamPreferred(); /** @@ -110,6 +106,42 @@ public abstract class ReadFrom { */ public static final ReadFrom ANY_REPLICA = new ReadFromImpl.ReadFromAnyReplica(); + /** + * Setting to read from any node in the subnets. + * + * @param cidrNotations CIDR-block notation strings, e.g., "192.168.0.0/16", "2001:db8:abcd:0000::/52". Must not be + * {@code null}. + * @return an instance of {@link ReadFromImpl.ReadFromSubnet}. + * @since 6.1 + */ + public static ReadFrom subnet(String... cidrNotations) { + return new ReadFromImpl.ReadFromSubnet(cidrNotations); + } + + /** + * Read from any node that has {@link RedisURI} matching with the given pattern. + * + * @param pattern regex pattern, e.g., {@code Pattern.compile(".*region-1.*")}. Must not be {@code null}. + * @return an instance of {@link ReadFromImpl.ReadFromRegex}. + * @since 6.1 + */ + public static ReadFrom regex(Pattern pattern) { + return regex(pattern, false); + } + + /** + * Read from any node that has {@link RedisURI} matching with the given pattern. + * + * @param pattern regex pattern, e.g., {@code Pattern.compile(".*region-1.*")}. Must not be {@code null}. + * @param orderSensitive {@code true} to attempt reads in the order of hosts returned by {@link ReadFrom#select(Nodes)}; + * {@code false} to apply randomization. + * @return an instance of {@link ReadFromImpl.ReadFromRegex}. + * @since 6.1 + */ + public static ReadFrom regex(Pattern pattern, boolean orderSensitive) { + return new ReadFromImpl.ReadFromRegex(pattern, orderSensitive); + } + /** * Chooses the nodes from the matching Redis nodes that match this read selector. * @@ -178,6 +210,14 @@ public static ReadFrom valueOf(String name) { return ANY_REPLICA; } + if (name.equalsIgnoreCase("subnet")) { + throw new IllegalArgumentException("subnet must be created via ReadFrom#subnet"); + } + + if (name.equalsIgnoreCase("regex")) { + throw new IllegalArgumentException("regex must be created via ReadFrom#regex"); + } + throw new IllegalArgumentException("ReadFrom " + name + " not supported"); } diff --git a/src/main/java/io/lettuce/core/ReadFromImpl.java b/src/main/java/io/lettuce/core/ReadFromImpl.java index 35cec6e7d9..4e21a11953 100644 --- a/src/main/java/io/lettuce/core/ReadFromImpl.java +++ b/src/main/java/io/lettuce/core/ReadFromImpl.java @@ -15,19 +15,25 @@ */ package io.lettuce.core; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Predicate; +import java.util.regex.Pattern; +import io.lettuce.core.internal.LettuceAssert; import io.lettuce.core.internal.LettuceLists; +import io.lettuce.core.internal.LettuceStrings; import io.lettuce.core.models.role.RedisNodeDescription; +import io.netty.util.NetUtil; /** * Collection of common read setting implementations. * * @author Mark Paluch * @author Omer Cilingir + * @author Yohei Ueki * @since 4.0 */ class ReadFromImpl { @@ -129,6 +135,244 @@ public ReadFromAnyReplica() { } + /** + * Read from any node in the subnets. This class does not provide DNS resolution and supports only IP address style + * {@link RedisURI} i.e. unavailable when using {@link io.lettuce.core.masterreplica.MasterReplica} with static setup + * (provided hosts) and Redis Sentinel with {@literal announce-hostname yes}. Both IPv4 and IPv6 style subnets are supported + * but they never match with IP addresses of different version. + * + * @since 6.1 + */ + static final class ReadFromSubnet extends ReadFrom { + + private static final int CACHE_SIZE = 48; + + private final List rules = new ArrayList<>(); + + private final ConcurrentLruCache ipv4AddressCache = new ConcurrentLruCache<>(CACHE_SIZE, + Ipv4SubnetRule::toInt); + + private final ConcurrentLruCache ipv6AddressCache = new ConcurrentLruCache<>(CACHE_SIZE, + Ipv6SubnetRule::toBigInteger); + + /** + * @param cidrNotations CIDR-block notation strings, e.g., "192.168.0.0/16" or "2001:db8:abcd:0000::/52". + */ + ReadFromSubnet(String... cidrNotations) { + + LettuceAssert.notEmpty(cidrNotations, "CIDR notations must not be empty"); + + for (String cidrNotation : cidrNotations) { + rules.add(createSubnetRule(cidrNotation)); + } + } + + @Override + public List select(Nodes nodes) { + + List result = new ArrayList<>(nodes.getNodes().size()); + + for (RedisNodeDescription node : nodes) { + + if (test(node)) { + result.add(node); + + } + } + + return result; + } + + private boolean test(RedisNodeDescription node) { + + for (SubnetRule rule : rules) { + if (rule.isInSubnet(node.getUri().getHost())) { + return true; + } + } + + return false; + } + + interface SubnetRule { + + boolean isInSubnet(String ipAddress); + + } + + SubnetRule createSubnetRule(String cidrNotation) { + + String[] parts = cidrNotation.split("/"); + + LettuceAssert.isTrue(parts.length == 2, "CIDR notation must have exact one '/'"); + + String ipAddress = parts[0]; + int cidrPrefix = Integer.parseInt(parts[1]); + + if (NetUtil.isValidIpV4Address(ipAddress)) { + return new Ipv4SubnetRule(ipAddress, cidrPrefix, ipv4AddressCache); + } else if (NetUtil.isValidIpV6Address(ipAddress)) { + return new Ipv6SubnetRule(ipAddress, cidrPrefix, ipv6AddressCache); + } else { + throw new IllegalArgumentException("Invalid CIDR notation " + cidrNotation); + } + } + + static class Ipv4SubnetRule implements SubnetRule { + + private static final int IPV4_BYTE_COUNT = 4; + + private final int networkAddress; + + private final int subnetMask; + + private final ConcurrentLruCache ipv4AddressCache; + + Ipv4SubnetRule(String ipAddress, int cidrPrefix, ConcurrentLruCache ipv4AddressCache) { + + LettuceAssert.isTrue(NetUtil.isValidIpV4Address(ipAddress), + () -> String.format("Invalid IPv4 IP address %s", ipAddress)); + LettuceAssert.isTrue(0 <= cidrPrefix && cidrPrefix <= 32, + () -> String.format("Invalid CIDR prefix %d", cidrPrefix)); + + this.subnetMask = toSubnetMask(cidrPrefix); + this.networkAddress = toNetworkAddress(ipAddress, this.subnetMask); + this.ipv4AddressCache = ipv4AddressCache; + } + + /** + * return {@code true} if the {@code ipAddress} is in this subnet. If {@code ipAddress} is not valid IPv4 style + * (e.g., IPv6 style) {@code false} is always returned. + */ + @Override + public boolean isInSubnet(String ipAddress) { + + if (LettuceStrings.isEmpty(ipAddress) || !NetUtil.isValidIpV4Address(ipAddress)) { + return false; + } + + Integer address = ipv4AddressCache.get(ipAddress); + + return (address & subnetMask) == networkAddress; + } + + private int toSubnetMask(int cidrPrefix) { + return (int) (-1L << (32 - cidrPrefix)); + } + + private int toNetworkAddress(String ipAddress, int subnetMask) { + return toInt(ipAddress) & subnetMask; + } + + static int toInt(String ipAddress) { + + byte[] octets = NetUtil.createByteArrayFromIpAddressString(ipAddress); + + LettuceAssert.isTrue(octets != null && octets.length == IPV4_BYTE_COUNT, + () -> String.format("Invalid IP address %s", ipAddress)); + + return ((octets[0] & 0xff) << 24) | ((octets[1] & 0xff) << 16) | ((octets[2] & 0xff) << 8) | (octets[3] & 0xff); + } + + } + + static class Ipv6SubnetRule implements SubnetRule { + + private static final int IPV6_BYTE_COUNT = 16; + + private final BigInteger networkAddress; + + private final BigInteger subnetMask; + + private final ConcurrentLruCache ipv6AddressCache; + + public Ipv6SubnetRule(String ipAddress, int cidrPrefix, ConcurrentLruCache ipv6AddressCache) { + + LettuceAssert.isTrue(NetUtil.isValidIpV6Address(ipAddress), + () -> String.format("Invalid IPv6 IP address %s", ipAddress)); + LettuceAssert.isTrue(0 <= cidrPrefix && cidrPrefix <= 128, + () -> String.format("Invalid CIDR prefix %d", cidrPrefix)); + + this.subnetMask = toSubnetMask(cidrPrefix); + this.networkAddress = toNetworkAddress(ipAddress, this.subnetMask); + this.ipv6AddressCache = ipv6AddressCache; + } + + /** + * return {@code true} if the {@code ipAddress} is in this subnet. If {@code ipAddress} is not valid IPv6 style + * (e.g., IPv4 style) {@code false} is always returned. + */ + @Override + public boolean isInSubnet(String ipAddress) { + + if (LettuceStrings.isEmpty(ipAddress) || !NetUtil.isValidIpV6Address(ipAddress)) { + return false; + } + + BigInteger address = ipv6AddressCache.get(ipAddress); + return address.and(subnetMask).equals(networkAddress); + } + + private static BigInteger toSubnetMask(int cidrPrefix) { + return BigInteger.valueOf(-1).shiftLeft(128 - cidrPrefix); + } + + private static BigInteger toNetworkAddress(String ipAddress, BigInteger subnetMask) { + return toBigInteger(ipAddress).and(subnetMask); + } + + static BigInteger toBigInteger(String ipAddress) { + + byte[] octets = NetUtil.createByteArrayFromIpAddressString(ipAddress); + + LettuceAssert.isTrue(octets != null && octets.length == IPV6_BYTE_COUNT, + () -> String.format("Invalid IP address %s", ipAddress)); + + return new BigInteger(octets); + } + + } + + } + + /** + * Read from any node that has {@link RedisURI} matching with the given pattern. + * + * @since 6.1 + */ + static class ReadFromRegex extends ReadFrom { + + private final ReadFrom delegate; + + private final boolean orderSensitive; + + public ReadFromRegex(Pattern pattern, boolean orderSensitive) { + + LettuceAssert.notNull(pattern, "Pattern must not be null"); + + this.orderSensitive = orderSensitive; + + delegate = new UnorderedPredicateReadFromAdapter(redisNodeDescription -> { + String host = redisNodeDescription.getUri().getHost(); + if (LettuceStrings.isEmpty(host)) { + return false; + } + return pattern.matcher(host).matches(); + }); + } + + @Override + public List select(Nodes nodes) { + return delegate.select(nodes); + } + + @Override + protected boolean isOrderSensitive() { + return orderSensitive; + } + + } + /** * {@link Predicate}-based {@link ReadFrom} implementation. * diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java index 1030c22c14..ef360da897 100644 --- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java @@ -19,6 +19,7 @@ import static io.lettuce.core.protocol.CommandKeyword.*; import static io.lettuce.core.protocol.CommandType.*; import static io.lettuce.core.protocol.CommandType.COPY; +import static io.lettuce.core.protocol.CommandType.SAVE; import java.nio.ByteBuffer; import java.util.Arrays; @@ -38,6 +39,7 @@ import io.lettuce.core.protocol.BaseRedisCommandBuilder; import io.lettuce.core.protocol.Command; import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; import io.lettuce.core.protocol.CommandType; import io.lettuce.core.protocol.RedisCommand; @@ -67,6 +69,104 @@ class RedisCommandBuilder extends BaseRedisCommandBuilder { super(codec); } + Command> aclCat() { + CommandArgs args = new CommandArgs<>(codec); + args.add(CAT); + return createCommand(ACL, new EnumSetOutput<>(codec, AclCategory.class, String::toUpperCase, it -> null), args); + } + + Command> aclCat(AclCategory category) { + LettuceAssert.notNull(category, "Category " + MUST_NOT_BE_NULL); + CommandArgs args = new CommandArgs<>(codec); + args.add(CAT).add(category.name().toLowerCase()); + return createCommand(ACL, new EnumSetOutput<>(codec, CommandType.class, String::toUpperCase, it -> null), args); + } + + Command aclDeluser(String... usernames) { + notEmpty(usernames); + CommandArgs args = new CommandArgs<>(codec); + args.add(DELUSER); + for (String username : usernames) { + args.add(username); + } + return createCommand(ACL, new IntegerOutput<>(codec), args); + } + + Command aclGenpass() { + CommandArgs args = new CommandArgs<>(codec); + args.add(GENPASS); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command aclGenpass(int bits) { + CommandArgs args = new CommandArgs<>(codec); + args.add(GENPASS).add(bits); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command> aclGetuser(String username) { + LettuceAssert.notNull(username, "Username " + MUST_NOT_BE_NULL); + CommandArgs args = new CommandArgs<>(codec); + args.add(GETUSER).add(username); + return createCommand(ACL, new NestedMultiOutput<>(codec), args); + } + + Command> aclList() { + CommandArgs args = new CommandArgs<>(codec); + args.add(LIST); + return createCommand(ACL, new StringListOutput<>(codec), args); + } + + Command aclLoad() { + CommandArgs args = new CommandArgs<>(codec); + args.add(LOAD); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command>> aclLog() { + CommandArgs args = new CommandArgs<>(codec); + args.add(LOG); + return new Command(ACL, new ListOfGenericMapsOutput<>(StringCodec.ASCII), args); + } + + Command>> aclLog(int count) { + CommandArgs args = new CommandArgs<>(codec); + args.add(LOG).add(count); + return new Command(ACL, new ListOfGenericMapsOutput<>(StringCodec.ASCII), args); + } + + Command aclLogReset() { + CommandArgs args = new CommandArgs<>(codec); + args.add(LOG).add(RESET); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command aclSave() { + CommandArgs args = new CommandArgs<>(codec); + args.add(CommandKeyword.SAVE); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command aclSetuser(String username, AclSetuserArgs setuserArgs) { + notNullKey(username); + CommandArgs args = new CommandArgs<>(codec); + args.add(SETUSER).add(username); + setuserArgs.build(args); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command> aclUsers() { + CommandArgs args = new CommandArgs<>(codec); + args.add(USERS); + return createCommand(ACL, new StringListOutput<>(codec), args); + } + + Command aclWhoami() { + CommandArgs args = new CommandArgs<>(codec); + args.add(WHOAMI); + return createCommand(ACL, new StatusOutput<>(codec), args); + } + Command append(K key, V value) { notNullKey(key); @@ -811,7 +911,6 @@ Command> georadius(CommandType commandType, K key, double longitude String unit) { notNullKey(key); LettuceAssert.notNull(unit, "Unit " + MUST_NOT_BE_NULL); - LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY); CommandArgs args = new CommandArgs<>(codec).addKey(key).add(longitude).add(latitude).add(distance).add(unit); return createCommand(commandType, new ValueSetOutput<>(codec), args); @@ -840,7 +939,7 @@ Command georadius(K key, double longitude, double latitude, double d LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY); LettuceAssert.notNull(geoRadiusStoreArgs, "GeoRadiusStoreArgs " + MUST_NOT_BE_NULL); LettuceAssert.isTrue(geoRadiusStoreArgs.getStoreKey() != null || geoRadiusStoreArgs.getStoreDistKey() != null, - "At least STORE key or STORDIST key is required"); + "At least STORE key or STOREDIST key is required"); CommandArgs args = new CommandArgs<>(codec).addKey(key).add(longitude).add(latitude).add(distance).add(unit); geoRadiusStoreArgs.build(args); @@ -882,7 +981,7 @@ Command georadiusbymember(K key, V member, double distance, String u LettuceAssert.notNull(unit, "Unit " + MUST_NOT_BE_NULL); LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY); LettuceAssert.isTrue(geoRadiusStoreArgs.getStoreKey() != null || geoRadiusStoreArgs.getStoreDistKey() != null, - "At least STORE key or STORDIST key is required"); + "At least STORE key or STOREDIST key is required"); CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(member).add(distance).add(unit); geoRadiusStoreArgs.build(args); @@ -890,6 +989,57 @@ Command georadiusbymember(K key, V member, double distance, String u return createCommand(GEORADIUSBYMEMBER, new IntegerOutput<>(codec), args); } + Command> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate) { + notNullKey(key); + LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + + reference.build(args); + predicate.build(args); + + return createCommand(GEOSEARCH, new ValueSetOutput<>(codec), args); + } + + Command>> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, + GeoArgs geoArgs) { + notNullKey(key); + LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec).addKey(key); + + reference.build(args); + predicate.build(args); + geoArgs.build(args); + + return createCommand(GEOSEARCH, + new GeoWithinListOutput<>(codec, geoArgs.isWithDistance(), geoArgs.isWithHash(), geoArgs.isWithCoordinates()), + args); + } + + Command geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, + GeoArgs geoArgs, boolean storeDist) { + notNullKey(key); + LettuceAssert.notNull(destination, "Destination " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(key, "Key " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec).addKey(destination).addKey(key); + + reference.build(args); + predicate.build(args); + geoArgs.build(args); + + if (storeDist) { + args.add("STOREDIST"); + } + + return createCommand(GEOSEARCHSTORE, new IntegerOutput<>(codec), args); + } + Command get(K key) { notNullKey(key); @@ -2526,20 +2676,20 @@ public Command xtrim(K key, boolean approximateTrimming, long count) private static String getLowerValue(Range range) { - if (range.getLower().equals(Boundary.unbounded())) { - return "-"; - } + Boundary boundary = range.getLower(); - return range.getLower().getValue(); + return boundary.equals(Boundary.unbounded()) ? "-" : getRange(boundary); } private static String getUpperValue(Range range) { - if (range.getUpper().equals(Boundary.unbounded())) { - return "+"; - } + Boundary boundary = range.getUpper(); + + return boundary.equals(Boundary.unbounded()) ? "+" : getRange(boundary); + } - return range.getUpper().getValue(); + private static String getRange(Boundary boundary) { + return !boundary.isIncluding() ? "(" + boundary.getValue() : boundary.getValue(); } public Command>> xread(XReadArgs xReadArgs, StreamOffset[] streams) { diff --git a/src/main/java/io/lettuce/core/SetArgs.java b/src/main/java/io/lettuce/core/SetArgs.java index e1574404f4..c0eaf136b3 100644 --- a/src/main/java/io/lettuce/core/SetArgs.java +++ b/src/main/java/io/lettuce/core/SetArgs.java @@ -15,6 +15,11 @@ */ package io.lettuce.core; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +import io.lettuce.core.internal.LettuceAssert; import io.lettuce.core.protocol.CommandArgs; /** @@ -31,8 +36,12 @@ public class SetArgs implements CompositeArgument { private Long ex; + private Long exAt; + private Long px; + private Long pxAt; + private boolean nx = false; private boolean xx = false; @@ -51,7 +60,7 @@ private Builder() { } /** - * Creates new {@link SetArgs} and enabling {@literal EX}. + * Creates new {@link SetArgs} and enable {@literal EX}. * * @param timeout expire time in seconds. * @return new {@link SetArgs} with {@literal EX} enabled. @@ -62,7 +71,54 @@ public static SetArgs ex(long timeout) { } /** - * Creates new {@link SetArgs} and enabling {@literal PX}. + * Creates new {@link SetArgs} and enable {@literal EX}. + * + * @param timeout expire time in seconds. + * @return new {@link SetArgs} with {@literal EX} enabled. + * @see SetArgs#ex(long) + * @since 6.1 + */ + public static SetArgs ex(Duration timeout) { + return new SetArgs().ex(timeout); + } + + /** + * Creates new {@link SetArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link SetArgs} with {@literal EXAT} enabled. + * @see SetArgs#exAt(long) + */ + public static SetArgs exAt(long timestamp) { + return new SetArgs().exAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link SetArgs} with {@literal EXAT} enabled. + * @see SetArgs#exAt(Date) + * @since 6.1 + */ + public static SetArgs exAt(Date timestamp) { + return new SetArgs().exAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal EXAT}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return new {@link SetArgs} with {@literal EXAT} enabled. + * @see SetArgs#exAt(Instant) + * @since 6.1 + */ + public static SetArgs exAt(Instant timestamp) { + return new SetArgs().exAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal PX}. * * @param timeout expire time in milliseconds. * @return new {@link SetArgs} with {@literal PX} enabled. @@ -73,7 +129,54 @@ public static SetArgs px(long timeout) { } /** - * Creates new {@link SetArgs} and enabling {@literal NX}. + * Creates new {@link SetArgs} and enable {@literal PX}. + * + * @param timeout expire time in milliseconds. + * @return new {@link SetArgs} with {@literal PX} enabled. + * @see SetArgs#px(long) + * @since 6.1 + */ + public static SetArgs px(Duration timeout) { + return new SetArgs().px(timeout); + } + + /** + * Creates new {@link SetArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link SetArgs} with {@literal PXAT} enabled. + * @see SetArgs#pxAt(long) + */ + public static SetArgs pxAt(long timestamp) { + return new SetArgs().pxAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link SetArgs} with {@literal PXAT} enabled. + * @see SetArgs#pxAt(Date) + * @since 6.1 + */ + public static SetArgs pxAt(Date timestamp) { + return new SetArgs().pxAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal PXAT}. + * + * @param timestamp the timestamp type: posix time. + * @return new {@link SetArgs} with {@literal PXAT} enabled. + * @see SetArgs#pxAt(Instant) + * @since 6.1 + */ + public static SetArgs pxAt(Instant timestamp) { + return new SetArgs().pxAt(timestamp); + } + + /** + * Creates new {@link SetArgs} and enable {@literal NX}. * * @return new {@link SetArgs} with {@literal NX} enabled. * @see SetArgs#nx() @@ -83,7 +186,7 @@ public static SetArgs nx() { } /** - * Creates new {@link SetArgs} and enabling {@literal XX}. + * Creates new {@link SetArgs} and enable {@literal XX}. * * @return new {@link SetArgs} with {@literal XX} enabled. * @see SetArgs#xx() @@ -93,7 +196,7 @@ public static SetArgs xx() { } /** - * Creates new {@link SetArgs} and enabling {@literal KEEPTTL}. + * Creates new {@link SetArgs} and enable {@literal KEEPTTL}. * * @return new {@link SetArgs} with {@literal KEEPTTL} enabled. * @see SetArgs#keepttl() @@ -117,6 +220,62 @@ public SetArgs ex(long timeout) { return this; } + /** + * Set the specified expire time, in seconds. + * + * @param timeout expire time in seconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs ex(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.ex = timeout.toMillis() / 1000; + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs exAt(long timestamp) { + + this.exAt = timestamp; + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs exAt(Date timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + return exAt(timestamp.getTime() / 1000); + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in seconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs exAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + return exAt(timestamp.toEpochMilli() / 1000); + } + /** * Set the specified expire time, in milliseconds. * @@ -129,6 +288,61 @@ public SetArgs px(long timeout) { return this; } + /** + * Set the specified expire time, in milliseconds. + * + * @param timeout expire time in milliseconds. + * @return {@code this} {@link SetArgs}. + */ + public SetArgs px(Duration timeout) { + + LettuceAssert.notNull(timeout, "Timeout must not be null"); + + this.px = timeout.toMillis(); + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs pxAt(long timestamp) { + + this.pxAt = timestamp; + return this; + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs pxAt(Date timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + return pxAt(timestamp.getTime()); + } + + /** + * Set the specified expire at time using a posix {@code timestamp}. + * + * @param timestamp the timestamp type: posix time in milliseconds. + * @return {@code this} {@link SetArgs}. + * @since 6.1 + */ + public SetArgs pxAt(Instant timestamp) { + + LettuceAssert.notNull(timestamp, "Timestamp must not be null"); + + return pxAt(timestamp.toEpochMilli()); + } + /** * Only set the key if it does not already exist. * @@ -163,16 +377,25 @@ public SetArgs xx() { return this; } + @Override public void build(CommandArgs args) { if (ex != null) { args.add("EX").add(ex); } + if (exAt != null) { + args.add("EXAT").add(exAt); + } + if (px != null) { args.add("PX").add(px); } + if (pxAt != null) { + args.add("PXAT").add(pxAt); + } + if (nx) { args.add("NX"); } diff --git a/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java new file mode 100644 index 0000000000..0a4b77f5a7 --- /dev/null +++ b/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.api.async; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.lettuce.core.AclCategory; +import io.lettuce.core.AclSetuserArgs; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.protocol.CommandType; + +/** + * Asynchronous executed commands for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + * @generated by io.lettuce.apigenerator.CreateAsyncApi + */ +public interface RedisAclAsyncCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List<AclCategory> a list of ACL categories or + */ + RedisFuture> aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List<CommandType> a list of commands inside a given category + */ + RedisFuture> aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + RedisFuture aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + RedisFuture aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + RedisFuture aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + RedisFuture> aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List<String> a list of strings. + */ + RedisFuture> aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + RedisFuture aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return List<Map<K,Object>> list of security events. + */ + RedisFuture>> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List<Map<K, Object>> list of security events. + */ + RedisFuture>> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + RedisFuture aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + RedisFuture aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + RedisFuture aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List<K> a list of usernames. + */ + RedisFuture> aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + RedisFuture aclWhoami(); +} diff --git a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java index 4cad414e0d..40c80b281f 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java @@ -27,7 +27,7 @@ * @author Mark Paluch * @since 3.0 */ -public interface RedisAsyncCommands extends BaseRedisAsyncCommands, RedisClusterAsyncCommands, +public interface RedisAsyncCommands extends BaseRedisAsyncCommands, RedisAclAsyncCommands, RedisClusterAsyncCommands, RedisGeoAsyncCommands, RedisHashAsyncCommands, RedisHLLAsyncCommands, RedisKeyAsyncCommands, RedisListAsyncCommands, RedisScriptingAsyncCommands, RedisServerAsyncCommands, RedisSetAsyncCommands, RedisSortedSetAsyncCommands, RedisStreamAsyncCommands, diff --git a/src/main/java/io/lettuce/core/api/async/RedisGeoAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisGeoAsyncCommands.java index 717ec7c7da..4eeb92084d 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisGeoAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisGeoAsyncCommands.java @@ -18,7 +18,13 @@ import java.util.List; import java.util.Set; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.Value; /** * Asynchronous executed commands for the Geo-API. @@ -49,6 +55,19 @@ public interface RedisGeoAsyncCommands { */ RedisFuture geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + RedisFuture geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -59,6 +78,16 @@ public interface RedisGeoAsyncCommands { */ RedisFuture>> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + RedisFuture> geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -139,25 +168,42 @@ public interface RedisGeoAsyncCommands { RedisFuture georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - RedisFuture> geopos(K key, V... members); + RedisFuture> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - RedisFuture geodist(K key, V from, V to, GeoArgs.Unit unit); + RedisFuture>> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + RedisFuture geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist); } diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java index aa8e610fb0..ce1e921e18 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.api.async; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; @@ -48,7 +50,7 @@ public interface RedisKeyAsyncCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ RedisFuture copy(K source, K destination); @@ -59,7 +61,7 @@ public interface RedisKeyAsyncCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ RedisFuture copy(K source, K destination, CopyArgs copyArgs); @@ -101,20 +103,38 @@ public interface RedisKeyAsyncCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ RedisFuture expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + RedisFuture expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + RedisFuture expireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ RedisFuture expireat(K key, Date timestamp); @@ -123,12 +143,11 @@ public interface RedisKeyAsyncCommands { * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: - * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 */ - RedisFuture expireat(K key, long timestamp); + RedisFuture expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -222,20 +241,38 @@ public interface RedisKeyAsyncCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ RedisFuture pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + RedisFuture pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + RedisFuture pexpireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ RedisFuture pexpireat(K key, Date timestamp); @@ -245,11 +282,10 @@ public interface RedisKeyAsyncCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - RedisFuture pexpireat(K key, long timestamp); + RedisFuture pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/java/io/lettuce/core/api/async/RedisSortedSetAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisSortedSetAsyncCommands.java index 54296c4774..206f61b4d6 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisSortedSetAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisSortedSetAsyncCommands.java @@ -203,7 +203,7 @@ public interface RedisSortedSetAsyncCommands { * * @param keys the keys. * @return List<V> array-reply list of elements. - * @since 6.2 + * @since 6.1 */ RedisFuture> zdiff(K... keys); @@ -213,7 +213,7 @@ public interface RedisSortedSetAsyncCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ RedisFuture zdiffstore(K destKey, K... srcKeys); @@ -222,7 +222,7 @@ public interface RedisSortedSetAsyncCommands { * * @param keys the keys. * @return List<V> array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ RedisFuture>> zdiffWithScores(K... keys); @@ -765,7 +765,7 @@ public interface RedisSortedSetAsyncCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ RedisFuture zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -777,7 +777,7 @@ public interface RedisSortedSetAsyncCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ RedisFuture zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1242,7 +1242,7 @@ public interface RedisSortedSetAsyncCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ RedisFuture zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1254,7 +1254,7 @@ public interface RedisSortedSetAsyncCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ RedisFuture zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java index d0528c3cfe..45f703c536 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java @@ -18,7 +18,12 @@ import java.util.List; import java.util.Map; -import io.lettuce.core.*; +import io.lettuce.core.BitFieldArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.SetArgs; +import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -79,7 +84,7 @@ public interface RedisStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -99,7 +104,7 @@ public interface RedisStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -121,7 +126,7 @@ public interface RedisStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -312,7 +317,7 @@ public interface RedisStringAsyncCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ RedisFuture setGet(K key, V value); @@ -323,7 +328,7 @@ public interface RedisStringAsyncCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ RedisFuture setGet(K key, V value, SetArgs setArgs); diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java new file mode 100644 index 0000000000..e6d0b0dbcf --- /dev/null +++ b/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.api.reactive; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import io.lettuce.core.AclCategory; +import io.lettuce.core.AclSetuserArgs; +import io.lettuce.core.protocol.CommandType; + +/** + * Reactive executed commands for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + * @generated by io.lettuce.apigenerator.CreateReactiveApi + */ +public interface RedisAclReactiveCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return AclCategory a list of ACL categories or + */ + Mono> aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return CommandType a list of commands inside a given category + */ + Mono> aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + Mono aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + Mono aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + Mono aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + Mono> aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return String a list of strings. + */ + Flux aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + Mono aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return Map<K,Object> list of security events. + */ + Flux> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return Map<K, Object> list of security events. + */ + Flux> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + Mono aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + Mono aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + Mono aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K a list of usernames. + */ + Flux aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + Mono aclWhoami(); +} diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisGeoReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisGeoReactiveCommands.java index c5c9cafc57..91baef51c5 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisGeoReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisGeoReactiveCommands.java @@ -17,7 +17,12 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.Value; /** * Reactive executed commands for the Geo-API. @@ -48,6 +53,19 @@ public interface RedisGeoReactiveCommands { */ Mono geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + Mono geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -58,6 +76,16 @@ public interface RedisGeoReactiveCommands { */ Flux> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + Flux> geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -138,25 +166,42 @@ public interface RedisGeoReactiveCommands { Mono georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - Flux> geopos(K key, V... members); + Flux geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - Mono geodist(K key, V from, V to, GeoArgs.Unit unit); + Flux> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + Mono geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist); } diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java index 5f71af5a5e..c261f11896 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.api.reactive; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import reactor.core.publisher.Flux; @@ -48,7 +50,7 @@ public interface RedisKeyReactiveCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Mono copy(K source, K destination); @@ -59,7 +61,7 @@ public interface RedisKeyReactiveCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Mono copy(K source, K destination, CopyArgs copyArgs); @@ -101,20 +103,38 @@ public interface RedisKeyReactiveCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Mono expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + Mono expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Mono expireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Mono expireat(K key, Date timestamp); @@ -123,12 +143,11 @@ public interface RedisKeyReactiveCommands { * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: - * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 */ - Mono expireat(K key, long timestamp); + Mono expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -224,20 +243,38 @@ public interface RedisKeyReactiveCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Mono pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + Mono pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Mono pexpireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Mono pexpireat(K key, Date timestamp); @@ -247,11 +284,10 @@ public interface RedisKeyReactiveCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Mono pexpireat(K key, long timestamp); + Mono pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java index be8c49ddfc..c754710c67 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java @@ -27,7 +27,7 @@ * @author Mark Paluch * @since 5.0 */ -public interface RedisReactiveCommands extends BaseRedisReactiveCommands, RedisClusterReactiveCommands, +public interface RedisReactiveCommands extends BaseRedisReactiveCommands, RedisAclReactiveCommands, RedisClusterReactiveCommands, RedisGeoReactiveCommands, RedisHashReactiveCommands, RedisHLLReactiveCommands, RedisKeyReactiveCommands, RedisListReactiveCommands, RedisScriptingReactiveCommands, RedisServerReactiveCommands, RedisSetReactiveCommands, RedisSortedSetReactiveCommands, diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisSortedSetReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisSortedSetReactiveCommands.java index 40c1ffbd3e..f6f087119b 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisSortedSetReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisSortedSetReactiveCommands.java @@ -205,7 +205,7 @@ public interface RedisSortedSetReactiveCommands { * * @param keys the keys. * @return V array-reply list of elements. - * @since 6.2 + * @since 6.1 */ Flux zdiff(K... keys); @@ -215,7 +215,7 @@ public interface RedisSortedSetReactiveCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ Mono zdiffstore(K destKey, K... srcKeys); @@ -224,7 +224,7 @@ public interface RedisSortedSetReactiveCommands { * * @param keys the keys. * @return V array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ Flux> zdiffWithScores(K... keys); @@ -787,7 +787,7 @@ public interface RedisSortedSetReactiveCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Mono zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -799,7 +799,7 @@ public interface RedisSortedSetReactiveCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Mono zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1284,7 +1284,7 @@ public interface RedisSortedSetReactiveCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Mono zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1296,7 +1296,7 @@ public interface RedisSortedSetReactiveCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Mono zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java index 2c8db52a2b..0087eee571 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java @@ -19,7 +19,12 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import io.lettuce.core.*; +import io.lettuce.core.BitFieldArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.SetArgs; +import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.StringMatchResult; +import io.lettuce.core.Value; import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -80,7 +85,7 @@ public interface RedisStringReactiveCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -100,7 +105,7 @@ public interface RedisStringReactiveCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -122,7 +127,7 @@ public interface RedisStringReactiveCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -315,7 +320,7 @@ public interface RedisStringReactiveCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ Mono setGet(K key, V value); @@ -326,7 +331,7 @@ public interface RedisStringReactiveCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ Mono setGet(K key, V value, SetArgs setArgs); diff --git a/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java new file mode 100644 index 0000000000..c1e56f0194 --- /dev/null +++ b/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java @@ -0,0 +1,148 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.api.sync; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.lettuce.core.AclCategory; +import io.lettuce.core.AclSetuserArgs; +import io.lettuce.core.protocol.CommandType; + +/** + * Synchronous executed commands for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + * @generated by io.lettuce.apigenerator.CreateSyncApi + */ +public interface RedisAclCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List<AclCategory> a list of ACL categories or + */ + Set aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List<CommandType> a list of commands inside a given category + */ + Set aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + Long aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + String aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + String aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + List aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List<String> a list of strings. + */ + List aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + String aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return List<Map<K,Object>> list of security events. + */ + List> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List<Map<K, Object>> list of security events. + */ + List> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + String aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + String aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + String aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List<K> a list of usernames. + */ + List aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + String aclWhoami(); +} diff --git a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java index 875e076105..69371de1e9 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java @@ -27,7 +27,7 @@ * @author Mark Paluch * @since 3.0 */ -public interface RedisCommands extends BaseRedisCommands, RedisClusterCommands, RedisGeoCommands, +public interface RedisCommands extends BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands { diff --git a/src/main/java/io/lettuce/core/api/sync/RedisGeoCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisGeoCommands.java index 208eb104f2..7dfbf3011d 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisGeoCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisGeoCommands.java @@ -18,7 +18,12 @@ import java.util.List; import java.util.Set; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.Value; /** * Synchronous executed commands for the Geo-API. @@ -49,6 +54,19 @@ public interface RedisGeoCommands { */ Long geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + Double geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -59,6 +77,16 @@ public interface RedisGeoCommands { */ List> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + List geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -139,25 +167,42 @@ public interface RedisGeoCommands { Long georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - List geopos(K key, V... members); + Set geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - Double geodist(K key, V from, V to, GeoArgs.Unit unit); + List> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + Long geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist); } diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java index d7d7dffbfd..5a6328d123 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.api.sync; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; @@ -47,7 +49,7 @@ public interface RedisKeyCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Boolean copy(K source, K destination); @@ -58,7 +60,7 @@ public interface RedisKeyCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Boolean copy(K source, K destination, CopyArgs copyArgs); @@ -100,20 +102,38 @@ public interface RedisKeyCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Boolean expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + Boolean expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Boolean expireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Boolean expireat(K key, Date timestamp); @@ -122,12 +142,11 @@ public interface RedisKeyCommands { * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: - * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 */ - Boolean expireat(K key, long timestamp); + Boolean expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -221,20 +240,38 @@ public interface RedisKeyCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Boolean pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + Boolean pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Boolean pexpireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Boolean pexpireat(K key, Date timestamp); @@ -244,11 +281,10 @@ public interface RedisKeyCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Boolean pexpireat(K key, long timestamp); + Boolean pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/java/io/lettuce/core/api/sync/RedisSortedSetCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisSortedSetCommands.java index d6fcf2e08b..f2653749ce 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisSortedSetCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisSortedSetCommands.java @@ -203,7 +203,7 @@ public interface RedisSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of elements. - * @since 6.2 + * @since 6.1 */ List zdiff(K... keys); @@ -213,7 +213,7 @@ public interface RedisSortedSetCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ Long zdiffstore(K destKey, K... srcKeys); @@ -222,7 +222,7 @@ public interface RedisSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ List> zdiffWithScores(K... keys); @@ -765,7 +765,7 @@ public interface RedisSortedSetCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -777,7 +777,7 @@ public interface RedisSortedSetCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1242,7 +1242,7 @@ public interface RedisSortedSetCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1254,7 +1254,7 @@ public interface RedisSortedSetCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java index 71871b885f..53a0c06504 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java @@ -18,7 +18,11 @@ import java.util.List; import java.util.Map; -import io.lettuce.core.*; +import io.lettuce.core.BitFieldArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.SetArgs; +import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -79,7 +83,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -99,7 +103,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -121,7 +125,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -312,7 +316,7 @@ public interface RedisStringCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ V setGet(K key, V value); @@ -323,7 +327,7 @@ public interface RedisStringCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ V setGet(K key, V value, SetArgs setArgs); diff --git a/src/main/java/io/lettuce/core/cluster/PubSubClusterEndpoint.java b/src/main/java/io/lettuce/core/cluster/PubSubClusterEndpoint.java index 5fb220ff6b..b47996ab07 100644 --- a/src/main/java/io/lettuce/core/cluster/PubSubClusterEndpoint.java +++ b/src/main/java/io/lettuce/core/cluster/PubSubClusterEndpoint.java @@ -35,7 +35,7 @@ public class PubSubClusterEndpoint extends PubSubEndpoint { private final NotifyingMessageListener multicast = new NotifyingMessageListener(); - private final UpstreamMessageListener upstream = new UpstreamMessageListener(); + private final MasterMessageListener upstream = new MasterMessageListener(); private volatile boolean nodeMessagePropagation = false; @@ -108,7 +108,7 @@ protected void notifyListeners(PubSubMessage output) { } } - private class UpstreamMessageListener extends NotifyingMessageListener { + private class MasterMessageListener extends NotifyingMessageListener { @Override public void message(RedisClusterNode node, K channel, V message) { diff --git a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java index ee823eba2e..fc8cc0ab1d 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java +++ b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java @@ -145,8 +145,7 @@ public class RedisClusterClient extends AbstractRedisClient { private static final InternalLogger logger = InternalLoggerFactory.getInstance(RedisClusterClient.class); - private final ClusterTopologyRefresh refresh = ClusterTopologyRefresh.create(new NodeConnectionFactoryImpl(), - getResources()); + private final ClusterTopologyRefresh refresh; private final ClusterTopologyRefreshScheduler topologyRefreshScheduler = new ClusterTopologyRefreshScheduler( this::getClusterClientOptions, this::getPartitions, this::refreshPartitionsAsync, getResources()); @@ -162,7 +161,8 @@ protected RedisClusterClient() { super(null); - initialUris = Collections.emptyList(); + this.initialUris = Collections.emptyList(); + this.refresh = createTopologyRefresh(); } /** @@ -182,6 +182,7 @@ protected RedisClusterClient(ClientResources clientResources, Iterable assertSameOptions(redisURIs); this.initialUris = Collections.unmodifiableList(LettuceLists.newList(redisURIs)); + this.refresh = createTopologyRefresh(); setDefaultTimeout(getFirstUri().getTimeout()); setOptions(ClusterClientOptions.create()); @@ -1094,6 +1095,16 @@ protected void forEachCloseable(Predicate { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List<AclCategory> a list of ACL categories or + */ + AsyncExecutions> aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List<CommandType> a list of commands inside a given category + */ + AsyncExecutions> aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + AsyncExecutions aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + AsyncExecutions aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + AsyncExecutions aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + AsyncExecutions> aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List<String> a list of strings. + */ + AsyncExecutions> aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + AsyncExecutions aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return List<Map<K,Object>> list of security events. + */ + AsyncExecutions>> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List<Map<K, Object>> list of security events. + */ + AsyncExecutions>> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + AsyncExecutions aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + AsyncExecutions aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + AsyncExecutions aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List<K> a list of usernames. + */ + AsyncExecutions> aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + AsyncExecutions aclWhoami(); +} diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionGeoAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionGeoAsyncCommands.java index 58dcfca4e7..48b576aa0b 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionGeoAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionGeoAsyncCommands.java @@ -18,7 +18,12 @@ import java.util.List; import java.util.Set; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.Value; /** * Asynchronous executed commands on a node selection for the Geo-API. @@ -49,6 +54,19 @@ public interface NodeSelectionGeoAsyncCommands { */ AsyncExecutions geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + AsyncExecutions geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -59,6 +77,16 @@ public interface NodeSelectionGeoAsyncCommands { */ AsyncExecutions>> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + AsyncExecutions> geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -139,25 +167,42 @@ public interface NodeSelectionGeoAsyncCommands { AsyncExecutions georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - AsyncExecutions> geopos(K key, V... members); + AsyncExecutions> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - AsyncExecutions geodist(K key, V from, V to, GeoArgs.Unit unit); + AsyncExecutions>> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + AsyncExecutions geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist); } diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java index b856fbef58..8b1fdc3239 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.cluster.api.async; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; @@ -47,7 +49,7 @@ public interface NodeSelectionKeyAsyncCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ AsyncExecutions copy(K source, K destination); @@ -58,7 +60,7 @@ public interface NodeSelectionKeyAsyncCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ AsyncExecutions copy(K source, K destination, CopyArgs copyArgs); @@ -100,20 +102,38 @@ public interface NodeSelectionKeyAsyncCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ AsyncExecutions expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + AsyncExecutions expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + AsyncExecutions expireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ AsyncExecutions expireat(K key, Date timestamp); @@ -122,12 +142,11 @@ public interface NodeSelectionKeyAsyncCommands { * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: - * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 */ - AsyncExecutions expireat(K key, long timestamp); + AsyncExecutions expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -221,20 +240,38 @@ public interface NodeSelectionKeyAsyncCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ AsyncExecutions pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + AsyncExecutions pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + AsyncExecutions pexpireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ AsyncExecutions pexpireat(K key, Date timestamp); @@ -244,11 +281,10 @@ public interface NodeSelectionKeyAsyncCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - AsyncExecutions pexpireat(K key, long timestamp); + AsyncExecutions pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSortedSetAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSortedSetAsyncCommands.java index d3137faace..91267d6e37 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSortedSetAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionSortedSetAsyncCommands.java @@ -203,7 +203,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * * @param keys the keys. * @return List<V> array-reply list of elements. - * @since 6.2 + * @since 6.1 */ AsyncExecutions> zdiff(K... keys); @@ -213,7 +213,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ AsyncExecutions zdiffstore(K destKey, K... srcKeys); @@ -222,7 +222,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * * @param keys the keys. * @return List<V> array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ AsyncExecutions>> zdiffWithScores(K... keys); @@ -765,7 +765,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ AsyncExecutions zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -777,7 +777,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ AsyncExecutions zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1242,7 +1242,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ AsyncExecutions zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1254,7 +1254,7 @@ public interface NodeSelectionSortedSetAsyncCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ AsyncExecutions zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java index 24fa588532..43ca19f492 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java @@ -18,7 +18,11 @@ import java.util.List; import java.util.Map; -import io.lettuce.core.*; +import io.lettuce.core.BitFieldArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.SetArgs; +import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -79,7 +83,7 @@ public interface NodeSelectionStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -99,7 +103,7 @@ public interface NodeSelectionStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -121,7 +125,7 @@ public interface NodeSelectionStringAsyncCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -312,7 +316,7 @@ public interface NodeSelectionStringAsyncCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ AsyncExecutions setGet(K key, V value); @@ -323,7 +327,7 @@ public interface NodeSelectionStringAsyncCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ AsyncExecutions setGet(K key, V value, SetArgs setArgs); diff --git a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java index 412e3d72fe..0ba2030af0 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java @@ -18,7 +18,6 @@ import java.time.Duration; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import io.lettuce.core.KeyValue; import io.lettuce.core.RedisFuture; @@ -32,7 +31,7 @@ * @author Mark Paluch * @since 4.0 */ -public interface RedisClusterAsyncCommands extends BaseRedisAsyncCommands, RedisGeoAsyncCommands, +public interface RedisClusterAsyncCommands extends BaseRedisAsyncCommands, RedisAclAsyncCommands, RedisGeoAsyncCommands, RedisHashAsyncCommands, RedisHLLAsyncCommands, RedisKeyAsyncCommands, RedisListAsyncCommands, RedisScriptingAsyncCommands, RedisServerAsyncCommands, RedisSetAsyncCommands, RedisSortedSetAsyncCommands, RedisStreamAsyncCommands, RedisStringAsyncCommands { diff --git a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java index 4e79af4504..1fcb0456bb 100644 --- a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java @@ -17,7 +17,6 @@ import java.time.Duration; import java.util.Map; -import java.util.concurrent.TimeUnit; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -33,10 +32,11 @@ * @since 5.0 */ public interface RedisClusterReactiveCommands - extends BaseRedisReactiveCommands, RedisGeoReactiveCommands, RedisHashReactiveCommands, - RedisHLLReactiveCommands, RedisKeyReactiveCommands, RedisListReactiveCommands, - RedisScriptingReactiveCommands, RedisServerReactiveCommands, RedisSetReactiveCommands, - RedisSortedSetReactiveCommands, RedisStreamReactiveCommands, RedisStringReactiveCommands { + extends BaseRedisReactiveCommands, RedisAclReactiveCommands, RedisGeoReactiveCommands, + RedisHashReactiveCommands, RedisHLLReactiveCommands, RedisKeyReactiveCommands, + RedisListReactiveCommands, RedisScriptingReactiveCommands, RedisServerReactiveCommands, + RedisSetReactiveCommands, RedisSortedSetReactiveCommands, RedisStreamReactiveCommands, + RedisStringReactiveCommands { /** * Set the default timeout for operations. A zero timeout value indicates to not time out. diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java new file mode 100644 index 0000000000..197da0a096 --- /dev/null +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java @@ -0,0 +1,148 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.cluster.api.sync; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.lettuce.core.AclCategory; +import io.lettuce.core.AclSetuserArgs; +import io.lettuce.core.protocol.CommandType; + +/** + * Synchronous executed commands on a node selection for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + * @generated by io.lettuce.apigenerator.CreateSyncNodeSelectionClusterApi + */ +public interface NodeSelectionAclCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List<AclCategory> a list of ACL categories or + */ + Executions> aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List<CommandType> a list of commands inside a given category + */ + Executions> aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + Executions aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + Executions aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + Executions aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + Executions> aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List<String> a list of strings. + */ + Executions> aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + Executions aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return List<Map<K,Object>> list of security events. + */ + Executions>> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List<Map<K, Object>> list of security events. + */ + Executions>> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + Executions aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + Executions aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + Executions aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List<K> a list of usernames. + */ + Executions> aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + Executions aclWhoami(); +} diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionGeoCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionGeoCommands.java index fb322cf12e..1ebafe6bcf 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionGeoCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionGeoCommands.java @@ -18,7 +18,12 @@ import java.util.List; import java.util.Set; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.Value; /** * Synchronous executed commands on a node selection for the Geo-API. @@ -49,6 +54,19 @@ public interface NodeSelectionGeoCommands { */ Executions geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + Executions geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -59,6 +77,16 @@ public interface NodeSelectionGeoCommands { */ Executions>> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + Executions> geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -139,25 +167,42 @@ public interface NodeSelectionGeoCommands { Executions georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - Executions> geopos(K key, V... members); + Executions> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - Executions geodist(K key, V from, V to, GeoArgs.Unit unit); + Executions>> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + Executions geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist); } diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java index 3769502de7..52fc65f095 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.cluster.api.sync; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; @@ -47,7 +49,7 @@ public interface NodeSelectionKeyCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Executions copy(K source, K destination); @@ -58,7 +60,7 @@ public interface NodeSelectionKeyCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Executions copy(K source, K destination, CopyArgs copyArgs); @@ -100,20 +102,38 @@ public interface NodeSelectionKeyCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Executions expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + Executions expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Executions expireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Executions expireat(K key, Date timestamp); @@ -122,12 +142,11 @@ public interface NodeSelectionKeyCommands { * * @param key the key. * @param timestamp the timestamp type: posix time. - * @return Boolean integer-reply specifically: - * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 */ - Executions expireat(K key, long timestamp); + Executions expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -221,20 +240,38 @@ public interface NodeSelectionKeyCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Executions pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + Executions pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. - * @return Boolean integer-reply specifically: + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Executions pexpireat(K key, long timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * - * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set - * (see: {@code EXPIRE}). + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). */ Executions pexpireat(K key, Date timestamp); @@ -244,11 +281,10 @@ public interface NodeSelectionKeyCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Executions pexpireat(K key, long timestamp); + Executions pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSortedSetCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSortedSetCommands.java index 00d404d013..3f1de597b2 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSortedSetCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionSortedSetCommands.java @@ -203,7 +203,7 @@ public interface NodeSelectionSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of elements. - * @since 6.2 + * @since 6.1 */ Executions> zdiff(K... keys); @@ -213,7 +213,7 @@ public interface NodeSelectionSortedSetCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ Executions zdiffstore(K destKey, K... srcKeys); @@ -222,7 +222,7 @@ public interface NodeSelectionSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ Executions>> zdiffWithScores(K... keys); @@ -765,7 +765,7 @@ public interface NodeSelectionSortedSetCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Executions zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -777,7 +777,7 @@ public interface NodeSelectionSortedSetCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Executions zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1242,7 +1242,7 @@ public interface NodeSelectionSortedSetCommands { * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Executions zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1254,7 +1254,7 @@ public interface NodeSelectionSortedSetCommands { * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Executions zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java index 2efca005a2..a28928caf2 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java @@ -18,7 +18,11 @@ import java.util.List; import java.util.Map; -import io.lettuce.core.*; +import io.lettuce.core.BitFieldArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.SetArgs; +import io.lettuce.core.StrAlgoArgs; +import io.lettuce.core.StringMatchResult; import io.lettuce.core.output.KeyValueStreamingChannel; /** @@ -79,7 +83,7 @@ public interface NodeSelectionStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -99,7 +103,7 @@ public interface NodeSelectionStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -121,7 +125,7 @@ public interface NodeSelectionStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -312,7 +316,7 @@ public interface NodeSelectionStringCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ Executions setGet(K key, V value); @@ -323,7 +327,7 @@ public interface NodeSelectionStringCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ Executions setGet(K key, V value, SetArgs setArgs); diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java index 146e44d622..423a5c56da 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java @@ -17,7 +17,6 @@ import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; import io.lettuce.core.api.sync.*; @@ -30,9 +29,11 @@ * @since 4.0 */ public interface RedisClusterCommands - extends BaseRedisCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, - RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, - RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands { + extends BaseRedisCommands, RedisAclCommands, RedisGeoCommands, + RedisHashCommands, RedisHLLCommands, RedisKeyCommands, + RedisListCommands, RedisScriptingCommands, RedisServerCommands, + RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, + RedisStringCommands { /** * Set the default timeout for operations. A zero timeout value indicates to not time out. diff --git a/src/main/java/io/lettuce/core/internal/LettuceStrings.java b/src/main/java/io/lettuce/core/internal/LettuceStrings.java index 24a52c1141..46fb252cd3 100644 --- a/src/main/java/io/lettuce/core/internal/LettuceStrings.java +++ b/src/main/java/io/lettuce/core/internal/LettuceStrings.java @@ -34,23 +34,51 @@ private LettuceStrings() { } /** - * Checks if a CharSequence is empty ("") or null. + * Checks if a String is empty or null. * * @param cs the char sequence * @return true if empty + * @since 6.0.3 + * @see String#isEmpty() */ - public static boolean isEmpty(final CharSequence cs) { + public static boolean isEmpty(String cs) { + return cs == null || cs.isEmpty(); + } + + /** + * Checks if a CharSequence has a length of 0 or null. + * + * @param cs the char sequence + * @return true if empty + * @see CharSequence#length() + */ + public static boolean isEmpty(CharSequence cs) { + if (cs instanceof String) { + return isEmpty((String) cs); + } return cs == null || cs.length() == 0; } /** - * Checks if a CharSequence is not empty ("") and not null. + * Checks if a String is not empty and not null. * * @param cs the char sequence * @return true if not empty + * @since 6.0.3 + * @see String#isEmpty() + */ + public static boolean isNotEmpty(String cs) { + return !isEmpty(cs); + } + + /** + * Checks if a CharSequence has a non-zero length and is not null. * + * @param cs the char sequence + * @return true if not empty + * @see CharSequence#length() */ - public static boolean isNotEmpty(final CharSequence cs) { + public static boolean isNotEmpty(CharSequence cs) { return !isEmpty(cs); } diff --git a/src/main/java/io/lettuce/core/masterreplica/AutodiscoveryConnector.java b/src/main/java/io/lettuce/core/masterreplica/AutodiscoveryConnector.java index 75b6c02bc6..5759eab0ac 100644 --- a/src/main/java/io/lettuce/core/masterreplica/AutodiscoveryConnector.java +++ b/src/main/java/io/lettuce/core/masterreplica/AutodiscoveryConnector.java @@ -34,13 +34,13 @@ import io.lettuce.core.models.role.RedisNodeDescription; /** - * {@link UpstreamReplicaConnector} to connect unmanaged Redis Master/Replica with auto-discovering master and replica nodes - * from a single {@link RedisURI}. + * {@link MasterReplicaConnector} to connect unmanaged Redis Master/Replica with auto-discovering master and replica nodes from + * a single {@link RedisURI}. * * @author Mark Paluch * @since 5.1 */ -class AutodiscoveryConnector implements UpstreamReplicaConnector { +class AutodiscoveryConnector implements MasterReplicaConnector { private final RedisClient redisClient; @@ -112,8 +112,8 @@ private Mono> initializeConnection(Re ReplicaTopologyProvider topologyProvider = new ReplicaTopologyProvider(connectionAndUri.getT2(), connectionAndUri.getT1()); - UpstreamReplicaTopologyRefresh refresh = new UpstreamReplicaTopologyRefresh(redisClient, topologyProvider); - UpstreamReplicaConnectionProvider connectionProvider = new UpstreamReplicaConnectionProvider<>(redisClient, codec, + MasterReplicaTopologyRefresh refresh = new MasterReplicaTopologyRefresh(redisClient, topologyProvider); + MasterReplicaConnectionProvider connectionProvider = new MasterReplicaConnectionProvider<>(redisClient, codec, redisURI, (Map) initialConnections); Mono> refreshFuture = refresh.getNodes(redisURI); @@ -122,10 +122,10 @@ private Mono> initializeConnection(Re connectionProvider.setKnownNodes(nodes); - UpstreamReplicaChannelWriter channelWriter = new UpstreamReplicaChannelWriter(connectionProvider, + MasterReplicaChannelWriter channelWriter = new MasterReplicaChannelWriter(connectionProvider, redisClient.getResources()); - StatefulRedisUpstreamReplicaConnectionImpl connection = new StatefulRedisUpstreamReplicaConnectionImpl<>( + StatefulRedisMasterReplicaConnectionImpl connection = new StatefulRedisMasterReplicaConnectionImpl<>( channelWriter, codec, redisURI.getTimeout()); connection.setOptions(redisClient.getOptions()); diff --git a/src/main/java/io/lettuce/core/masterreplica/MasterReplica.java b/src/main/java/io/lettuce/core/masterreplica/MasterReplica.java index c555c23362..a3321cad45 100644 --- a/src/main/java/io/lettuce/core/masterreplica/MasterReplica.java +++ b/src/main/java/io/lettuce/core/masterreplica/MasterReplica.java @@ -57,12 +57,12 @@ *

* Master-Replica topologies are either static or semi-static. Redis Standalone instances with attached replicas provide no * failover/HA mechanism. Redis Sentinel managed instances are controlled by Redis Sentinel and allow failover (which include - * upstream promotion). The {@link MasterReplica} API supports both mechanisms. The topology is provided by a + * master promotion). The {@link MasterReplica} API supports both mechanisms. The topology is provided by a * {@link TopologyProvider}: * *

    *
  • {@link ReplicaTopologyProvider}: Dynamic topology lookup using the {@code INFO REPLICATION} output. Replicas are listed - * as {@code replicaN=...} entries. The initial connection can either point to a upstream or a replica and the topology provider + * as {@code replicaN=...} entries. The initial connection can either point to a master or a replica and the topology provider * will discover nodes. The connection needs to be re-established outside of lettuce in a case of Master/Replica failover or * topology changes.
  • *
  • {@link StaticMasterReplicaTopologyProvider}: Topology is defined by the list of {@link RedisURI URIs} and the @@ -83,7 +83,7 @@ * *
      *
    • Redis Sentinel: At least one Sentinel must be reachable, the masterId must be registered and at least one host must be - * available (upstream or replica). Allows for runtime-recovery based on Sentinel Events.
    • + * available (master or replica). Allows for runtime-recovery based on Sentinel Events. *
    • Static Setup (auto-discovery): The initial endpoint must be reachable. No recovery/reconfiguration during runtime.
    • *
    • Static Setup (provided hosts): All endpoints must be reachable. No recovery/reconfiguration during runtime.
    • *
    @@ -98,7 +98,7 @@ public class MasterReplica { * {@link RedisCodec codec} to encode/decode keys. *

    * This {@link MasterReplica} performs auto-discovery of nodes using either Redis Sentinel or Master/Replica. A - * {@link RedisURI} can point to either a upstream or a replica host. + * {@link RedisURI} can point to either a master or a replica host. *

    * * @param redisClient the Redis client. @@ -118,7 +118,7 @@ public static StatefulRedisMasterReplicaConnection connect(RedisCli * supplied {@link RedisCodec codec} to encode/decode keys. *

    * This {@link MasterReplica} performs auto-discovery of nodes using either Redis Sentinel or Master/Replica. A - * {@link RedisURI} can point to either a upstream or a replica host. + * {@link RedisURI} can point to either a master or a replica host. *

    * * @param redisClient the Redis client. @@ -224,7 +224,7 @@ private static CompletableFuture(redisClient, codec, first).connectAsync(); } - return new StaticUpstreamReplicaConnector<>(redisClient, codec, uriList).connectAsync(); + return new StaticMasterReplicaConnector<>(redisClient, codec, uriList).connectAsync(); } private static boolean isSentinel(RedisURI redisURI) { diff --git a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriter.java b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriter.java similarity index 86% rename from src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriter.java rename to src/main/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriter.java index bb7cdf99ed..9899dbadfd 100644 --- a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriter.java +++ b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriter.java @@ -23,7 +23,7 @@ import io.lettuce.core.RedisException; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.internal.LettuceAssert; -import io.lettuce.core.masterreplica.UpstreamReplicaConnectionProvider.Intent; +import io.lettuce.core.masterreplica.MasterReplicaConnectionProvider.Intent; import io.lettuce.core.protocol.ConnectionFacade; import io.lettuce.core.protocol.ProtocolKeyword; import io.lettuce.core.protocol.RedisCommand; @@ -34,9 +34,9 @@ * * @author Mark Paluch */ -class UpstreamReplicaChannelWriter implements RedisChannelWriter { +class MasterReplicaChannelWriter implements RedisChannelWriter { - private UpstreamReplicaConnectionProvider upstreamReplicaConnectionProvider; + private MasterReplicaConnectionProvider masterReplicaConnectionProvider; private final ClientResources clientResources; @@ -44,9 +44,9 @@ class UpstreamReplicaChannelWriter implements RedisChannelWriter { private boolean inTransaction; - UpstreamReplicaChannelWriter(UpstreamReplicaConnectionProvider upstreamReplicaConnectionProvider, + MasterReplicaChannelWriter(MasterReplicaConnectionProvider masterReplicaConnectionProvider, ClientResources clientResources) { - this.upstreamReplicaConnectionProvider = upstreamReplicaConnectionProvider; + this.masterReplicaConnectionProvider = masterReplicaConnectionProvider; this.clientResources = clientResources; } @@ -65,7 +65,7 @@ public RedisCommand write(RedisCommand command) { } Intent intent = inTransaction ? Intent.WRITE : getIntent(command.getType()); - CompletableFuture> future = (CompletableFuture) upstreamReplicaConnectionProvider + CompletableFuture> future = (CompletableFuture) masterReplicaConnectionProvider .getConnectionAsync(intent); if (isEndTransaction(command.getType())) { @@ -118,7 +118,7 @@ private static void writeCommand(RedisCommand command, StatefulR // Currently: Retain order Intent intent = inTransaction ? Intent.WRITE : getIntent(commands); - CompletableFuture> future = (CompletableFuture) upstreamReplicaConnectionProvider + CompletableFuture> future = (CompletableFuture) masterReplicaConnectionProvider .getConnectionAsync(intent); for (RedisCommand command : commands) { @@ -207,9 +207,9 @@ public CompletableFuture closeAsync() { CompletableFuture future = null; - if (upstreamReplicaConnectionProvider != null) { - future = upstreamReplicaConnectionProvider.closeAsync(); - upstreamReplicaConnectionProvider = null; + if (masterReplicaConnectionProvider != null) { + future = masterReplicaConnectionProvider.closeAsync(); + masterReplicaConnectionProvider = null; } if (future == null) { @@ -219,8 +219,8 @@ public CompletableFuture closeAsync() { return future; } - UpstreamReplicaConnectionProvider getUpstreamReplicaConnectionProvider() { - return upstreamReplicaConnectionProvider; + MasterReplicaConnectionProvider getUpstreamReplicaConnectionProvider() { + return masterReplicaConnectionProvider; } @Override @@ -234,17 +234,17 @@ public ClientResources getClientResources() { @Override public void setAutoFlushCommands(boolean autoFlush) { - upstreamReplicaConnectionProvider.setAutoFlushCommands(autoFlush); + masterReplicaConnectionProvider.setAutoFlushCommands(autoFlush); } @Override public void flushCommands() { - upstreamReplicaConnectionProvider.flushCommands(); + masterReplicaConnectionProvider.flushCommands(); } @Override public void reset() { - upstreamReplicaConnectionProvider.reset(); + masterReplicaConnectionProvider.reset(); } /** @@ -254,7 +254,7 @@ public void reset() { * @param readFrom the read from setting, must not be {@code null} */ public void setReadFrom(ReadFrom readFrom) { - upstreamReplicaConnectionProvider.setReadFrom(readFrom); + masterReplicaConnectionProvider.setReadFrom(readFrom); } /** @@ -263,7 +263,7 @@ public void setReadFrom(ReadFrom readFrom) { * @return the read from setting */ public ReadFrom getReadFrom() { - return upstreamReplicaConnectionProvider.getReadFrom(); + return masterReplicaConnectionProvider.getReadFrom(); } private static boolean isSuccessfullyCompleted(CompletableFuture connectFuture) { diff --git a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProvider.java b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProvider.java similarity index 94% rename from src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProvider.java rename to src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProvider.java index f9d7a65946..9953b532e7 100644 --- a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProvider.java +++ b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProvider.java @@ -43,9 +43,9 @@ * @author Mark Paluch * @since 4.1 */ -class UpstreamReplicaConnectionProvider { +class MasterReplicaConnectionProvider { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(UpstreamReplicaConnectionProvider.class); + private static final InternalLogger logger = InternalLoggerFactory.getInstance(MasterReplicaConnectionProvider.class); private final boolean debugEnabled = logger.isDebugEnabled(); @@ -61,7 +61,7 @@ class UpstreamReplicaConnectionProvider { private ReadFrom readFrom; - UpstreamReplicaConnectionProvider(RedisClient redisClient, RedisCodec redisCodec, RedisURI initialRedisUri, + MasterReplicaConnectionProvider(RedisClient redisClient, RedisCodec redisCodec, RedisURI initialRedisUri, Map> initialConnections) { this.initialRedisUri = initialRedisUri; @@ -77,9 +77,9 @@ class UpstreamReplicaConnectionProvider { } /** - * Retrieve a {@link StatefulRedisConnection} by the intent. {@link UpstreamReplicaConnectionProvider.Intent#WRITE} - * intentions use the master connection, {@link UpstreamReplicaConnectionProvider.Intent#READ} intentions lookup one or more - * read candidates using the {@link ReadFrom} setting. + * Retrieve a {@link StatefulRedisConnection} by the intent. {@link MasterReplicaConnectionProvider.Intent#WRITE} intentions + * use the master connection, {@link MasterReplicaConnectionProvider.Intent#READ} intentions lookup one or more read + * candidates using the {@link ReadFrom} setting. * * @param intent command intent * @return the connection. @@ -98,9 +98,9 @@ public StatefulRedisConnection getConnection(Intent intent) { } /** - * Retrieve a {@link StatefulRedisConnection} by the intent. {@link UpstreamReplicaConnectionProvider.Intent#WRITE} - * intentions use the master connection, {@link UpstreamReplicaConnectionProvider.Intent#READ} intentions lookup one or more - * read candidates using the {@link ReadFrom} setting. + * Retrieve a {@link StatefulRedisConnection} by the intent. {@link MasterReplicaConnectionProvider.Intent#WRITE} intentions + * use the master connection, {@link MasterReplicaConnectionProvider.Intent#READ} intentions lookup one or more read + * candidates using the {@link ReadFrom} setting. * * @param intent command intent * @return the connection. diff --git a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnector.java b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnector.java similarity index 87% rename from src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnector.java rename to src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnector.java index 8b9ed9e184..3904773cc0 100644 --- a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaConnector.java +++ b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaConnector.java @@ -20,15 +20,15 @@ import io.lettuce.core.codec.RedisCodec; /** - * Interface declaring an asynchronous connect method to connect a Upstream/Replica setup. + * Interface declaring an asynchronous connect method to connect a Master/Replica setup. * * @author Mark Paluch * @since 5.1 */ -interface UpstreamReplicaConnector { +interface MasterReplicaConnector { /** - * Asynchronously connect to a Upstream/Replica setup given {@link RedisCodec}. + * Asynchronously connect to a Master/Replica setup given {@link RedisCodec}. * * @return Future that is notified about the connection progress. */ diff --git a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefresh.java b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefresh.java similarity index 94% rename from src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefresh.java rename to src/main/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefresh.java index 7f5b1138c7..8964c0bb80 100644 --- a/src/main/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefresh.java +++ b/src/main/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefresh.java @@ -37,9 +37,9 @@ * * @author Mark Paluch */ -class UpstreamReplicaTopologyRefresh { +class MasterReplicaTopologyRefresh { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(UpstreamReplicaTopologyRefresh.class); + private static final InternalLogger logger = InternalLoggerFactory.getInstance(MasterReplicaTopologyRefresh.class); private static final StringCodec CODEC = StringCodec.UTF8; @@ -49,11 +49,11 @@ class UpstreamReplicaTopologyRefresh { private final ScheduledExecutorService eventExecutors; - UpstreamReplicaTopologyRefresh(RedisClient client, TopologyProvider topologyProvider) { + MasterReplicaTopologyRefresh(RedisClient client, TopologyProvider topologyProvider) { this(new RedisClientNodeConnectionFactory(client), client.getResources().eventExecutorGroup(), topologyProvider); } - UpstreamReplicaTopologyRefresh(NodeConnectionFactory nodeConnectionFactory, ScheduledExecutorService eventExecutors, + MasterReplicaTopologyRefresh(NodeConnectionFactory nodeConnectionFactory, ScheduledExecutorService eventExecutors, TopologyProvider topologyProvider) { this.nodeConnectionFactory = nodeConnectionFactory; diff --git a/src/main/java/io/lettuce/core/masterreplica/RedisUpstreamReplicaNode.java b/src/main/java/io/lettuce/core/masterreplica/RedisMasterReplicaNode.java similarity index 87% rename from src/main/java/io/lettuce/core/masterreplica/RedisUpstreamReplicaNode.java rename to src/main/java/io/lettuce/core/masterreplica/RedisMasterReplicaNode.java index 2e481faa12..db7643abaf 100644 --- a/src/main/java/io/lettuce/core/masterreplica/RedisUpstreamReplicaNode.java +++ b/src/main/java/io/lettuce/core/masterreplica/RedisMasterReplicaNode.java @@ -24,13 +24,13 @@ * @author Mark Paluch * @author Adam McElwee */ -class RedisUpstreamReplicaNode implements RedisNodeDescription { +class RedisMasterReplicaNode implements RedisNodeDescription { private final RedisURI redisURI; private final Role role; - RedisUpstreamReplicaNode(String host, int port, RedisURI seed, Role role) { + RedisMasterReplicaNode(String host, int port, RedisURI seed, Role role) { this.redisURI = RedisURI.builder(seed).withHost(host).withPort(port).build(); this.role = role; @@ -50,10 +50,10 @@ public Role getRole() { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof RedisUpstreamReplicaNode)) + if (!(o instanceof RedisMasterReplicaNode)) return false; - RedisUpstreamReplicaNode that = (RedisUpstreamReplicaNode) o; + RedisMasterReplicaNode that = (RedisMasterReplicaNode) o; if (!redisURI.equals(that.redisURI)) return false; diff --git a/src/main/java/io/lettuce/core/masterreplica/ReplicaTopologyProvider.java b/src/main/java/io/lettuce/core/masterreplica/ReplicaTopologyProvider.java index 76cee71cd1..51ba9d4d90 100644 --- a/src/main/java/io/lettuce/core/masterreplica/ReplicaTopologyProvider.java +++ b/src/main/java/io/lettuce/core/masterreplica/ReplicaTopologyProvider.java @@ -142,7 +142,7 @@ private List getReplicasFromInfo(String info) { String ip = getNested(IP_PATTERN, group, 1); String port = getNested(PORT_PATTERN, group, 1); - replicas.add(new RedisUpstreamReplicaNode(ip, Integer.parseInt(port), redisURI, RedisInstance.Role.SLAVE)); + replicas.add(new RedisMasterReplicaNode(ip, Integer.parseInt(port), redisURI, RedisInstance.Role.SLAVE)); } return replicas; @@ -163,7 +163,7 @@ private RedisNodeDescription getMasterFromInfo(String info) { String host = masterHostMatcher.group(1); int port = Integer.parseInt(masterPortMatcher.group(1)); - return new RedisUpstreamReplicaNode(host, port, redisURI, RedisInstance.Role.UPSTREAM); + return new RedisMasterReplicaNode(host, port, redisURI, RedisInstance.Role.UPSTREAM); } private String getNested(Pattern pattern, String string, int group) { @@ -196,7 +196,7 @@ private RedisNodeDescription getRedisNodeDescription(Matcher matcher) { + RedisInstance.Role.REPLICA); } - return new RedisUpstreamReplicaNode(redisURI.getHost(), redisURI.getPort(), redisURI, role); + return new RedisMasterReplicaNode(redisURI.getHost(), redisURI.getPort(), redisURI, role); } } diff --git a/src/main/java/io/lettuce/core/masterreplica/SentinelConnector.java b/src/main/java/io/lettuce/core/masterreplica/SentinelConnector.java index dcdad7ad7d..16081f2f67 100644 --- a/src/main/java/io/lettuce/core/masterreplica/SentinelConnector.java +++ b/src/main/java/io/lettuce/core/masterreplica/SentinelConnector.java @@ -31,12 +31,12 @@ import io.netty.util.internal.logging.InternalLoggerFactory; /** - * {@link UpstreamReplicaConnector} to connect a Sentinel-managed Master/Replica setup using a Sentinel {@link RedisURI}. + * {@link MasterReplicaConnector} to connect a Sentinel-managed Master/Replica setup using a Sentinel {@link RedisURI}. * * @author Mark Paluch * @since 5.1 */ -class SentinelConnector implements UpstreamReplicaConnector { +class SentinelConnector implements MasterReplicaConnector { private static final InternalLogger LOG = InternalLoggerFactory.getInstance(SentinelConnector.class); @@ -59,8 +59,8 @@ public CompletableFuture> connectAsyn SentinelTopologyRefresh sentinelTopologyRefresh = new SentinelTopologyRefresh(redisClient, redisURI.getSentinelMasterId(), redisURI.getSentinels()); - UpstreamReplicaTopologyRefresh refresh = new UpstreamReplicaTopologyRefresh(redisClient, topologyProvider); - UpstreamReplicaConnectionProvider connectionProvider = new UpstreamReplicaConnectionProvider<>(redisClient, codec, + MasterReplicaTopologyRefresh refresh = new MasterReplicaTopologyRefresh(redisClient, topologyProvider); + MasterReplicaConnectionProvider connectionProvider = new MasterReplicaConnectionProvider<>(redisClient, codec, redisURI, Collections.emptyMap()); Runnable runnable = getTopologyRefreshRunnable(refresh, connectionProvider); @@ -76,12 +76,12 @@ public CompletableFuture> connectAsyn } private Mono> initializeConnection(RedisCodec codec, - SentinelTopologyRefresh sentinelTopologyRefresh, UpstreamReplicaConnectionProvider connectionProvider, + SentinelTopologyRefresh sentinelTopologyRefresh, MasterReplicaConnectionProvider connectionProvider, Runnable runnable, List nodes) { connectionProvider.setKnownNodes(nodes); - UpstreamReplicaChannelWriter channelWriter = new UpstreamReplicaChannelWriter(connectionProvider, + MasterReplicaChannelWriter channelWriter = new MasterReplicaChannelWriter(connectionProvider, redisClient.getResources()) { @Override @@ -91,7 +91,7 @@ public CompletableFuture closeAsync() { }; - StatefulRedisUpstreamReplicaConnectionImpl connection = new StatefulRedisUpstreamReplicaConnectionImpl<>( + StatefulRedisMasterReplicaConnectionImpl connection = new StatefulRedisMasterReplicaConnectionImpl<>( channelWriter, codec, redisURI.getTimeout()); connection.setOptions(redisClient.getOptions()); @@ -102,8 +102,8 @@ public CompletableFuture closeAsync() { }).then(Mono.just(connection)); } - private Runnable getTopologyRefreshRunnable(UpstreamReplicaTopologyRefresh refresh, - UpstreamReplicaConnectionProvider connectionProvider) { + private Runnable getTopologyRefreshRunnable(MasterReplicaTopologyRefresh refresh, + MasterReplicaConnectionProvider connectionProvider) { return () -> { try { diff --git a/src/main/java/io/lettuce/core/masterreplica/SentinelTopologyProvider.java b/src/main/java/io/lettuce/core/masterreplica/SentinelTopologyProvider.java index e4adf40f25..208734a4de 100644 --- a/src/main/java/io/lettuce/core/masterreplica/SentinelTopologyProvider.java +++ b/src/main/java/io/lettuce/core/masterreplica/SentinelTopologyProvider.java @@ -133,7 +133,7 @@ private RedisNodeDescription toNode(Map map, RedisInstance.Role String ip = map.get("ip"); String port = map.get("port"); - return new RedisUpstreamReplicaNode(ip, Integer.parseInt(port), sentinelUri, role); + return new RedisMasterReplicaNode(ip, Integer.parseInt(port), sentinelUri, role); } } diff --git a/src/main/java/io/lettuce/core/masterreplica/StatefulRedisUpstreamReplicaConnectionImpl.java b/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java similarity index 80% rename from src/main/java/io/lettuce/core/masterreplica/StatefulRedisUpstreamReplicaConnectionImpl.java rename to src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java index dd4a3f959a..c9274c1143 100644 --- a/src/main/java/io/lettuce/core/masterreplica/StatefulRedisUpstreamReplicaConnectionImpl.java +++ b/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java @@ -24,7 +24,7 @@ /** * @author Mark Paluch */ -class StatefulRedisUpstreamReplicaConnectionImpl extends StatefulRedisConnectionImpl +class StatefulRedisMasterReplicaConnectionImpl extends StatefulRedisConnectionImpl implements StatefulRedisMasterReplicaConnection { /** @@ -34,7 +34,7 @@ class StatefulRedisUpstreamReplicaConnectionImpl extends StatefulRedisConn * @param codec Codec used to encode/decode keys and values. * @param timeout Maximum time to wait for a response. */ - StatefulRedisUpstreamReplicaConnectionImpl(UpstreamReplicaChannelWriter writer, RedisCodec codec, Duration timeout) { + StatefulRedisMasterReplicaConnectionImpl(MasterReplicaChannelWriter writer, RedisCodec codec, Duration timeout) { super(writer, NoOpPushHandler.INSTANCE, codec, timeout); } @@ -49,8 +49,8 @@ public ReadFrom getReadFrom() { } @Override - public UpstreamReplicaChannelWriter getChannelWriter() { - return (UpstreamReplicaChannelWriter) super.getChannelWriter(); + public MasterReplicaChannelWriter getChannelWriter() { + return (MasterReplicaChannelWriter) super.getChannelWriter(); } } diff --git a/src/main/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaConnector.java b/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaConnector.java similarity index 75% rename from src/main/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaConnector.java rename to src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaConnector.java index bc645477e5..27c23298cf 100644 --- a/src/main/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaConnector.java +++ b/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaConnector.java @@ -30,13 +30,13 @@ import io.lettuce.core.models.role.RedisNodeDescription; /** - * {@link UpstreamReplicaConnector} to connect to a static declared Master/Replica setup providing a fixed array of + * {@link MasterReplicaConnector} to connect to a static declared Master/Replica setup providing a fixed array of * {@link RedisURI}. This connector determines roles and remains using only the provided endpoints. * * @author Mark Paluch * @since 5.1 */ -class StaticUpstreamReplicaConnector implements UpstreamReplicaConnector { +class StaticMasterReplicaConnector implements MasterReplicaConnector { private final RedisClient redisClient; @@ -44,7 +44,7 @@ class StaticUpstreamReplicaConnector implements UpstreamReplicaConnector redisURIs; - StaticUpstreamReplicaConnector(RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { + StaticMasterReplicaConnector(RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { this.redisClient = redisClient; this.codec = codec; this.redisURIs = redisURIs; @@ -59,8 +59,8 @@ public CompletableFuture> connectAsyn RedisURI seedNode = redisURIs.iterator().next(); - UpstreamReplicaTopologyRefresh refresh = new UpstreamReplicaTopologyRefresh(redisClient, topologyProvider); - UpstreamReplicaConnectionProvider connectionProvider = new UpstreamReplicaConnectionProvider<>(redisClient, codec, + MasterReplicaTopologyRefresh refresh = new MasterReplicaTopologyRefresh(redisClient, topologyProvider); + MasterReplicaConnectionProvider connectionProvider = new MasterReplicaConnectionProvider<>(redisClient, codec, seedNode, initialConnections); return refresh.getNodes(seedNode).flatMap(nodes -> { @@ -74,14 +74,14 @@ public CompletableFuture> connectAsyn } private Mono> initializeConnection(RedisCodec codec, RedisURI seedNode, - UpstreamReplicaConnectionProvider connectionProvider, List nodes) { + MasterReplicaConnectionProvider connectionProvider, List nodes) { connectionProvider.setKnownNodes(nodes); - UpstreamReplicaChannelWriter channelWriter = new UpstreamReplicaChannelWriter(connectionProvider, + MasterReplicaChannelWriter channelWriter = new MasterReplicaChannelWriter(connectionProvider, redisClient.getResources()); - StatefulRedisUpstreamReplicaConnectionImpl connection = new StatefulRedisUpstreamReplicaConnectionImpl<>( + StatefulRedisMasterReplicaConnectionImpl connection = new StatefulRedisMasterReplicaConnectionImpl<>( channelWriter, codec, seedNode.getTimeout()); connection.setOptions(redisClient.getOptions()); diff --git a/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaTopologyProvider.java b/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaTopologyProvider.java index c1ca8fab5a..890511aa98 100644 --- a/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaTopologyProvider.java +++ b/src/main/java/io/lettuce/core/masterreplica/StaticMasterReplicaTopologyProvider.java @@ -117,7 +117,7 @@ private static Mono getNodeDescription(RedisURI uri, StatefulRedisConnection connection) { return connection.reactive().role().collectList().map(RoleParser::parse) - .map(it -> new RedisUpstreamReplicaNode(uri.getHost(), uri.getPort(), uri, it.getRole())); + .map(it -> new RedisMasterReplicaNode(uri.getHost(), uri.getPort(), uri, it.getRole())); } } diff --git a/src/main/java/io/lettuce/core/models/command/CommandDetail.java b/src/main/java/io/lettuce/core/models/command/CommandDetail.java index 50951f8834..6bd9f29320 100644 --- a/src/main/java/io/lettuce/core/models/command/CommandDetail.java +++ b/src/main/java/io/lettuce/core/models/command/CommandDetail.java @@ -15,6 +15,8 @@ */ package io.lettuce.core.models.command; +import io.lettuce.core.AclCategory; + import java.io.Serializable; import java.util.Set; @@ -226,115 +228,4 @@ public enum Flag { */ MOVABLEKEYS; } - - /** - * @since 6.1 - */ - public enum AclCategory { - - /** - * command affects keyspace - */ - KEYSPACE, - - /** - * read command - */ - READ, - - /** - * write command - */ - WRITE, - - /** - * command for sets - */ - SET, - - /** - * command for sorted sets - */ - SORTEDSET, - - /** - * command for lists - */ - LIST, - - /** - * command for hash ops - */ - HASH, - - /** - * command for strings - */ - STRING, - - /** - * command for bitmaps - */ - BITMAP, - - /** - * command for hyperloglog - */ - HYPERLOGLOG, - - /** - * geo command - */ - GEO, - - /** - * streaming command - */ - STREAM, - - /** - * pubsub command - */ - PUBSUB, - - /** - * admin command - */ - ADMIN, - - /** - * fast command - */ - FAST, - - /** - * slow command - */ - SLOW, - - /** - * blocking command - */ - BLOCKING, - - /** - * dangerous command - */ - DANGEROUS, - - /** - * connection-establishing command - */ - CONNECTION, - - /** - * transactional command - */ - TRANSACTION, - - /** - * scripting command - */ - SCRIPTING - } } diff --git a/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java b/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java index 608d6adecc..71de0ac274 100644 --- a/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java +++ b/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java @@ -17,6 +17,7 @@ import java.util.*; +import io.lettuce.core.AclCategory; import io.lettuce.core.internal.LettuceAssert; /** @@ -40,7 +41,7 @@ public class CommandDetailParser { protected static final Map FLAG_MAPPING; @SuppressWarnings("serial") - protected static final Map ACL_CATEGORY_MAPPING; + protected static final Map ACL_CATEGORY_MAPPING; static { Map flagMap = new HashMap<>(); @@ -60,28 +61,28 @@ public class CommandDetailParser { flagMap.put("write", CommandDetail.Flag.WRITE); FLAG_MAPPING = Collections.unmodifiableMap(flagMap); - Map aclCategoriesMap = new HashMap<>(); - aclCategoriesMap.put("@keyspace", CommandDetail.AclCategory.KEYSPACE); - aclCategoriesMap.put("@read", CommandDetail.AclCategory.READ); - aclCategoriesMap.put("@write", CommandDetail.AclCategory.WRITE); - aclCategoriesMap.put("@set", CommandDetail.AclCategory.SET); - aclCategoriesMap.put("@sortedset", CommandDetail.AclCategory.SORTEDSET); - aclCategoriesMap.put("@list", CommandDetail.AclCategory.LIST); - aclCategoriesMap.put("@hash", CommandDetail.AclCategory.HASH); - aclCategoriesMap.put("@string", CommandDetail.AclCategory.STRING); - aclCategoriesMap.put("@bitmap", CommandDetail.AclCategory.BITMAP); - aclCategoriesMap.put("@hyperloglog", CommandDetail.AclCategory.HYPERLOGLOG); - aclCategoriesMap.put("@geo", CommandDetail.AclCategory.GEO); - aclCategoriesMap.put("@stream", CommandDetail.AclCategory.STREAM); - aclCategoriesMap.put("@pubsub", CommandDetail.AclCategory.PUBSUB); - aclCategoriesMap.put("@admin", CommandDetail.AclCategory.ADMIN); - aclCategoriesMap.put("@fast", CommandDetail.AclCategory.FAST); - aclCategoriesMap.put("@slow", CommandDetail.AclCategory.SLOW); - aclCategoriesMap.put("@blocking", CommandDetail.AclCategory.BLOCKING); - aclCategoriesMap.put("@dangerous", CommandDetail.AclCategory.DANGEROUS); - aclCategoriesMap.put("@connection", CommandDetail.AclCategory.CONNECTION); - aclCategoriesMap.put("@transaction", CommandDetail.AclCategory.TRANSACTION); - aclCategoriesMap.put("@scripting", CommandDetail.AclCategory.SCRIPTING); + Map aclCategoriesMap = new HashMap<>(); + aclCategoriesMap.put("@keyspace", AclCategory.KEYSPACE); + aclCategoriesMap.put("@read", AclCategory.READ); + aclCategoriesMap.put("@write", AclCategory.WRITE); + aclCategoriesMap.put("@set", AclCategory.SET); + aclCategoriesMap.put("@sortedset", AclCategory.SORTEDSET); + aclCategoriesMap.put("@list", AclCategory.LIST); + aclCategoriesMap.put("@hash", AclCategory.HASH); + aclCategoriesMap.put("@string", AclCategory.STRING); + aclCategoriesMap.put("@bitmap", AclCategory.BITMAP); + aclCategoriesMap.put("@hyperloglog", AclCategory.HYPERLOGLOG); + aclCategoriesMap.put("@geo", AclCategory.GEO); + aclCategoriesMap.put("@stream", AclCategory.STREAM); + aclCategoriesMap.put("@pubsub", AclCategory.PUBSUB); + aclCategoriesMap.put("@admin", AclCategory.ADMIN); + aclCategoriesMap.put("@fast", AclCategory.FAST); + aclCategoriesMap.put("@slow", AclCategory.SLOW); + aclCategoriesMap.put("@blocking", AclCategory.BLOCKING); + aclCategoriesMap.put("@dangerous", AclCategory.DANGEROUS); + aclCategoriesMap.put("@connection", AclCategory.CONNECTION); + aclCategoriesMap.put("@transaction", AclCategory.TRANSACTION); + aclCategoriesMap.put("@scripting", AclCategory.SCRIPTING); ACL_CATEGORY_MAPPING = Collections.unmodifiableMap(aclCategoriesMap); } @@ -127,7 +128,7 @@ private static CommandDetail parseCommandDetail(Collection collection) { Object categories = iterator.hasNext() ? iterator.next() : null; Set parsedFlags = parseFlags(flags); - Set parsedAclCategories = parseAclCategories(categories); + Set parsedAclCategories = parseAclCategories(categories); return new CommandDetail(name, arity, parsedFlags, firstKey, lastKey, keyStepCount, parsedAclCategories); } @@ -148,13 +149,13 @@ private static Set parseFlags(Object flags) { return Collections.unmodifiableSet(result); } - private static Set parseAclCategories(Object aclCategories) { - Set result = new HashSet<>(); + private static Set parseAclCategories(Object aclCategories) { + Set result = new HashSet<>(); if (aclCategories instanceof Collection) { Collection collection = (Collection) aclCategories; for (Object o : collection) { - CommandDetail.AclCategory aclCategory = ACL_CATEGORY_MAPPING.get(o); + AclCategory aclCategory = ACL_CATEGORY_MAPPING.get(o); if (aclCategory != null) { result.add(aclCategory); } diff --git a/src/main/java/io/lettuce/core/models/role/RedisInstance.java b/src/main/java/io/lettuce/core/models/role/RedisInstance.java index 9a5d627ed7..23a75b78df 100644 --- a/src/main/java/io/lettuce/core/models/role/RedisInstance.java +++ b/src/main/java/io/lettuce/core/models/role/RedisInstance.java @@ -34,9 +34,13 @@ public interface RedisInstance { */ enum Role { - @Deprecated MASTER { + @Override + public boolean isMaster() { + return true; + } + @Override public boolean isUpstream() { return true; @@ -56,6 +60,11 @@ public boolean isReplica() { UPSTREAM { + @Override + public boolean isMaster() { + return true; + } + @Override public boolean isUpstream() { return true; @@ -78,10 +87,18 @@ public boolean isReplica() { * @return {@code true} if the role indicates that the role is a replication source. * @since 6.0 */ - public boolean isUpstream() { + public boolean isMaster() { return false; } + /** + * @return {@code true} if the role indicates that the role is a replication source. + * @since 6.1 + */ + public boolean isUpstream() { + return isMaster(); + } + /** * @return {@code true} if the role indicates that the role is a replicated node (replica). * @since 6.0 diff --git a/src/main/java/io/lettuce/core/models/role/RedisMasterInstance.java b/src/main/java/io/lettuce/core/models/role/RedisMasterInstance.java index 5636a879ca..4669f8dfe4 100644 --- a/src/main/java/io/lettuce/core/models/role/RedisMasterInstance.java +++ b/src/main/java/io/lettuce/core/models/role/RedisMasterInstance.java @@ -22,10 +22,8 @@ * * @author Mark Paluch * @since 3.0 - * @deprecated since 6.0 in favor of {@link RedisUpstreamInstance} */ @SuppressWarnings("serial") -@Deprecated public class RedisMasterInstance extends RedisUpstreamInstance { public RedisMasterInstance() { diff --git a/src/main/java/io/lettuce/core/output/EnumSetOutput.java b/src/main/java/io/lettuce/core/output/EnumSetOutput.java new file mode 100644 index 0000000000..dd2c1c40fe --- /dev/null +++ b/src/main/java/io/lettuce/core/output/EnumSetOutput.java @@ -0,0 +1,80 @@ +package io.lettuce.core.output; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import io.lettuce.core.codec.RedisCodec; + +/** + * {@link EnumSet} output. + * + * @param Key type. + * @param Value type. + * @author Mikhael Sokolov + * @author Mark Paluch + * @since 6.1 + */ +public class EnumSetOutput> extends CommandOutput> { + + private boolean initialized; + + private final Class enumClass; + + private final UnaryOperator enumValuePreprocessor; + + private final Function onUnknownValue; + + /** + * Create a new {@link EnumSetOutput}. + * + * @param codec Codec used to encode/decode keys and values, must not be {@code null}. + * @param enumClass {@link Enum} type. + * @param enumValuePreprocessor pre-processor for {@link String} values before looking up the enum value. + * @param onUnknownValue fallback {@link Function} to be called when an enum value cannot be looked up. + */ + public EnumSetOutput(RedisCodec codec, Class enumClass, UnaryOperator enumValuePreprocessor, + Function onUnknownValue) { + super(codec, Collections.emptySet()); + this.enumClass = enumClass; + this.enumValuePreprocessor = enumValuePreprocessor; + this.onUnknownValue = onUnknownValue; + } + + @Override + public void set(ByteBuffer bytes) { + + if (bytes == null) { + return; + } + + E enumConstant = resolve(enumValuePreprocessor.apply(decodeAscii(bytes))); + + if (enumConstant == null) { + return; + } + + output.add(enumConstant); + } + + @Override + public void multi(int count) { + + if (!initialized) { + output = EnumSet.noneOf(enumClass); + initialized = true; + } + } + + private E resolve(String value) { + try { + return Enum.valueOf(enumClass, value); + } catch (IllegalArgumentException e) { + return onUnknownValue.apply(value); + } + } + +} diff --git a/src/main/java/io/lettuce/core/output/ListOfGenericMapsOutput.java b/src/main/java/io/lettuce/core/output/ListOfGenericMapsOutput.java new file mode 100644 index 0000000000..679b24de62 --- /dev/null +++ b/src/main/java/io/lettuce/core/output/ListOfGenericMapsOutput.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.output; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.lettuce.core.codec.RedisCodec; + +/** + * {@link List} of maps output. + * + * @param Key type. + * @param Value type. + * @author Mikhael Sokolov + * @since 6.1 + * @see GenericMapOutput + */ +public class ListOfGenericMapsOutput extends CommandOutput>> { + + private final GenericMapOutput nested; + + private int mapCount = -1; + + private final List counts = new ArrayList<>(); + + public ListOfGenericMapsOutput(RedisCodec codec) { + super(codec, new ArrayList<>()); + nested = new GenericMapOutput<>(codec); + } + + @Override + public void set(ByteBuffer bytes) { + nested.set(bytes); + } + + @Override + public void complete(int depth) { + if (counts.size() > 0) { + int expectedSize = counts.get(0); + + if (nested.get().size() == expectedSize) { + counts.remove(0); + output.add(new LinkedHashMap<>(nested.get())); + nested.get().clear(); + } + } + } + + @Override + public void multi(int count) { + + nested.multi(count); + + if (mapCount == -1) { + mapCount = count; + } else { + // div 2 because of key value pair counts twice + counts.add(count / 2); + } + } + + @Override + public void set(long integer) { + nested.set(integer); + } + + @Override + public void set(double number) { + nested.set(number); + } +} diff --git a/src/main/java/io/lettuce/core/output/ListOfMapsOutput.java b/src/main/java/io/lettuce/core/output/ListOfMapsOutput.java index 39fefe6851..a7d8d1502f 100644 --- a/src/main/java/io/lettuce/core/output/ListOfMapsOutput.java +++ b/src/main/java/io/lettuce/core/output/ListOfMapsOutput.java @@ -76,4 +76,8 @@ public void multi(int count) { } } + @Override + public void set(long integer) { + nested.set(integer); + } } diff --git a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java index b52566bd85..7c0fc9ff3e 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java +++ b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java @@ -27,21 +27,21 @@ */ public enum CommandKeyword implements ProtocolKeyword { - ADDR, ADDSLOTS, AFTER, AGGREGATE, ALPHA, AND, ASK, ASC, ASYNC, BEFORE, BLOCK, BUMPEPOCH, + ADDR, ADDSLOTS, AFTER, AGGREGATE, ALLCHANNELS, ALLCOMMANDS, ALLKEYS, ALPHA, AND, ASK, ASC, ASYNC, BEFORE, BLOCK, BUMPEPOCH, - BY, BYLEX, BYSCORE, CACHING, CHANNELS, COPY, COUNT, COUNTKEYSINSLOT, CONSUMERS, CREATE, DB, DELSLOTS, DESC, SOFT, HARD, ENCODING, + BY, BYLEX, BYSCORE, CACHING, CAT, CHANNELS, COPY, COUNT, COUNTKEYSINSLOT, CONSUMERS, CREATE, DB, DELSLOTS, DELUSER, DESC, SOFT, HARD, ENCODING, - FAILOVER, FORGET, FLUSH, FORCE, FLUSHSLOTS, GETNAME, GETKEYSINSLOT, GETREDIR, GROUP, GROUPS, HTSTATS, ID, IDLE, + FAILOVER, FORGET, FLUSH, FORCE, FLUSHSLOTS, GENPASS, GETNAME, GETUSER, GETKEYSINSLOT, GETREDIR, GROUP, GROUPS, HTSTATS, ID, IDLE, - IDLETIME, JUSTID, KILL, KEYSLOT, LEFT, LEN, LIMIT, LIST, LOAD, MATCH, + IDLETIME, JUSTID, KILL, KEYSLOT, LEFT, LEN, LIMIT, LIST, LOAD, LOG, MATCH, - MAX, MAXLEN, MEET, MIN, MOVED, NO, NOACK, NODE, NODES, NOMKSTREAM, NOSAVE, NOT, NUMSUB, NUMPAT, OFF, ON, ONE, OR, PAUSE, + MAX, MAXLEN, MEET, MIN, MOVED, NO, NOACK, NOCOMMANDS, NODE, NODES, NOMKSTREAM, NOPASS, NOSAVE, NOT, NUMSUB, NUMPAT, OFF, ON, ONE, OR, PAUSE, - REFCOUNT, REMOVE, RELOAD, REPLACE, REPLICATE, REV, RESET, + REFCOUNT, REMOVE, RELOAD, REPLACE, REPLICATE, REV, RESET, RESETCHANNELS, RESETKEYS, RESETSTAT, RESTART, RETRYCOUNT, REWRITE, RIGHT, SAVECONFIG, SDSLEN, SETNAME, SETSLOT, SLOTS, STABLE, - MIGRATING, IMPORTING, SKIPME, SLAVES, STREAM, STORE, SUM, SEGFAULT, TRACKING, TYPE, UNBLOCK, WEIGHTS, + MIGRATING, IMPORTING, SAVE, SKIPME, SLAVES, STREAM, STORE, SUM, SEGFAULT, SETUSER, TRACKING, TYPE, UNBLOCK, USERS, WEIGHTS, WHOAMI, WITHSCORES, XOR, YES, USAGE; diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java index 50ea48aada..957386a663 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandType.java +++ b/src/main/java/io/lettuce/core/protocol/CommandType.java @@ -89,8 +89,7 @@ public enum CommandType implements ProtocolKeyword { BITCOUNT, BITFIELD, BITOP, GETBIT, SETBIT, BITPOS, // Geo - - GEOADD, GEORADIUS, GEORADIUS_RO, GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, GEOENCODE, GEODECODE, GEOPOS, GEODIST, GEOHASH, + GEOADD, GEODIST, GEOHASH, GEOENCODE, GEODECODE, GEOPOS, GEORADIUS, GEORADIUS_RO, GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOSEARCHSTORE, // Stream diff --git a/src/main/java/io/lettuce/core/support/BoundedAsyncPool.java b/src/main/java/io/lettuce/core/support/BoundedAsyncPool.java index 6f898d96eb..644edc9d0a 100644 --- a/src/main/java/io/lettuce/core/support/BoundedAsyncPool.java +++ b/src/main/java/io/lettuce/core/support/BoundedAsyncPool.java @@ -54,6 +54,8 @@ public class BoundedAsyncPool extends BasePool implements AsyncPool { private static final IllegalStateException NOT_PART_OF_POOL = unknownStackTrace( new IllegalStateException("Returned object not currently part of this pool"), BoundedAsyncPool.class, "release()"); + public static final CompletableFuture COMPLETED_FUTURE = CompletableFuture.completedFuture(null); + private final int maxTotal; private final int maxIdle; @@ -153,7 +155,7 @@ CompletableFuture createIdle() { int potentialIdle = getMinIdle() - getIdle(); if (potentialIdle <= 0 || !isPoolActive()) { - return CompletableFuture.completedFuture(null); + return (CompletableFuture) COMPLETED_FUTURE; } int totalLimit = getAvailableCapacity(); @@ -163,7 +165,9 @@ CompletableFuture createIdle() { for (int i = 0; i < toCreate; i++) { if (getAvailableCapacity() <= 0) { - break; + + futures[i] = COMPLETED_FUTURE; + continue; } CompletableFuture future = new CompletableFuture<>(); diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt new file mode 100644 index 0000000000..74d3edb008 --- /dev/null +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.lettuce.core.api.coroutines + +import io.lettuce.core.AclCategory +import io.lettuce.core.AclSetuserArgs +import io.lettuce.core.ExperimentalLettuceCoroutinesApi +import io.lettuce.core.protocol.CommandType +import kotlinx.coroutines.flow.Flow + +/** + * Coroutine executed commands for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + * @generated by io.lettuce.apigenerator.CreateKotlinCoroutinesApi + */ +@ExperimentalLettuceCoroutinesApi +interface RedisAclCoroutinesCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List a list of ACL categories or + */ + suspend fun aclCat(): Set + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List a list of commands inside a given category + */ + suspend fun aclCat(category: AclCategory): Set + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + suspend fun aclDeluser(vararg usernames: String): Long? + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + suspend fun aclGenpass(): String? + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + suspend fun aclGenpass(bits: Int): String? + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map a map of ACL rule definitions for the user. + */ + suspend fun aclGetuser(username: String): List + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List a list of strings. + */ + fun aclList(): Flow + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + suspend fun aclLoad(): String? + + /** + * The command shows a list of recent ACL security events. + * + * @return List> list of security events. + */ + fun aclLog(): Flow> + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List> list of security events. + */ + fun aclLog(count: Int): Flow> + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + suspend fun aclLogReset(): String? + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + suspend fun aclSave(): String? + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + suspend fun aclSetuser(username: String, setuserArgs: AclSetuserArgs): String? + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List a list of usernames. + */ + suspend fun aclUsers(): List + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + suspend fun aclWhoami(): String? + +} + diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt new file mode 100644 index 0000000000..5af18a1970 --- /dev/null +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.lettuce.core.api.coroutines + +import io.lettuce.core.AclCategory +import io.lettuce.core.AclSetuserArgs +import io.lettuce.core.ExperimentalLettuceCoroutinesApi +import io.lettuce.core.api.reactive.RedisAclReactiveCommands +import io.lettuce.core.protocol.CommandType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull + +/** + * Coroutine executed commands (based on reactive commands) for basic commands. + * + * @param Key type. + * @param Value type. + * @author Mikhael Sokolov + * @since 6.0 + */ +@ExperimentalLettuceCoroutinesApi +internal class RedisAclCoroutinesCommandsImpl(internal val ops: RedisAclReactiveCommands) : RedisAclCoroutinesCommands { + + override suspend fun aclCat(): Set = + ops.aclCat().awaitFirstOrElse { emptySet() } + + override suspend fun aclCat(category: AclCategory): Set = + ops.aclCat(category).awaitFirstOrElse { emptySet() } + + override suspend fun aclDeluser(vararg usernames: String): Long? = ops.aclDeluser(*usernames).awaitFirstOrNull() + + override suspend fun aclGenpass(): String? = ops.aclGenpass().awaitFirstOrNull() + + override suspend fun aclGenpass(bits: Int): String? = ops.aclGenpass(bits).awaitFirstOrNull() + + override suspend fun aclGetuser(username: String): List = + ops.aclGetuser(username).awaitFirst() + + override fun aclList(): Flow = ops.aclList().asFlow() + + override suspend fun aclLoad(): String? = ops.aclLoad().awaitFirstOrNull() + + override fun aclLog(): Flow> = ops.aclLog().asFlow() + + override fun aclLog(count: Int): Flow> = ops.aclLog(count).asFlow() + + override suspend fun aclLogReset(): String? = ops.aclLogReset().awaitFirstOrNull() + + override suspend fun aclSave(): String? = ops.aclSave().awaitFirstOrNull() + + override suspend fun aclSetuser(username: String, setuserArgs: AclSetuserArgs): String? = ops.aclSetuser(username, setuserArgs).awaitFirstOrNull() + + override suspend fun aclUsers(): List = ops.aclUsers().asFlow().toList() + + override suspend fun aclWhoami(): String? = ops.aclWhoami().awaitFirstOrNull() + +} + diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommands.kt index ce6d59ccdd..183d21ab28 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommands.kt @@ -29,6 +29,7 @@ import io.lettuce.core.cluster.api.coroutines.RedisClusterCoroutinesCommands @ExperimentalLettuceCoroutinesApi interface RedisCoroutinesCommands : BaseRedisCoroutinesCommands, + RedisAclCoroutinesCommands, RedisGeoCoroutinesCommands, RedisHashCoroutinesCommands, RedisHLLCoroutinesCommands, diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommandsImpl.kt index 8b320f516a..348def6db4 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisCoroutinesCommandsImpl.kt @@ -36,6 +36,7 @@ open class RedisCoroutinesCommandsImpl( internal val ops: RedisReactiveCommands ) : RedisCoroutinesCommands, RedisClusterCoroutinesCommands, BaseRedisCoroutinesCommands by BaseRedisCoroutinesCommandsImpl(ops), + RedisAclCoroutinesCommands by RedisAclCoroutinesCommandsImpl(ops), RedisGeoCoroutinesCommands by RedisGeoCoroutinesCommandsImpl(ops), RedisHashCoroutinesCommands by RedisHashCoroutinesCommandsImpl(ops), RedisHLLCoroutinesCommands by RedisHLLCoroutinesCommandsImpl(ops), diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommands.kt index d68d733624..237396438f 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommands.kt @@ -49,6 +49,19 @@ interface RedisGeoCoroutinesCommands { */ suspend fun geoadd(key: K, vararg lngLatMember: Any): Long? + /** + * Retrieve distance between points `from` and `to`. If one or more elements are missing `null` is + * returned. Default in meters by, otherwise according to `unit` + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points `from` and `to`. If one or more elements are missing `null` is + * returned. + */ + suspend fun geodist(key: K, from: V, to: V, unit: GeoArgs.Unit): Double? + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -59,6 +72,16 @@ interface RedisGeoCoroutinesCommands { */ fun geohash(key: K, vararg members: V): Flow> + /** + * Get geo coordinates for the `members`. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of [GeoCoordinates]s representing the x,y position of each element specified in the arguments. For + * missing elements `null` is returned. + */ + suspend fun geopos(key: K, vararg members: V): List + /** * Retrieve members selected by distance with the center of `longitude` and `latitude`. * @@ -139,27 +162,44 @@ interface RedisGeoCoroutinesCommands { suspend fun georadiusbymember(key: K, member: V, distance: Double, unit: GeoArgs.Unit, geoRadiusStoreArgs: GeoRadiusStoreArgs): Long? /** - * Get geo coordinates for the `members`. + * Retrieve members selected by distance with the center of `reference` the search `predicate`. + * Use [GeoSearch] to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of [GeoCoordinates]s representing the x,y position of each element specified in the arguments. For - * missing elements `null` is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - suspend fun geopos(key: K, vararg members: V): List + fun geosearch(key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate): Flow /** - * Retrieve distance between points `from` and `to`. If one or more elements are missing `null` is - * returned. Default in meters by, otherwise according to `unit` + * Retrieve members selected by distance with the center of `reference` the search `predicate`. + * Use [GeoSearch] to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points `from` and `to`. If one or more elements are missing `null` is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The [GeoWithin] contains only fields which were requested by [GeoArgs]. + * @since 6.1 */ - suspend fun geodist(key: K, from: V, to: V, unit: GeoArgs.Unit): Double? + fun geosearch(key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate, geoArgs: GeoArgs): Flow> + + /** + * Perform a [geosearch(Any, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)] query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + suspend fun geosearchstore(destination: K, key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate, geoArgs: GeoArgs, storeDist: Boolean): Long? } diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommandsImpl.kt index 1e9a72e3e0..8e8df7fbc3 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisGeoCoroutinesCommandsImpl.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.reactive.awaitFirstOrNull * Coroutine executed commands (based on reactive commands) for the Geo-API. * * @author Mikhael Sokolov + * @author Mark Paluch * @since 6.0 */ @ExperimentalLettuceCoroutinesApi @@ -37,6 +38,10 @@ internal class RedisGeoCoroutinesCommandsImpl(internal val ops override suspend fun geoadd(key: K, vararg lngLatMember: Any): Long? = ops.geoadd(key, *lngLatMember).awaitFirstOrNull() + override suspend fun geopos(key: K, vararg members: V): List = ops.geopos(key, *members).map { it.value }.asFlow().toList() + + override suspend fun geodist(key: K, from: V, to: V, unit: GeoArgs.Unit): Double? = ops.geodist(key, from, to, unit).awaitFirstOrNull() + override fun geohash(key: K, vararg members: V): Flow> = ops.geohash(key, *members).asFlow() override fun georadius(key: K, longitude: Double, latitude: Double, distance: Double, unit: GeoArgs.Unit): Flow = ops.georadius(key, longitude, latitude, distance, unit).asFlow() @@ -51,9 +56,10 @@ internal class RedisGeoCoroutinesCommandsImpl(internal val ops override suspend fun georadiusbymember(key: K, member: V, distance: Double, unit: GeoArgs.Unit, geoRadiusStoreArgs: GeoRadiusStoreArgs): Long? = ops.georadiusbymember(key, member, distance, unit, geoRadiusStoreArgs).awaitFirstOrNull() - override suspend fun geopos(key: K, vararg members: V): List = ops.geopos(key, *members).map { it.value }.asFlow().toList() + override fun geosearch(key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate): Flow = ops.geosearch(key, reference, predicate).asFlow() - override suspend fun geodist(key: K, from: V, to: V, unit: GeoArgs.Unit): Double? = ops.geodist(key, from, to, unit).awaitFirstOrNull() + override fun geosearch(key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate, geoArgs: GeoArgs): Flow> = ops.geosearch(key, reference, predicate, geoArgs).asFlow() + override suspend fun geosearchstore(destination: K, key: K, reference: GeoSearch.GeoRef, predicate: GeoSearch.GeoPredicate, geoArgs: GeoArgs, storeDist: Boolean): Long? = ops.geosearchstore(destination, key, reference, predicate, geoArgs, storeDist).awaitFirstOrNull() } diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt index e0f88bd019..bfd04c42a0 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt @@ -18,6 +18,8 @@ package io.lettuce.core.api.coroutines import io.lettuce.core.* import kotlinx.coroutines.flow.Flow +import java.time.Duration +import java.time.Instant import java.util.* /** @@ -38,7 +40,7 @@ interface RedisKeyCoroutinesCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: `true` if source was copied. `false` if source was not copied. - * @since 6.2 + * @since 6. */ suspend fun copy(source: K, destination: K): Boolean? @@ -49,7 +51,7 @@ interface RedisKeyCoroutinesCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: `true` if source was copied. `false` if source was not copied. - * @since 6.2 + * @since 6.1 */ suspend fun copy(source: K, destination: K, copyArgs: CopyArgs): Boolean? @@ -91,18 +93,38 @@ interface RedisKeyCoroutinesCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set. */ suspend fun expire(key: K, seconds: Long): Boolean? + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: + * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set. + * @since 6.1 + */ + suspend fun expire(key: K, seconds: Duration): Boolean? + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. * @return Boolean integer-reply specifically: + * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set + * (see: `EXPIRE`). + */ + suspend fun expireat(key: K, timestamp: Long): Boolean? + + /** + * Set the expiration for a key as a UNIX timestamp. * + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set * (see: `EXPIRE`). */ @@ -114,11 +136,11 @@ interface RedisKeyCoroutinesCommands { * @param key the key. * @param timestamp the timestamp type: posix time. * @return Boolean integer-reply specifically: - * * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set * (see: `EXPIRE`). + * @since 6.1 */ - suspend fun expireat(key: K, timestamp: Long): Boolean? + suspend fun expireat(key: K, timestamp: Instant): Boolean? /** * Find all keys matching the given pattern. @@ -203,18 +225,38 @@ interface RedisKeyCoroutinesCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set. */ suspend fun pexpire(key: K, milliseconds: Long): Boolean? + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: + * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set. + * @since 6.1 + */ + suspend fun pexpire(key: K, milliseconds: Duration): Boolean? + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: + * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set + * (see: `EXPIRE`). + */ + suspend fun pexpireat(key: K, timestamp: Long): Boolean? + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set * (see: `EXPIRE`). */ @@ -226,11 +268,10 @@ interface RedisKeyCoroutinesCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * `true` if the timeout was set. `false` if `key` does not exist or the timeout could not be set * (see: `EXPIRE`). */ - suspend fun pexpireat(key: K, timestamp: Long): Boolean? + suspend fun pexpireat(key: K, timestamp: Instant): Boolean? /** * Get the time to live for a key in milliseconds. diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt index 41ea2a4780..aa40aa547b 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt @@ -21,6 +21,8 @@ import io.lettuce.core.api.reactive.RedisKeyReactiveCommands import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.reactive.asFlow import kotlinx.coroutines.reactive.awaitFirstOrNull +import java.time.Duration +import java.time.Instant import java.util.* /** @@ -53,9 +55,15 @@ internal class RedisKeyCoroutinesCommandsImpl(internal val ops override suspend fun expire(key: K, seconds: Long): Boolean? = ops.expire(key, seconds).awaitFirstOrNull() + override suspend fun expire(key: K, seconds: Duration): Boolean? = + ops.expire(key, seconds).awaitFirstOrNull() + override suspend fun expireat(key: K, timestamp: Date): Boolean? = ops.expireat(key, timestamp).awaitFirstOrNull() + override suspend fun expireat(key: K, timestamp: Instant): Boolean? = + ops.expireat(key, timestamp).awaitFirstOrNull() + override suspend fun expireat(key: K, timestamp: Long): Boolean? = ops.expireat(key, timestamp).awaitFirstOrNull() @@ -89,10 +97,14 @@ internal class RedisKeyCoroutinesCommandsImpl(internal val ops override suspend fun pexpire(key: K, milliseconds: Long): Boolean? = ops.pexpire(key, milliseconds).awaitFirstOrNull() - override suspend fun pexpireat(key: K, timestamp: Date): Boolean? = ops.pexpireat(key, timestamp).awaitFirstOrNull() + override suspend fun pexpire(key: K, milliseconds: Duration): Boolean? = ops.pexpire(key, milliseconds).awaitFirstOrNull() override suspend fun pexpireat(key: K, timestamp: Long): Boolean? = ops.pexpireat(key, timestamp).awaitFirstOrNull() + override suspend fun pexpireat(key: K, timestamp: Date): Boolean? = ops.pexpireat(key, timestamp).awaitFirstOrNull() + + override suspend fun pexpireat(key: K, timestamp: Instant): Boolean? = ops.pexpireat(key, timestamp).awaitFirstOrNull() + override suspend fun pttl(key: K): Long? = ops.pttl(key).awaitFirstOrNull() override suspend fun randomkey(): K? = ops.randomkey().awaitFirstOrNull() diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisSortedSetCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisSortedSetCoroutinesCommands.kt index d7488556e4..bb7e59ca10 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisSortedSetCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisSortedSetCoroutinesCommands.kt @@ -175,29 +175,29 @@ interface RedisSortedSetCoroutinesCommands { /** * Computes the difference between the first and all successive input sorted sets. - * - * @param keys the keys. - * @return List array-reply list of elements. - * @since 6.2 + * + * @param keys the keys. + * @return List array-reply list of elements. + * @since 6.1 */ fun zdiff(vararg keys: K): Flow /** - * Computes the difference between the first and all successive input sorted sets and stores the result in destination. - * - * @param destKey the dest key. - * @param srcKeys the src keys. - * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * Computes the difference between the first and all successive input sorted sets and stores the result in destination. + * + * @param destKey the dest key. + * @param srcKeys the src keys. + * @return Long the number of elements in the resulting sorted set at destination. + * @since 6.1 */ suspend fun zdiffstore(destKey: K, vararg srcKeys: K): Long? /** - * Computes the difference between the first and all successive input sorted sets. - * - * @param keys the keys. - * @return List array-reply list of scored values. - * @since 6.2 + * Computes the difference between the first and all successive input sorted sets. + * + * @param keys the keys. + * @return List array-reply list of scored values. + * @since 6.1 */ fun zdiffWithScores(vararg keys: K): Flow> @@ -414,24 +414,24 @@ interface RedisSortedSetCoroutinesCommands { fun zrangebyscoreWithScores(key: K, range: Range, limit: Limit): Flow> /** - * Get the specified range of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. - * - * @param dstKey the dst key. - * @param srcKey the src key. - * @param range the lexicographical range. - * @return The number of elements in the resulting sorted set. - * @since 6.2 + * Get the specified range of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. + * + * @param dstKey the dst key. + * @param srcKey the src key. + * @param range the lexicographical range. + * @return The number of elements in the resulting sorted set. + * @since 6.1 */ suspend fun zrangestorebylex(dstKey: K, srcKey: K, range: Range, limit: Limit): Long? /** - * Get the specified range of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. - * - * @param dstKey the dst key. - * @param srcKey the src key. - * @param range the score range. - * @return The number of elements in the resulting sorted set. - * @since 6.2 + * Get the specified range of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. + * + * @param dstKey the dst key. + * @param srcKey the src key. + * @param range the score range. + * @return The number of elements in the resulting sorted set. + * @since 6.1 */ suspend fun zrangestorebyscore(dstKey: K, srcKey: K, range: Range, limit: Limit): Long? @@ -570,24 +570,24 @@ interface RedisSortedSetCoroutinesCommands { fun zrevrangebyscoreWithScores(key: K, range: Range, limit: Limit): Flow> /** - * Get the lexicographical range ordered from high to low of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. - * - * @param dstKey the src key. - * @param srcKey the dst key. - * @param range the lexicographical range. - * @return The number of elements in the resulting sorted set. - * @since 6.2 + * Get the lexicographical range ordered from high to low of elements in the sorted set stored at {@code srcKey} and stores the result in the {@code dstKey} destination key. + * + * @param dstKey the src key. + * @param srcKey the dst key. + * @param range the lexicographical range. + * @return The number of elements in the resulting sorted set. + * @since 6.1 */ suspend fun zrevrangestorebylex(dstKey: K, srcKey: K, range: Range, limit: Limit): Long? /** - * Get the specified range of elements in the sorted set stored at {@code srcKey} with scores ordered from high to low and stores the result in the {@code dstKey} destination key. - * - * @param dstKey the src key. - * @param srcKey the dst key. - * @param range the score range. - * @return The number of elements in the resulting sorted set. - * @since 6.2 + * Get the specified range of elements in the sorted set stored at {@code srcKey} with scores ordered from high to low and stores the result in the {@code dstKey} destination key. + * + * @param dstKey the src key. + * @param srcKey the dst key. + * @param range the score range. + * @return The number of elements in the resulting sorted set. + * @since 6.1 */ suspend fun zrevrangestorebyscore(dstKey: K, srcKey: K, range: Range, limit: Limit): Long? diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt index 8909f649fd..77410242d1 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt @@ -78,7 +78,7 @@ interface RedisStringCoroutinesCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command `BITPOS key 0` will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -98,7 +98,7 @@ interface RedisStringCoroutinesCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command `BITPOS key 0` will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -120,7 +120,7 @@ interface RedisStringCoroutinesCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command `BITPOS key 0` will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -298,22 +298,22 @@ interface RedisStringCoroutinesCommands { /** * Set the string value of a key and return its old value. - * - * @param key the key. - * @param value the value. - * @return V bulk-string-reply the old value stored at `key`, or `null` when `key` did not exist. - * @since 6.2 + * + * @param key the key. + * @param value the value. + * @return V bulk-string-reply the old value stored at `key`, or `null` when `key` did not exist. + * @since 6.1 */ suspend fun setGet(key: K, value: V): V? /** - * Set the string value of a key and return its old value. - * - * @param key the key. - * @param value the value. - * @param setArgs the command arguments. - * @return V bulk-string-reply the old value stored at `key`, or `null` when `key` did not exist. - * @since 6.2 + * Set the string value of a key and return its old value. + * + * @param key the key. + * @param value the value. + * @param setArgs the command arguments. + * @return V bulk-string-reply the old value stored at `key`, or `null` when `key` did not exist. + * @since 6.1 */ suspend fun setGet(key: K, value: V, setArgs: SetArgs): V? diff --git a/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommands.kt index 9e69abb6d2..2e9930117a 100644 --- a/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommands.kt @@ -29,6 +29,7 @@ import io.lettuce.core.api.coroutines.* @ExperimentalLettuceCoroutinesApi interface RedisClusterCoroutinesCommands : BaseRedisCoroutinesCommands, + RedisAclCoroutinesCommands, RedisGeoCoroutinesCommands, RedisHashCoroutinesCommands, RedisHLLCoroutinesCommands, diff --git a/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommandsImpl.kt index 5da3830970..29cf406abf 100644 --- a/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/cluster/api/coroutines/RedisClusterCoroutinesCommandsImpl.kt @@ -36,6 +36,7 @@ internal class RedisClusterCoroutinesCommandsImpl( internal val ops: RedisClusterReactiveCommands ) : RedisClusterCoroutinesCommands, BaseRedisCoroutinesCommands by BaseRedisCoroutinesCommandsImpl(ops), + RedisAclCoroutinesCommands by RedisAclCoroutinesCommandsImpl(ops), RedisGeoCoroutinesCommands by RedisGeoCoroutinesCommandsImpl(ops), RedisHashCoroutinesCommands by RedisHashCoroutinesCommandsImpl(ops), RedisHLLCoroutinesCommands by RedisHLLCoroutinesCommandsImpl(ops), diff --git a/src/main/templates/io/lettuce/core/api/RedisAclCommands.java b/src/main/templates/io/lettuce/core/api/RedisAclCommands.java new file mode 100644 index 0000000000..442c37dde0 --- /dev/null +++ b/src/main/templates/io/lettuce/core/api/RedisAclCommands.java @@ -0,0 +1,146 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core; + +import io.lettuce.core.*; +import io.lettuce.core.protocol.CommandType; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ${intent} for the ACL-API. + * + * @author Mikhael Sokolov + * @since 6.1 + */ +public interface RedisAclCommands { + + /** + * The command shows the available ACL categories if called without arguments. + * + * @return List<AclCategory> a list of ACL categories or + */ + Set aclCat(); + + /** + * The command shows all the Redis commands in the specified category. + * + * @param category the specified category + * @return List<CommandType> a list of commands inside a given category + */ + Set aclCat(AclCategory category); + + /** + * Delete all the specified ACL users and terminate all the connections that are authenticated with such users. + * + * @param usernames the specified usernames + * @return Long The number of users that were deleted + */ + Long aclDeluser(String... usernames); + + /** + * The command generates a password. + * + * @return String bulk-string-reply 64 bytes string password representing 256 bits of pseudorandom data. + */ + String aclGenpass(); + + /** + * The command generates a password. + * + * @param bits amount of bits + * @return String bulk-string-reply N/4 bytes string password representing N bits of pseudorandom data. + */ + String aclGenpass(int bits); + + /** + * The command returns all the rules defined for an existing ACL user. + * + * @param username the specified username + * @return Map<String, Object> a map of ACL rule definitions for the user. + */ + List aclGetuser(String username); + + /** + * The command shows the currently active ACL rules in the Redis server. + * + * @return List<String> a list of strings. + */ + List aclList(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), this command + * will reload the ACLs from the file, replacing all the current ACL rules with the ones defined in the file. + * + * @return String simple-string-reply OK or error message. + */ + String aclLoad(); + + /** + * The command shows a list of recent ACL security events. + * + * @return List<Map<K,Object>> list of security events. + */ + List> aclLog(); + + /** + * The command shows a list of recent ACL security events. + * + * @param count max count of events + * @return List<Map<K, Object>> list of security events. + */ + List> aclLog(int count); + + /** + * The command clears ACL security events. + * + * @return String simple-string-reply OK if the security log was cleared. + */ + String aclLogReset(); + + /** + * When Redis is configured to use an ACL file (with the aclfile configuration option), + * this command will save the currently defined ACLs from the server memory to the ACL file. + * + * @return String simple-string-reply OK or error message. + */ + String aclSave(); + + /** + * Create an ACL user with the specified rules or modify the rules of an existing user. + * + * @param username the specified username + * @param setuserArgs rules + * @return String simple-string-reply OK or error message. + */ + String aclSetuser(String username, AclSetuserArgs setuserArgs); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return List<K> a list of usernames. + */ + List aclUsers(); + + /** + * The command shows a list of all the usernames of the currently configured users in the Redis ACL system. + * + * @return K bulk-string-reply the username of the current connection. + */ + String aclWhoami(); +} diff --git a/src/main/templates/io/lettuce/core/api/RedisGeoCommands.java b/src/main/templates/io/lettuce/core/api/RedisGeoCommands.java index de945c773d..55aef150c9 100644 --- a/src/main/templates/io/lettuce/core/api/RedisGeoCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisGeoCommands.java @@ -47,6 +47,19 @@ public interface RedisGeoCommands { */ Long geoadd(K key, Object... lngLatMember); + /** + * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. Default in meters by, otherwise according to {@code unit} + * + * @param key the key of the geo set. + * @param from from member. + * @param to to member. + * @param unit distance unit. + * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is + * returned. + */ + Double geodist(K key, V from, V to, GeoArgs.Unit unit); + /** * Retrieve Geohash strings representing the position of one or more elements in a sorted set value representing a * geospatial index. @@ -57,6 +70,16 @@ public interface RedisGeoCommands { */ List> geohash(K key, V... members); + /** + * Get geo coordinates for the {@code members}. + * + * @param key the key of the geo set. + * @param members the members. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@code null} is returned. + */ + List geopos(K key, V... members); + /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * @@ -138,26 +161,44 @@ Long georadius(K key, double longitude, double latitude, double distance, GeoArg Long georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoRadiusStoreArgs geoRadiusStoreArgs); /** - * Get geo coordinates for the {@code members}. + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param members the members. - * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For - * missing elements {@code null} is returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @return bulk reply. + * @since 6.1 */ - List geopos(K key, V... members); + Set geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate); /** - * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. Default in meters by, otherwise according to {@code unit} + * Retrieve members selected by distance with the center of {@code reference} the search {@code predicate}. + * Use {@link GeoSearch} to create reference and predicate objects. * * @param key the key of the geo set. - * @param from from member. - * @param to to member. - * @param unit distance unit. - * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@code null} is - * returned. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs}. + * @since 6.1 */ - Double geodist(K key, V from, V to, GeoArgs.Unit unit); + List> geosearch(K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs); + + /** + * Perform a {@link #geosearch(Object, GeoSearch.GeoRef, GeoSearch.GeoPredicate, GeoArgs)} query and store the results in a + * sorted set. + * + * @param destination the destination where to store results. + * @param key the key of the geo set. + * @param reference the reference member or longitude/latitude coordinates. + * @param predicate the bounding box or radius to search in. + * @param geoArgs args to control the result. + * @param storeDist stores the items in a sorted set populated with their distance from the center of the circle or box, as a floating-point number, in the same unit specified for that shape. + * @return Long integer-reply the number of elements in the result. + * @since 6.1 + */ + Long geosearchstore(K destination, K key, GeoSearch.GeoRef reference, GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, + boolean storeDist); } diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java index a904b93aa0..602a7ed23a 100644 --- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java @@ -15,10 +15,13 @@ */ package io.lettuce.core.api; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; -import io.lettuce.core.*; +import io.lettuce.core.KeyScanArgs; +import io.lettuce.core.RestoreArgs; import io.lettuce.core.output.KeyStreamingChannel; import io.lettuce.core.output.ValueStreamingChannel; @@ -38,7 +41,7 @@ public interface RedisKeyCommands { * @param source the source. * @param destination the destination. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Boolean copy(K source, K destination); @@ -49,7 +52,7 @@ public interface RedisKeyCommands { * @param destination the destination. * @param copyArgs the copyArgs. * @return Boolean integer-reply specifically: {@code true} if source was copied. {@code false} if source was not copied. - * @since 6.2 + * @since 6.1 */ Boolean copy(K source, K destination, CopyArgs copyArgs); @@ -91,22 +94,31 @@ public interface RedisKeyCommands { * @param key the key. * @param seconds the seconds type: long. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Boolean expire(K key, long seconds); + /** + * Set a key's time to live in seconds. + * + * @param key the key. + * @param seconds the seconds. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set. + * @since 6.1 + */ + Boolean expire(K key, Duration seconds); + /** * Set the expiration for a key as a UNIX timestamp. * * @param key the key. * @param timestamp the timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Boolean expireat(K key, Date timestamp); + Boolean expireat(K key, long timestamp); /** * Set the expiration for a key as a UNIX timestamp. @@ -114,11 +126,21 @@ public interface RedisKeyCommands { * @param key the key. * @param timestamp the timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Boolean expireat(K key, long timestamp); + Boolean expireat(K key, Date timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp. + * + * @param key the key. + * @param timestamp the timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + * @since 6.1 + */ + Boolean expireat(K key, Instant timestamp); /** * Find all keys matching the given pattern. @@ -212,22 +234,31 @@ public interface RedisKeyCommands { * @param key the key. * @param milliseconds the milliseconds type: long. * @return integer-reply, specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set. */ Boolean pexpire(K key, long milliseconds); + /** + * Set a key's time to live in milliseconds. + * + * @param key the key. + * @param milliseconds the milliseconds. + * @return integer-reply, specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not exist or + * the timeout could not be set. + * @since 6.1 + */ + Boolean pexpire(K key, Duration milliseconds); + /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. * * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Boolean pexpireat(K key, Date timestamp); + Boolean pexpireat(K key, long timestamp); /** * Set the expiration for a key as a UNIX timestamp specified in milliseconds. @@ -235,11 +266,20 @@ public interface RedisKeyCommands { * @param key the key. * @param timestamp the milliseconds-timestamp type: posix time. * @return Boolean integer-reply specifically: - * * {@code true} if the timeout was set. {@code false} if {@code key} does not exist or the timeout could not be set * (see: {@code EXPIRE}). */ - Boolean pexpireat(K key, long timestamp); + Boolean pexpireat(K key, Date timestamp); + + /** + * Set the expiration for a key as a UNIX timestamp specified in milliseconds. + * + * @param key the key. + * @param timestamp the milliseconds-timestamp type: posix time. + * @return Boolean integer-reply specifically: {@code true} if the timeout was set. {@code false} if {@code key} does not + * exist or the timeout could not be set (see: {@code EXPIRE}). + */ + Boolean pexpireat(K key, Instant timestamp); /** * Get the time to live for a key in milliseconds. diff --git a/src/main/templates/io/lettuce/core/api/RedisSortedSetCommands.java b/src/main/templates/io/lettuce/core/api/RedisSortedSetCommands.java index 51bcf55b76..124c494a1c 100644 --- a/src/main/templates/io/lettuce/core/api/RedisSortedSetCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisSortedSetCommands.java @@ -202,7 +202,7 @@ public interface RedisSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of elements. - * @since 6.2 + * @since 6.1 */ List zdiff(K... keys); @@ -212,7 +212,7 @@ public interface RedisSortedSetCommands { * @param destKey the dest key. * @param srcKeys the src keys. * @return Long the number of elements in the resulting sorted set at destination. - * @since 6.2 + * @since 6.1 */ Long zdiffstore(K destKey, K... srcKeys); @@ -221,7 +221,7 @@ public interface RedisSortedSetCommands { * * @param keys the keys. * @return List<V> array-reply list of scored values. - * @since 6.2 + * @since 6.1 */ List> zdiffWithScores(K... keys); @@ -765,7 +765,7 @@ Long zrangebyscoreWithScores(ScoredValueStreamingChannel channel, K key, Stri * @param srcKey the src key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -776,7 +776,7 @@ Long zrangebyscoreWithScores(ScoredValueStreamingChannel channel, K key, Stri * @param srcKey the src key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); @@ -1242,7 +1242,7 @@ Long zrevrangebyscoreWithScores(ScoredValueStreamingChannel channel, K key, S * @param srcKey the dst key. * @param range the lexicographical range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrevrangestorebylex(K dstKey, K srcKey, Range range, Limit limit); @@ -1253,7 +1253,7 @@ Long zrevrangebyscoreWithScores(ScoredValueStreamingChannel channel, K key, S * @param srcKey the dst key. * @param range the score range. * @return The number of elements in the resulting sorted set. - * @since 6.2 + * @since 6.1 */ Long zrevrangestorebyscore(K dstKey, K srcKey, Range range, Limit limit); diff --git a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java index ef40ccbf68..f703270dab 100644 --- a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java @@ -78,7 +78,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -98,7 +98,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -120,7 +120,7 @@ public interface RedisStringCommands { * returned. * * If we look for clear bits (the bit argument is 0) and the string only contains bit set to 1, the function returns - * the first bit not part of the string on the right. So if the string is tree bytes set to the value 0xff the + * the first bit not part of the string on the right. So if the string is three bytes set to the value 0xff the * command {@code BITPOS key 0} will return 24, since up to bit 23 all the bits are 1. * * Basically the function consider the right of the string as padded with zeros if you look for clear bits and @@ -311,7 +311,7 @@ public interface RedisStringCommands { * @param key the key. * @param value the value. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ V setGet(K key, V value); @@ -322,7 +322,7 @@ public interface RedisStringCommands { * @param value the value. * @param setArgs the command arguments. * @return V bulk-string-reply the old value stored at {@code key}, or {@code null} when {@code key} did not exist. - * @since 6.2 + * @since 6.1 */ V setGet(K key, V value, SetArgs setArgs); diff --git a/src/test/java/io/lettuce/apigenerator/Constants.java b/src/test/java/io/lettuce/apigenerator/Constants.java index 25075f4308..4772f1c4f8 100644 --- a/src/test/java/io/lettuce/apigenerator/Constants.java +++ b/src/test/java/io/lettuce/apigenerator/Constants.java @@ -24,6 +24,7 @@ class Constants { public static final String[] TEMPLATE_NAMES = { "BaseRedisCommands", + "RedisAclCommands", "RedisGeoCommands", "RedisHashCommands", "RedisHLLCommands", diff --git a/src/test/java/io/lettuce/apigenerator/CreateReactiveApi.java b/src/test/java/io/lettuce/apigenerator/CreateReactiveApi.java index 3ad811eea4..36d34e1cb1 100644 --- a/src/test/java/io/lettuce/apigenerator/CreateReactiveApi.java +++ b/src/test/java/io/lettuce/apigenerator/CreateReactiveApi.java @@ -16,7 +16,11 @@ package io.lettuce.apigenerator; import java.io.File; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -53,6 +57,9 @@ public class CreateReactiveApi { Map resultSpec = new HashMap<>(); resultSpec.put("geopos", "Flux>"); + resultSpec.put("aclCat()", "Mono>"); + resultSpec.put("aclCat(AclCategory category)", "Mono>"); + resultSpec.put("aclGetuser", "Mono>"); resultSpec.put("bitfield", "Flux>"); resultSpec.put("hgetall", "Flux>"); resultSpec.put("zmscore", "Mono>"); // Redis returns null if element was not found diff --git a/src/test/java/io/lettuce/apigenerator/KotlinCompilationUnitFactory.java b/src/test/java/io/lettuce/apigenerator/KotlinCompilationUnitFactory.java index eb895dcb48..1f38a37917 100644 --- a/src/test/java/io/lettuce/apigenerator/KotlinCompilationUnitFactory.java +++ b/src/test/java/io/lettuce/apigenerator/KotlinCompilationUnitFactory.java @@ -59,7 +59,9 @@ class KotlinCompilationUnitFactory { private static final Set SKIP_IMPORTS = LettuceSets.unmodifiableSet("java.util.List", "java.util.Set", "java.util.Map"); private static final Set NON_SUSPENDABLE_METHODS = LettuceSets.unmodifiableSet("isOpen", "flushCommands", "setAutoFlushCommands"); private static final Set SKIP_METHODS = LettuceSets.unmodifiableSet("BaseRedisCommands.reset", "getStatefulConnection"); - private static final Set FLOW_METHODS = LettuceSets.unmodifiableSet("dispatch", "geohash", "georadius", "georadiusbymember", + + private static final Set FLOW_METHODS = LettuceSets.unmodifiableSet("aclList", "aclLog", "dispatch", "geohash", "georadius", + "georadiusbymember", "geosearch", "hgetall", "hkeys", "hmget", "hvals", "keys", "mget", "sdiff", "sinter", "smembers", "smismember", "sort", "srandmember", "sunion", "xclaim", "xpending", "xrange", "xread", "xreadgroup", "xrevrange", "zdiff", "zdiffWithScores", "zinter", "zinterWithScores", "zpopmax", "zpopmin", "zrange", "zrangeWithScores", "zrangebylex", "zrangebyscore", "zrangebyscoreWithScores", "zrevrange", "zrevrangeWithScores", "zrevrangebylex", @@ -75,7 +77,7 @@ class KotlinCompilationUnitFactory { static { Map resultSpec = new HashMap<>(); - resultSpec.put("hgetall", "Flow>"); + resultSpec.put("hgetall", "Flow>"); resultSpec.put("zmscore", "List"); RESULT_SPEC = resultSpec; } @@ -249,10 +251,10 @@ private String toKotlinType(Type type, boolean isFlowable, boolean isForceNonNul } else if (isFlowable) { fixedType = type .asString() - .replace("List", "Flow") - .replace("Set", "Flow") - .replace("Map", "Flow") - .replace("T", "Flow"); + .replaceFirst("List|Map|Set", "Flow") + .replace("T", "Flow") + .replace("Object", "Any") + .replace(",", ", "); } else { fixedType = type .asString() diff --git a/src/test/java/io/lettuce/core/cluster/ReadFromUnitTests.java b/src/test/java/io/lettuce/core/cluster/ReadFromUnitTests.java index f41e9c14f4..7efbea700f 100644 --- a/src/test/java/io/lettuce/core/cluster/ReadFromUnitTests.java +++ b/src/test/java/io/lettuce/core/cluster/ReadFromUnitTests.java @@ -17,15 +17,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.regex.Pattern; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.lettuce.core.ReadFrom; +import io.lettuce.core.RedisURI; import io.lettuce.core.cluster.models.partitions.Partitions; import io.lettuce.core.cluster.models.partitions.RedisClusterNode; import io.lettuce.core.models.role.RedisNodeDescription; @@ -34,6 +38,7 @@ * @author Mark Paluch * @author Ryosuke Hasebe * @author Omer Cilingir + * @author Yohei Ueki */ class ReadFromUnitTests { @@ -90,6 +95,114 @@ void anyReplica() { assertThat(result).hasSize(2).containsExactly(nearest, replica); } + @Test + void subnetIpv4RuleIpv6NodeGiven() { + ReadFrom sut = ReadFrom.subnet("0.0.0.0/0"); + RedisClusterNode ipv6node = createNodeWithHost("2001:db8:abcd:1000::"); + + List result = sut.select(getNodes(ipv6node)); + + assertThat(result).isEmpty(); + } + + @Test + void subnetIpv4RuleAnyNode() { + ReadFrom sut = ReadFrom.subnet("0.0.0.0/0"); + RedisClusterNode node = createNodeWithHost("192.0.2.1"); + + List result = sut.select(getNodes(node)); + + assertThat(result).hasSize(1).containsExactly(node); + } + + @Test + void subnetIpv6RuleIpv4NodeGiven() { + ReadFrom sut = ReadFrom.subnet("::/0"); + RedisClusterNode node = createNodeWithHost("192.0.2.1"); + + List result = sut.select(getNodes(node)); + + assertThat(result).isEmpty(); + } + + @Test + void subnetIpv6RuleAnyNode() { + ReadFrom sut = ReadFrom.subnet("::/0"); + RedisClusterNode node = createNodeWithHost("2001:db8:abcd:1000::"); + + List result = sut.select(getNodes(node)); + + assertThat(result).hasSize(1).containsExactly(node); + } + + @Test + void subnetIpv4Ipv6Mixed() { + ReadFrom sut = ReadFrom.subnet("192.0.2.0/24", "2001:db8:abcd:0000::/52"); + + RedisClusterNode nodeInSubnetIpv4 = createNodeWithHost("192.0.2.1"); + RedisClusterNode nodeNotInSubnetIpv4 = createNodeWithHost("198.51.100.1"); + RedisClusterNode nodeInSubnetIpv6 = createNodeWithHost("2001:db8:abcd:0000::1"); + RedisClusterNode nodeNotInSubnetIpv6 = createNodeWithHost("2001:db8:abcd:1000::"); + + List result = sut + .select(getNodes(nodeInSubnetIpv4, nodeNotInSubnetIpv4, nodeInSubnetIpv6, nodeNotInSubnetIpv6)); + + assertThat(result).hasSize(2).containsExactly(nodeInSubnetIpv4, nodeInSubnetIpv6); + } + + @Test + void subnetNodeWithHostname() { + ReadFrom sut = ReadFrom.subnet("0.0.0.0/0"); + + RedisClusterNode hostNode = createNodeWithHost("example.com"); + RedisClusterNode localhostNode = createNodeWithHost("localhost"); + + List result = sut.select(getNodes(hostNode, localhostNode)); + + assertThat(result).isEmpty(); + } + + @Test + void subnetCidrValidation() { + // malformed CIDR notation + assertThatThrownBy(() -> ReadFrom.subnet("192.0.2.1//1")).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ReadFrom.subnet("2001:db8:abcd:0000:://52")).isInstanceOf(IllegalArgumentException.class); + // malformed ipAddress + assertThatThrownBy(() -> ReadFrom.subnet("foo.bar/12")).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ReadFrom.subnet("zzzz:db8:abcd:0000:://52")).isInstanceOf(IllegalArgumentException.class); + // malformed cidrPrefix + assertThatThrownBy(() -> ReadFrom.subnet("192.0.2.1/40")).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ReadFrom.subnet("192.0.2.1/foo")).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ReadFrom.subnet("2001:db8:abcd:0000/129")).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ReadFrom.subnet("2001:db8:abcd:0000/-1")).isInstanceOf(IllegalArgumentException.class); + + // acceptable cidrPrefix + assertDoesNotThrow(() -> ReadFrom.subnet("0.0.0.0/0")); + assertDoesNotThrow(() -> ReadFrom.subnet("0.0.0.0/32")); + assertDoesNotThrow(() -> ReadFrom.subnet("::/0")); + assertDoesNotThrow(() -> ReadFrom.subnet("::/128")); + } + + @Test + void regex() { + ReadFrom sut = ReadFrom.regex(Pattern.compile(".*region-1.*")); + + RedisClusterNode node1 = createNodeWithHost("redis-node-1.region-1.example.com"); + RedisClusterNode node2 = createNodeWithHost("redis-node-2.region-1.example.com"); + RedisClusterNode node3 = createNodeWithHost("redis-node-1.region-2.example.com"); + RedisClusterNode node4 = createNodeWithHost("redis-node-2.region-2.example.com"); + + List result = sut.select(getNodes(node1, node2, node3, node4)); + + assertThat(result).hasSize(2).containsExactly(node1, node2); + } + + private RedisClusterNode createNodeWithHost(String host) { + RedisClusterNode node = new RedisClusterNode(); + node.setUri(RedisURI.Builder.redis(host).build()); + return node; + } + @Test void valueOfNull() { assertThatThrownBy(() -> ReadFrom.valueOf(null)).isInstanceOf(IllegalArgumentException.class); @@ -130,6 +243,16 @@ void valueOfAnyReplica() { assertThat(ReadFrom.valueOf("anyReplica")).isEqualTo(ReadFrom.ANY_REPLICA); } + @Test + void valueOfSubnet() { + assertThatThrownBy(() -> ReadFrom.valueOf("subnet")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void valueOfRegex() { + assertThatThrownBy(() -> ReadFrom.valueOf("regex")).isInstanceOf(IllegalArgumentException.class); + } + private ReadFrom.Nodes getNodes() { return new ReadFrom.Nodes() { @Override @@ -144,4 +267,22 @@ public Iterator iterator() { }; } + + private ReadFrom.Nodes getNodes(RedisNodeDescription... nodes) { + return new ReadFrom.Nodes() { + + @Override + public List getNodes() { + return Arrays.asList(nodes); + } + + @Override + public Iterator iterator() { + return getNodes().iterator(); + } + + }; + + } + } diff --git a/src/test/java/io/lettuce/core/cluster/RedisClusterReadFromIntegrationTests.java b/src/test/java/io/lettuce/core/cluster/RedisClusterReadFromIntegrationTests.java index 319c47a472..c909d79173 100644 --- a/src/test/java/io/lettuce/core/cluster/RedisClusterReadFromIntegrationTests.java +++ b/src/test/java/io/lettuce/core/cluster/RedisClusterReadFromIntegrationTests.java @@ -17,6 +17,8 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.regex.Pattern; + import javax.inject.Inject; import org.junit.jupiter.api.AfterEach; @@ -32,6 +34,7 @@ /** * @author Mark Paluch + * @author Yohei Ueki */ @SuppressWarnings("unchecked") @ExtendWith(LettuceExtension.class) @@ -112,4 +115,27 @@ void readWriteNearest() { connection.getConnection(ClusterTestSettings.host, ClusterTestSettings.port2).sync().waitForReplication(1, 1000); assertThat(sync.get(key)).isEqualTo("value1"); } + + @Test + void readWriteSubnet() { + + connection.setReadFrom(ReadFrom.subnet("0.0.0.0/0", "::/0")); + + sync.set(key, "value1"); + + connection.getConnection(ClusterTestSettings.host, ClusterTestSettings.port2).sync().waitForReplication(1, 1000); + assertThat(sync.get(key)).isEqualTo("value1"); + } + + @Test + void readWriteRegex() { + + connection.setReadFrom(ReadFrom.regex(Pattern.compile(".*"))); + + sync.set(key, "value1"); + + connection.getConnection(ClusterTestSettings.host, ClusterTestSettings.port2).sync().waitForReplication(1, 1000); + assertThat(sync.get(key)).isEqualTo("value1"); + } + } diff --git a/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java new file mode 100644 index 0000000000..0e85a3a9c8 --- /dev/null +++ b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.commands; + +import static org.assertj.core.api.Assertions.*; + +import javax.inject.Inject; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.lettuce.core.AclCategory; +import io.lettuce.core.AclSetuserArgs; +import io.lettuce.core.RedisCommandExecutionException; +import io.lettuce.core.TestSupport; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.protocol.CommandType; +import io.lettuce.test.LettuceExtension; +import io.lettuce.test.condition.EnabledOnCommand; + +/** + * Integration tests for ACL commands. + * + * @author Mikhael Sokolov + * @author Mark Paluch + */ +@ExtendWith(LettuceExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@EnabledOnCommand("ACL") +public class AclCommandIntegrationTests extends TestSupport { + + private final RedisCommands redis; + + @Inject + protected AclCommandIntegrationTests(RedisCommands redis) { + this.redis = redis; + } + + @BeforeEach + void setUp() { + redis.flushall(); + redis.aclUsers().stream().filter(o -> !"default".equals(o)).forEach(redis::aclDeluser); + redis.aclLogReset(); + + } + + @Test + public void aclCat() { + assertThat(redis.aclCat()).isNotEmpty(); + assertThat(redis.aclCat(AclCategory.SLOW)).isNotEmpty(); + } + + @Test + void aclDeluser() { + assertThat(redis.aclDeluser("non-existing")).isZero(); + } + + @Test + void aclGenpass() { + assertThat(redis.aclGenpass()).hasSize(64); + assertThat(redis.aclGenpass(128)).hasSize(32); + } + + @Test + void aclGetuser() { + assertThat(redis.aclGetuser("default")).contains("flags"); + } + + @Test + void aclLoad() { + assertThatThrownBy(redis::aclLoad).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); + } + + @Test + void aclLog() { + assertThat(redis.aclLogReset()).isEqualTo("OK"); + assertThatThrownBy(() -> redis.auth("non-existing1", "foobar")); + assertThatThrownBy(() -> redis.auth("non-existing2", "foobar")); + assertThat(redis.aclLog()).hasSize(2).first().hasFieldOrProperty("reason"); + assertThat(redis.aclLog(1)).hasSize(1); + assertThat(redis.aclLogReset()).isEqualTo("OK"); + assertThat(redis.aclLog()).isEmpty(); + } + + @Test + void aclList() { + assertThat(redis.aclList()).hasSize(1).first().asString().contains("user default"); + } + + @Test + void aclSave() { + assertThatThrownBy(redis::aclSave).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); + } + + @Test + void aclSetuser() { + assertThat(redis.aclDeluser("foo")).isNotNull(); + AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*").addPassword("foobared"); + assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK"); + assertThat(redis.aclGetuser("foo")).contains("commands").contains("passwords").contains("keys"); + assertThat(redis.aclDeluser("foo")).isNotNull(); + } + + @Test + void aclUsers() { + assertThat(redis.aclUsers()).hasSize(1).first().isEqualTo("default"); + } + + @Test + void aclWhoami() { + assertThat(redis.aclWhoami()).isEqualTo("default"); + } +} diff --git a/src/test/java/io/lettuce/core/commands/GeoCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/GeoCommandIntegrationTests.java index 0892f77273..6b3aee37a6 100644 --- a/src/test/java/io/lettuce/core/commands/GeoCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/GeoCommandIntegrationTests.java @@ -15,10 +15,8 @@ */ package io.lettuce.core.commands; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.offset; -import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; import java.util.List; import java.util.Set; @@ -30,7 +28,15 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; -import io.lettuce.core.*; +import io.lettuce.core.GeoArgs; +import io.lettuce.core.GeoCoordinates; +import io.lettuce.core.GeoRadiusStoreArgs; +import io.lettuce.core.GeoSearch; +import io.lettuce.core.GeoWithin; +import io.lettuce.core.ScoredValue; +import io.lettuce.core.TestSupport; +import io.lettuce.core.TransactionResult; +import io.lettuce.core.Value; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.test.LettuceExtension; import io.lettuce.test.condition.EnabledOnCommand; @@ -516,6 +522,58 @@ void georadiusStorebymemberWithNullArgs() { .isInstanceOf(IllegalArgumentException.class); } + @Test + @EnabledOnCommand("GEOSEARCH") + void geosearchWithCountAndSort() { + + prepareGeo(); + + Set empty = redis.geosearch(key, GeoSearch.fromMember("Bahn"), GeoSearch.byRadius(1, GeoArgs.Unit.km)); + assertThat(empty).hasSize(1).contains("Bahn"); + + Set radius = redis.geosearch(key, GeoSearch.fromMember("Bahn"), GeoSearch.byRadius(5, GeoArgs.Unit.km)); + assertThat(radius).hasSize(2).contains("Bahn", "Weinheim"); + + Set box = redis.geosearch(key, GeoSearch.fromMember("Bahn"), GeoSearch.byBox(6, 3, GeoArgs.Unit.km)); + assertThat(box).hasSize(2).contains("Bahn", "Weinheim"); + } + + @Test + @EnabledOnCommand("GEOSEARCH") + void geosearchWithArgs() { + + prepareGeo(); + + List> empty = redis.geosearch(key, GeoSearch.fromMember("Bahn"), + GeoSearch.byRadius(1, GeoArgs.Unit.km), new GeoArgs().withHash().withCoordinates().withDistance().desc()); + assertThat(empty).isNotEmpty(); + + List> withDistanceAndCoordinates = redis.geosearch(key, GeoSearch.fromMember("Bahn"), + GeoSearch.byRadius(5, GeoArgs.Unit.km), new GeoArgs().withCoordinates().withDistance().desc()); + assertThat(withDistanceAndCoordinates).hasSize(2); + + GeoWithin weinheim = withDistanceAndCoordinates.get(0); + assertThat(weinheim.getMember()).isEqualTo("Weinheim"); + assertThat(weinheim.getGeohash()).isNull(); + assertThat(weinheim.getDistance()).isNotNull(); + assertThat(weinheim.getCoordinates()).isNotNull(); + } + + @Test + @EnabledOnCommand("GEOSEARCHSTORE") + void geosearchStoreWithCountAndSort() { + + prepareGeo(); + + String resultKey = "38o54"; // yields in same slot as "key" + Long result = redis.geosearchstore(resultKey, key, GeoSearch.fromMember("Bahn"), GeoSearch.byRadius(5, GeoArgs.Unit.km), + new GeoArgs(), true); + assertThat(result).isEqualTo(2); + + List> dist = redis.zrangeWithScores(resultKey, 0, -1); + assertThat(dist).hasSize(2); + } + protected void prepareGeo() { redis.geoadd(key, 8.6638775, 49.5282537, "Weinheim"); redis.geoadd(key, 8.3796281, 48.9978127, "EFS9", 8.665351, 49.553302, "Bahn"); diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java index 5e795cc563..dd694184be 100644 --- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; @@ -139,7 +140,10 @@ void expire() { assertThat(redis.expire(key, 10)).isFalse(); redis.set(key, value); assertThat(redis.expire(key, 10)).isTrue(); - assertThat((long) redis.ttl(key)).isEqualTo(10); + assertThat(redis.ttl(key)).isBetween(5L, 10L); + + redis.expire(key, Duration.ofSeconds(20)); + assertThat(redis.ttl(key)).isBetween(10L, 20L); } @Test @@ -150,6 +154,9 @@ void expireat() { assertThat(redis.expireat(key, expiration)).isTrue(); assertThat(redis.ttl(key)).isGreaterThanOrEqualTo(8); + + assertThat(redis.expireat(key, Instant.now().plusSeconds(15))).isTrue(); + assertThat(redis.ttl(key)).isBetween(10L, 20L); } @Test @@ -229,6 +236,9 @@ void pexpire() { redis.set(key, value); assertThat(redis.pexpire(key, 5000)).isTrue(); assertThat(redis.pttl(key)).isGreaterThan(0).isLessThanOrEqualTo(5000); + + redis.pexpire(key, Duration.ofSeconds(20)); + assertThat(redis.ttl(key)).isBetween(10L, 20L); } @Test @@ -238,6 +248,9 @@ void pexpireat() { redis.set(key, value); assertThat(redis.pexpireat(key, expiration)).isTrue(); assertThat(redis.pttl(key)).isGreaterThan(0).isLessThanOrEqualTo(5000); + + assertThat(redis.pexpireat(key, Instant.now().plusSeconds(15))).isTrue(); + assertThat(redis.ttl(key)).isBetween(10L, 20L); } @Test diff --git a/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java index b933b519a7..198f4c3e07 100644 --- a/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/StreamCommandIntegrationTests.java @@ -181,6 +181,20 @@ void xrange() { assertThat(range.get(0).getBody()).isEqualTo(expectedBody); } + @Test + @EnabledOnCommand("XAUTOCLAIM") // Redis 6.2 + void xrangeRanges() { + + String id1 = redis.xadd(key, Collections.singletonMap("key", "value")); + String id2 = redis.xadd(key, Collections.singletonMap("key", "value")); + String id3 = redis.xadd(key, Collections.singletonMap("key", "value")); + + assertThat(redis.xrange(key, Range.unbounded())).hasSize(3); + assertThat(redis.xrange(key, Range.from(Range.Boundary.including(id1), Range.Boundary.excluding(id3)))).hasSize(2); + assertThat(redis.xrange(key, Range.from(Range.Boundary.excluding(id1), Range.Boundary.excluding(id3)))).hasSize(1); + assertThat(redis.xrange(key, Range.from(Range.Boundary.excluding(id1), Range.Boundary.including(id3)))).hasSize(2); + } + @Test void xrevrange() { @@ -412,6 +426,7 @@ void xpending() { } @Test + @EnabledOnCommand("XAUTOCLAIM") // Redis 6.2 void xpendingWithArgs() { redis.xgroupCreate(StreamOffset.latest(key), "group", XGroupCreateArgs.Builder.mkstream()); @@ -428,6 +443,23 @@ void xpendingWithArgs() { assertThat(message.getRedeliveryCount()).isEqualTo(1); } + @Test + @EnabledOnCommand("XAUTOCLAIM") // Redis 6.2 + void xpendingRanges() { + + redis.xgroupCreate(StreamOffset.latest(key), "group", XGroupCreateArgs.Builder.mkstream()); + String id1 = redis.xadd(key, Collections.singletonMap("key", "value")); + String id2 = redis.xadd(key, Collections.singletonMap("key", "value")); + + redis.xreadgroup(Consumer.from("group", "consumer1"), StreamOffset.lastConsumed(key)); + + assertThat(redis.xpending(key, "group", Range.unbounded(), Limit.from(10))).hasSize(2); + assertThat(redis.xpending(key, "group", Range.from(Range.Boundary.including(id1), Range.Boundary.excluding(id2)), + Limit.from(10))).hasSize(1); + assertThat(redis.xpending(key, "group", Range.from(Range.Boundary.including(id1), Range.Boundary.including(id2)), + Limit.from(10))).hasSize(2); + } + @Test void xpendingWithoutMessages() { diff --git a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java index 076598cdc6..78273b24bf 100644 --- a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java @@ -19,6 +19,8 @@ import static io.lettuce.core.StringMatchResult.*; import static org.assertj.core.api.Assertions.*; +import java.time.Duration; +import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -156,6 +158,12 @@ void set() { assertThat(redis.get(key)).isEqualTo(value); assertThat(redis.ttl(key)).isGreaterThanOrEqualTo(9); + assertThat(redis.set(key, value, ex(Duration.ofSeconds(10)))).isEqualTo("OK"); + assertThat(redis.ttl(key)).isBetween(5L, 10L); + + assertThat(redis.set(key, value, px(Duration.ofSeconds(10)))).isEqualTo("OK"); + assertThat(redis.ttl(key)).isBetween(5L, 10L); + assertThat(redis.set(key, value, px(10000))).isEqualTo("OK"); assertThat(redis.get(key)).isEqualTo(value); assertThat(redis.ttl(key)).isGreaterThanOrEqualTo(9); @@ -175,6 +183,17 @@ void set() { assertThat(redis.ttl(key) >= 19).isTrue(); } + @Test + @EnabledOnCommand("ZMSCORE") // Redis 6.2 + void setExAt() { + + assertThat(redis.set(key, value, exAt(Instant.now().plusSeconds(60)))).isEqualTo("OK"); + assertThat(redis.ttl(key)).isBetween(50L, 61L); + + assertThat(redis.set(key, value, pxAt(Instant.now().plusSeconds(60)))).isEqualTo("OK"); + assertThat(redis.ttl(key)).isBetween(50L, 61L); + } + @Test @EnabledOnCommand("ACL") // Redis 6.0 guard void setKeepTTL() { diff --git a/src/test/java/io/lettuce/core/commands/reactive/AclReactiveCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/reactive/AclReactiveCommandIntegrationTests.java new file mode 100644 index 0000000000..aeea21d529 --- /dev/null +++ b/src/test/java/io/lettuce/core/commands/reactive/AclReactiveCommandIntegrationTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.commands.reactive; + +import javax.inject.Inject; + +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.commands.AclCommandIntegrationTests; +import io.lettuce.test.ReactiveSyncInvocationHandler; + +/** + * Integration tests though the reactive facade for {@link AclCommandIntegrationTests}. + * + * @author Mark Paluch + */ +class AclReactiveCommandIntegrationTests extends AclCommandIntegrationTests { + + @Inject + AclReactiveCommandIntegrationTests(StatefulRedisConnection connection) { + super(ReactiveSyncInvocationHandler.sync(connection)); + } + +} diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriterUnitTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriterUnitTests.java similarity index 61% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriterUnitTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriterUnitTests.java index 1bae53fc87..72a026eb89 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaChannelWriterUnitTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaChannelWriterUnitTests.java @@ -46,10 +46,10 @@ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class UpstreamReplicaChannelWriterUnitTests { +class MasterReplicaChannelWriterUnitTests { @Mock - private UpstreamReplicaConnectionProvider connectionProvider; + private MasterReplicaConnectionProvider connectionProvider; @Mock private ClientResources clientResources; @@ -63,18 +63,18 @@ void shouldReturnIntentForWriteCommand() { RedisCommand set = new Command<>(CommandType.SET, null); RedisCommand mset = new Command<>(CommandType.MSET, null); - assertThat(UpstreamReplicaChannelWriter.getIntent(Arrays.asList(set, mset))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.WRITE); + assertThat(MasterReplicaChannelWriter.getIntent(Arrays.asList(set, mset))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.WRITE); - assertThat(UpstreamReplicaChannelWriter.getIntent(Collections.singletonList(set))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.WRITE); + assertThat(MasterReplicaChannelWriter.getIntent(Collections.singletonList(set))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.WRITE); } @Test void shouldReturnDefaultIntentForNoCommands() { - assertThat(UpstreamReplicaChannelWriter.getIntent(Collections.emptyList())) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.WRITE); + assertThat(MasterReplicaChannelWriter.getIntent(Collections.emptyList())) + .isEqualTo(MasterReplicaConnectionProvider.Intent.WRITE); } @Test @@ -83,11 +83,11 @@ void shouldReturnIntentForReadCommand() { RedisCommand get = new Command<>(CommandType.GET, null); RedisCommand mget = new Command<>(CommandType.MGET, null); - assertThat(UpstreamReplicaChannelWriter.getIntent(Arrays.asList(get, mget))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.READ); + assertThat(MasterReplicaChannelWriter.getIntent(Arrays.asList(get, mget))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.READ); - assertThat(UpstreamReplicaChannelWriter.getIntent(Collections.singletonList(get))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.READ); + assertThat(MasterReplicaChannelWriter.getIntent(Collections.singletonList(get))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.READ); } @Test @@ -96,34 +96,34 @@ void shouldReturnIntentForMixedCommands() { RedisCommand set = new Command<>(CommandType.SET, null); RedisCommand mget = new Command<>(CommandType.MGET, null); - assertThat(UpstreamReplicaChannelWriter.getIntent(Arrays.asList(set, mget))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.WRITE); + assertThat(MasterReplicaChannelWriter.getIntent(Arrays.asList(set, mget))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.WRITE); - assertThat(UpstreamReplicaChannelWriter.getIntent(Collections.singletonList(set))) - .isEqualTo(UpstreamReplicaConnectionProvider.Intent.WRITE); + assertThat(MasterReplicaChannelWriter.getIntent(Collections.singletonList(set))) + .isEqualTo(MasterReplicaConnectionProvider.Intent.WRITE); } @Test void shouldBindTransactionsToMaster() { - UpstreamReplicaChannelWriter writer = new UpstreamReplicaChannelWriter(connectionProvider, clientResources); + MasterReplicaChannelWriter writer = new MasterReplicaChannelWriter(connectionProvider, clientResources); - when(connectionProvider.getConnectionAsync(any(UpstreamReplicaConnectionProvider.Intent.class))) + when(connectionProvider.getConnectionAsync(any(MasterReplicaConnectionProvider.Intent.class))) .thenReturn(CompletableFuture.completedFuture(connection)); writer.write(mockCommand(CommandType.MULTI)); writer.write(mockCommand(CommandType.GET)); writer.write(mockCommand(CommandType.EXEC)); - verify(connectionProvider, times(3)).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.WRITE); + verify(connectionProvider, times(3)).getConnectionAsync(MasterReplicaConnectionProvider.Intent.WRITE); } @Test void shouldBindTransactionsToMasterInBatch() { - UpstreamReplicaChannelWriter writer = new UpstreamReplicaChannelWriter(connectionProvider, clientResources); + MasterReplicaChannelWriter writer = new MasterReplicaChannelWriter(connectionProvider, clientResources); - when(connectionProvider.getConnectionAsync(any(UpstreamReplicaConnectionProvider.Intent.class))) + when(connectionProvider.getConnectionAsync(any(MasterReplicaConnectionProvider.Intent.class))) .thenReturn(CompletableFuture.completedFuture(connection)); List> commands = Arrays.asList(mockCommand(CommandType.MULTI), @@ -131,47 +131,47 @@ void shouldBindTransactionsToMasterInBatch() { writer.write(commands); - verify(connectionProvider).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.WRITE); + verify(connectionProvider).getConnectionAsync(MasterReplicaConnectionProvider.Intent.WRITE); } @Test void shouldDeriveIntentFromCommandTypeAfterTransaction() { - UpstreamReplicaChannelWriter writer = new UpstreamReplicaChannelWriter(connectionProvider, clientResources); + MasterReplicaChannelWriter writer = new MasterReplicaChannelWriter(connectionProvider, clientResources); - when(connectionProvider.getConnectionAsync(any(UpstreamReplicaConnectionProvider.Intent.class))) + when(connectionProvider.getConnectionAsync(any(MasterReplicaConnectionProvider.Intent.class))) .thenReturn(CompletableFuture.completedFuture(connection)); writer.write(mockCommand(CommandType.MULTI)); writer.write(mockCommand(CommandType.EXEC)); writer.write(mockCommand(CommandType.GET)); - verify(connectionProvider, times(2)).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.WRITE); - verify(connectionProvider).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.READ); + verify(connectionProvider, times(2)).getConnectionAsync(MasterReplicaConnectionProvider.Intent.WRITE); + verify(connectionProvider).getConnectionAsync(MasterReplicaConnectionProvider.Intent.READ); } @Test void shouldDeriveIntentFromCommandTypeAfterDiscardedTransaction() { - UpstreamReplicaChannelWriter writer = new UpstreamReplicaChannelWriter(connectionProvider, clientResources); + MasterReplicaChannelWriter writer = new MasterReplicaChannelWriter(connectionProvider, clientResources); - when(connectionProvider.getConnectionAsync(any(UpstreamReplicaConnectionProvider.Intent.class))) + when(connectionProvider.getConnectionAsync(any(MasterReplicaConnectionProvider.Intent.class))) .thenReturn(CompletableFuture.completedFuture(connection)); writer.write(mockCommand(CommandType.MULTI)); writer.write(mockCommand(CommandType.DISCARD)); writer.write(mockCommand(CommandType.GET)); - verify(connectionProvider, times(2)).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.WRITE); - verify(connectionProvider).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.READ); + verify(connectionProvider, times(2)).getConnectionAsync(MasterReplicaConnectionProvider.Intent.WRITE); + verify(connectionProvider).getConnectionAsync(MasterReplicaConnectionProvider.Intent.READ); } @Test void shouldDeriveIntentFromCommandBatchTypeAfterDiscardedTransaction() { - UpstreamReplicaChannelWriter writer = new UpstreamReplicaChannelWriter(connectionProvider, clientResources); + MasterReplicaChannelWriter writer = new MasterReplicaChannelWriter(connectionProvider, clientResources); - when(connectionProvider.getConnectionAsync(any(UpstreamReplicaConnectionProvider.Intent.class))) + when(connectionProvider.getConnectionAsync(any(MasterReplicaConnectionProvider.Intent.class))) .thenReturn(CompletableFuture.completedFuture(connection)); List> commands = Arrays.asList(mockCommand(CommandType.MULTI), @@ -180,8 +180,8 @@ void shouldDeriveIntentFromCommandBatchTypeAfterDiscardedTransaction() { writer.write(commands); writer.write(Collections.singletonList(mockCommand(CommandType.GET))); - verify(connectionProvider).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.WRITE); - verify(connectionProvider).getConnectionAsync(UpstreamReplicaConnectionProvider.Intent.READ); + verify(connectionProvider).getConnectionAsync(MasterReplicaConnectionProvider.Intent.WRITE); + verify(connectionProvider).getConnectionAsync(MasterReplicaConnectionProvider.Intent.READ); } private static Command mockCommand(CommandType multi) { diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProviderUnitTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProviderUnitTests.java similarity index 86% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProviderUnitTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProviderUnitTests.java index 3019fcb972..d6511a1836 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaConnectionProviderUnitTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaConnectionProviderUnitTests.java @@ -47,9 +47,9 @@ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class UpstreamReplicaConnectionProviderUnitTests { +class MasterReplicaConnectionProviderUnitTests { - private UpstreamReplicaConnectionProvider sut; + private MasterReplicaConnectionProvider sut; @Mock RedisClient clientMock; @@ -66,10 +66,10 @@ class UpstreamReplicaConnectionProviderUnitTests { void before() { nodeConnectionMock = (StatefulRedisConnection) channelHandlerMock; - sut = new UpstreamReplicaConnectionProvider<>(clientMock, StringCodec.UTF8, RedisURI.create("localhost", 1), + sut = new MasterReplicaConnectionProvider<>(clientMock, StringCodec.UTF8, RedisURI.create("localhost", 1), Collections.emptyMap()); sut.setKnownNodes(Arrays.asList( - new RedisUpstreamReplicaNode("localhost", 1, RedisURI.create("localhost", 1), RedisInstance.Role.UPSTREAM))); + new RedisMasterReplicaNode("localhost", 1, RedisURI.create("localhost", 1), RedisInstance.Role.UPSTREAM))); } @Test @@ -80,7 +80,7 @@ void shouldCloseConnections() { when(clientMock.connectAsync(eq(StringCodec.UTF8), any())) .thenReturn(ConnectionFuture.completed(null, nodeConnectionMock)); - StatefulRedisConnection connection = sut.getConnection(UpstreamReplicaConnectionProvider.Intent.READ); + StatefulRedisConnection connection = sut.getConnection(MasterReplicaConnectionProvider.Intent.READ); assertThat(connection).isNotNull(); sut.close(); diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaSentinelSslIntegrationTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java similarity index 94% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaSentinelSslIntegrationTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java index b531cc35f1..7f4ba7ffe1 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaSentinelSslIntegrationTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java @@ -39,12 +39,12 @@ * @author Mark Paluch */ @ExtendWith(LettuceExtension.class) -class UpstreamReplicaSentinelSslIntegrationTests extends TestSupport { +class MasterReplicaSentinelSslIntegrationTests extends TestSupport { private final ClientResources clientResources; @Inject - UpstreamReplicaSentinelSslIntegrationTests(ClientResources clientResources) { + MasterReplicaSentinelSslIntegrationTests(ClientResources clientResources) { this.clientResources = clientResources.mutate() .socketAddressResolver(MappingSocketAddressResolver.create(DnsResolver.jvmDefault(), hostAndPort -> { diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTest.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTest.java similarity index 96% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTest.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaTest.java index 52f4ba0b55..a0d0fa42f9 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTest.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTest.java @@ -15,9 +15,8 @@ */ package io.lettuce.core.masterreplica; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; import java.util.Collections; import java.util.List; @@ -42,9 +41,11 @@ import io.lettuce.test.settings.TestSettings; /** + * Integration tests for master/replica via {@link MasterReplica}. + * * @author Mark Paluch */ -class UpstreamReplicaTest extends AbstractRedisClientTest { +class MasterReplicaTest extends AbstractRedisClientTest { private RedisURI masterURI = RedisURI.Builder.redis(host, TestSettings.port(3)).withPassword(passwd) .withClientName("my-client").withDatabase(5).build(); diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyProviderUnitTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyProviderUnitTests.java similarity index 96% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyProviderUnitTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyProviderUnitTests.java index fd1a795ad6..75290b4875 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyProviderUnitTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyProviderUnitTests.java @@ -15,9 +15,8 @@ */ package io.lettuce.core.masterreplica; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.List; @@ -30,9 +29,11 @@ import io.lettuce.test.settings.TestSettings; /** + * Unit tests for {@link ReplicaTopologyProvider}. + * * @author Mark Paluch */ -class UpstreamReplicaTopologyProviderUnitTests { +class MasterReplicaTopologyProviderUnitTests { private StatefulRedisConnection connectionMock = mock(StatefulRedisConnection.class); diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefreshUnitTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefreshUnitTests.java similarity index 88% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefreshUnitTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefreshUnitTests.java index 37a5f2c1be..f7b7c7effb 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaTopologyRefreshUnitTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaTopologyRefreshUnitTests.java @@ -47,12 +47,12 @@ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class UpstreamReplicaTopologyRefreshUnitTests { +class MasterReplicaTopologyRefreshUnitTests { - private static final RedisUpstreamReplicaNode UPSTREAM = new RedisUpstreamReplicaNode("localhost", 1, new RedisURI(), + private static final RedisMasterReplicaNode UPSTREAM = new RedisMasterReplicaNode("localhost", 1, new RedisURI(), RedisInstance.Role.UPSTREAM); - private static final RedisUpstreamReplicaNode REPLICA = new RedisUpstreamReplicaNode("localhost", 2, new RedisURI(), + private static final RedisMasterReplicaNode REPLICA = new RedisMasterReplicaNode("localhost", 2, new RedisURI(), RedisInstance.Role.REPLICA); @Mock @@ -93,7 +93,7 @@ void tearDown() { @Test void shouldRetrieveTopology() { - UpstreamReplicaTopologyRefresh refresh = new UpstreamReplicaTopologyRefresh(connectionFactory, executorService, + MasterReplicaTopologyRefresh refresh = new MasterReplicaTopologyRefresh(connectionFactory, executorService, provider); CompletableFuture> master = CompletableFuture.completedFuture(connection); @@ -112,7 +112,7 @@ void shouldRetrieveTopology() { @Test void shouldRetrieveTopologyWithFailedNode() { - UpstreamReplicaTopologyRefresh refresh = new UpstreamReplicaTopologyRefresh(connectionFactory, executorService, + MasterReplicaTopologyRefresh refresh = new MasterReplicaTopologyRefresh(connectionFactory, executorService, provider); CompletableFuture> connected = CompletableFuture.completedFuture(connection); diff --git a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaUtilsUnitTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaUtilsUnitTests.java similarity index 64% rename from src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaUtilsUnitTests.java rename to src/test/java/io/lettuce/core/masterreplica/MasterReplicaUtilsUnitTests.java index 7e0f7f63f6..9d200de1ce 100644 --- a/src/test/java/io/lettuce/core/masterreplica/UpstreamReplicaUtilsUnitTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaUtilsUnitTests.java @@ -15,7 +15,7 @@ */ package io.lettuce.core.masterreplica; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.assertj.core.api.AssertionsForInterfaceTypes.*; import java.util.Arrays; @@ -25,21 +25,23 @@ import io.lettuce.core.models.role.RedisInstance; /** + * Unit tests for {@link RedisMasterReplicaNode}. + * * @author Mark Paluch */ -class UpstreamReplicaUtilsUnitTests { +class MasterReplicaUtilsUnitTests { @Test void isChangedShouldReturnFalse() { - RedisUpstreamReplicaNode upstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode upstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode replica = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 234), + RedisMasterReplicaNode replica = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 234), RedisInstance.Role.REPLICA); - RedisUpstreamReplicaNode newupstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 555), + RedisMasterReplicaNode newupstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 555), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode newslave = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 666), + RedisMasterReplicaNode newslave = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 666), RedisInstance.Role.REPLICA); assertThat(ReplicaUtils.isChanged(Arrays.asList(upstream, replica), Arrays.asList(newupstream, newslave))).isFalse(); @@ -52,12 +54,12 @@ void isChangedShouldReturnFalse() { @Test void isChangedShouldReturnTrueBecauseSlaveIsGone() { - RedisUpstreamReplicaNode upstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode upstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode replica = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 234), + RedisMasterReplicaNode replica = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 234), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode newupstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode newupstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.UPSTREAM); assertThat(ReplicaUtils.isChanged(Arrays.asList(upstream, replica), Arrays.asList(newupstream))).isTrue(); @@ -66,14 +68,14 @@ void isChangedShouldReturnTrueBecauseSlaveIsGone() { @Test void isChangedShouldReturnTrueBecauseHostWasMigrated() { - RedisUpstreamReplicaNode upstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode upstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode replica = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 234), + RedisMasterReplicaNode replica = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 234), RedisInstance.Role.REPLICA); - RedisUpstreamReplicaNode newupstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 555), + RedisMasterReplicaNode newupstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 555), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode newslave = new RedisUpstreamReplicaNode("newhost", 234, RedisURI.create("newhost", 666), + RedisMasterReplicaNode newslave = new RedisMasterReplicaNode("newhost", 234, RedisURI.create("newhost", 666), RedisInstance.Role.REPLICA); assertThat(ReplicaUtils.isChanged(Arrays.asList(upstream, replica), Arrays.asList(newupstream, newslave))).isTrue(); @@ -85,14 +87,14 @@ void isChangedShouldReturnTrueBecauseHostWasMigrated() { @Test void isChangedShouldReturnTrueBecauseRolesSwitched() { - RedisUpstreamReplicaNode upstream = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode upstream = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode replica = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 234), + RedisMasterReplicaNode replica = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 234), RedisInstance.Role.UPSTREAM); - RedisUpstreamReplicaNode newslave = new RedisUpstreamReplicaNode("host", 1234, RedisURI.create("host", 111), + RedisMasterReplicaNode newslave = new RedisMasterReplicaNode("host", 1234, RedisURI.create("host", 111), RedisInstance.Role.REPLICA); - RedisUpstreamReplicaNode newupstream = new RedisUpstreamReplicaNode("host", 234, RedisURI.create("host", 234), + RedisMasterReplicaNode newupstream = new RedisMasterReplicaNode("host", 234, RedisURI.create("host", 234), RedisInstance.Role.UPSTREAM); assertThat(ReplicaUtils.isChanged(Arrays.asList(upstream, replica), Arrays.asList(newupstream, newslave))).isTrue(); diff --git a/src/test/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaTest.java b/src/test/java/io/lettuce/core/masterreplica/StaticMasterReplicaTest.java similarity index 91% rename from src/test/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaTest.java rename to src/test/java/io/lettuce/core/masterreplica/StaticMasterReplicaTest.java index 1d156e69bb..d42deaf1f5 100644 --- a/src/test/java/io/lettuce/core/masterreplica/StaticUpstreamReplicaTest.java +++ b/src/test/java/io/lettuce/core/masterreplica/StaticMasterReplicaTest.java @@ -15,9 +15,8 @@ */ package io.lettuce.core.masterreplica; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; import java.util.Arrays; import java.util.Collections; @@ -40,9 +39,11 @@ import io.lettuce.test.settings.TestSettings; /** + * Integration tests for static master/replica via {@link MasterReplica}. + * * @author Mark Paluch */ -class StaticUpstreamReplicaTest extends AbstractRedisClientTest { +class StaticMasterReplicaTest extends AbstractRedisClientTest { private StatefulRedisMasterReplicaConnection connection; @@ -196,7 +197,7 @@ static String replicaCall(StatefulRedisMasterReplicaConnection c @Test void testConnectionCount() { - UpstreamReplicaConnectionProvider connectionProvider = getConnectionProvider(); + MasterReplicaConnectionProvider connectionProvider = getConnectionProvider(); assertThat(connectionProvider.getConnectionCount()).isEqualTo(0); replicaCall(connection); @@ -209,7 +210,7 @@ void testConnectionCount() { @Test void testReconfigureTopology() { - UpstreamReplicaConnectionProvider connectionProvider = getConnectionProvider(); + MasterReplicaConnectionProvider connectionProvider = getConnectionProvider(); replicaCall(connection); @@ -218,8 +219,8 @@ void testReconfigureTopology() { assertThat(connectionProvider.getConnectionCount()).isEqualTo(0); } - UpstreamReplicaConnectionProvider getConnectionProvider() { - UpstreamReplicaChannelWriter writer = ((StatefulRedisUpstreamReplicaConnectionImpl) connection).getChannelWriter(); + MasterReplicaConnectionProvider getConnectionProvider() { + MasterReplicaChannelWriter writer = ((StatefulRedisMasterReplicaConnectionImpl) connection).getChannelWriter(); return writer.getUpstreamReplicaConnectionProvider(); } } diff --git a/src/test/java/io/lettuce/core/support/BoundedAsyncPoolUnitTests.java b/src/test/java/io/lettuce/core/support/BoundedAsyncPoolUnitTests.java index f1b7b237e6..b366fbf4d9 100644 --- a/src/test/java/io/lettuce/core/support/BoundedAsyncPoolUnitTests.java +++ b/src/test/java/io/lettuce/core/support/BoundedAsyncPoolUnitTests.java @@ -15,8 +15,7 @@ */ package io.lettuce.core.support; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.List; @@ -38,9 +37,11 @@ class BoundedAsyncPoolUnitTests { private AtomicInteger counter = new AtomicInteger(); + private List destroyed = new ArrayList<>(); private AsyncObjectFactory STRING_OBJECT_FACTORY = new AsyncObjectFactory() { + @Override public CompletableFuture create() { return CompletableFuture.completedFuture(counter.incrementAndGet() + ""); @@ -56,6 +57,7 @@ public CompletableFuture destroy(String object) { public CompletableFuture validate(String object) { return CompletableFuture.completedFuture(true); } + }; @Test @@ -119,8 +121,8 @@ public CompletableFuture validate(String object) { @Test void shouldCreateMinIdleObject() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().minIdle(2) - .build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().minIdle(2).build()); assertThat(pool.getIdle()).isEqualTo(2); assertThat(pool.getObjectCount()).isEqualTo(2); @@ -129,8 +131,8 @@ void shouldCreateMinIdleObject() { @Test void shouldCreateMaintainMinIdleObject() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().minIdle(2) - .build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().minIdle(2).build()); TestFutures.awaitOrTimeout(pool.acquire()); @@ -141,8 +143,8 @@ void shouldCreateMaintainMinIdleObject() { @Test void shouldCreateMaintainMinMaxIdleObject() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().minIdle(2) - .maxTotal(2).build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().minIdle(2).maxTotal(2).build()); TestFutures.awaitOrTimeout(pool.acquire()); @@ -176,8 +178,8 @@ void shouldReuseObjects() { @Test void shouldDestroyIdle() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().maxIdle(2) - .maxTotal(5).build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().maxIdle(2).maxTotal(5).build()); List objects = new ArrayList<>(); for (int i = 0; i < 3; i++) { @@ -200,8 +202,8 @@ void shouldDestroyIdle() { @Test void shouldExhaustPool() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().maxTotal(4) - .build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().maxTotal(4).build()); String object1 = TestFutures.getOrTimeout(pool.acquire()); String object2 = TestFutures.getOrTimeout(pool.acquire()); @@ -228,8 +230,8 @@ void shouldExhaustPool() { @Test void shouldClearPool() { - BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, BoundedPoolConfig.builder().maxTotal(4) - .build()); + BoundedAsyncPool pool = new BoundedAsyncPool<>(STRING_OBJECT_FACTORY, + BoundedPoolConfig.builder().maxTotal(4).build()); for (int i = 0; i < 20; i++) { @@ -257,6 +259,7 @@ void shouldExhaustPoolConcurrent() { List> progress = new ArrayList<>(); AsyncObjectFactory IN_PROGRESS = new AsyncObjectFactory() { + @Override public CompletableFuture create() { @@ -276,6 +279,7 @@ public CompletableFuture destroy(String object) { public CompletableFuture validate(String object) { return CompletableFuture.completedFuture(true); } + }; BoundedAsyncPool pool = new BoundedAsyncPool<>(IN_PROGRESS, BoundedPoolConfig.builder().maxTotal(4).build()); @@ -304,6 +308,7 @@ void shouldConcurrentlyFail() { List> progress = new ArrayList<>(); AsyncObjectFactory IN_PROGRESS = new AsyncObjectFactory() { + @Override public CompletableFuture create() { @@ -323,6 +328,7 @@ public CompletableFuture destroy(String object) { public CompletableFuture validate(String object) { return CompletableFuture.completedFuture(true); } + }; BoundedAsyncPool pool = new BoundedAsyncPool<>(IN_PROGRESS, BoundedPoolConfig.builder().maxTotal(4).build()); @@ -343,4 +349,46 @@ public CompletableFuture validate(String object) { assertThat(pool.getObjectCount()).isZero(); assertThat(pool.getCreationInProgress()).isZero(); } + + @Test + void cancelShouldReturnObjectToPool() { + + List> progress = new ArrayList<>(); + AsyncObjectFactory IN_PROGRESS = new AsyncObjectFactory() { + + @Override + public CompletableFuture create() { + + CompletableFuture future = new CompletableFuture<>(); + progress.add(future); + + return future; + } + + @Override + public CompletableFuture destroy(String object) { + destroyed.add(object); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture validate(String object) { + return CompletableFuture.completedFuture(true); + } + + }; + + BoundedAsyncPool pool = new BoundedAsyncPool<>(IN_PROGRESS, + BoundedPoolConfig.builder().maxTotal(1).maxIdle(0).build()); + + CompletableFuture acquire = pool.acquire(); + + assertThat(acquire).isNotCompleted(); + acquire.cancel(false); + assertThat(acquire).isCancelled(); + + progress.get(0).complete("after-cancel"); + assertThat(destroyed).contains("after-cancel"); + } + }