Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.RedissonTimeSeries Maven / Gradle / Ivy
Go to download
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* 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
*
* http://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 org.redisson;
import org.redisson.api.RFuture;
import org.redisson.api.RTimeSeries;
import org.redisson.api.TimeSeriesEntry;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.TimeSeriesEntryReplayDecoder;
import org.redisson.client.protocol.decoder.TimeSeriesFirstEntryReplayDecoder;
import org.redisson.client.protocol.decoder.TimeSeriesSingleEntryReplayDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.eviction.EvictionScheduler;
import org.redisson.iterator.RedissonBaseIterator;
import org.redisson.misc.CompletableFutureWrapper;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonTimeSeries extends RedissonExpirable implements RTimeSeries {
private final EvictionScheduler evictionScheduler;
private String timeoutSetName;
public RedissonTimeSeries(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
this.evictionScheduler = evictionScheduler;
this.timeoutSetName = getTimeoutSetName(getRawName());
if (evictionScheduler != null) {
evictionScheduler.scheduleTimeSeries(getRawName(), timeoutSetName);
}
}
public RedissonTimeSeries(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
this.evictionScheduler = evictionScheduler;
this.timeoutSetName = getTimeoutSetName(getRawName());
if (evictionScheduler != null) {
evictionScheduler.scheduleTimeSeries(getRawName(), timeoutSetName);
}
}
String getTimeoutSetName(String name) {
return prefixName("redisson__ts_ttl", name);
}
@Override
public void add(long timestamp, V value) {
addAll(Collections.singletonMap(timestamp, value));
}
@Override
public RFuture addAsync(long timestamp, V object) {
return addAllAsync(Collections.singletonMap(timestamp, object));
}
@Override
public void add(long timestamp, V object, L label) {
addAll(Collections.singletonList(new TimeSeriesEntry<>(timestamp, object, label)));
}
@Override
public RFuture addAsync(long timestamp, V object, L label) {
return addAllAsync(Collections.singletonList(new TimeSeriesEntry<>(timestamp, object, label)));
}
@Override
public void addAll(Map objects) {
addAll(objects, 0, TimeUnit.MILLISECONDS);
}
@Override
public void add(long timestamp, V value, long timeToLive, TimeUnit timeUnit) {
addAll(Collections.singletonMap(timestamp, value), timeToLive, timeUnit);
}
@Override
public RFuture addAsync(long timestamp, V object, long timeToLive, TimeUnit timeUnit) {
return addAllAsync(Collections.singletonMap(timestamp, object), timeToLive, timeUnit);
}
@Override
public void add(long timestamp, V object, Duration timeToLive) {
get(addAsync(timestamp, object, timeToLive));
}
@Override
public RFuture addAsync(long timestamp, V object, Duration timeToLive) {
return addAllAsync(Collections.singletonMap(timestamp, object), timeToLive);
}
@Override
public void add(long timestamp, V object, L label, Duration timeToLive) {
addAll(Collections.singletonList(new TimeSeriesEntry<>(timestamp, object, label)), timeToLive);
}
@Override
public RFuture addAsync(long timestamp, V object, L label, Duration timeToLive) {
return addAllAsync(Collections.singletonList(new TimeSeriesEntry<>(timestamp, object, label)), timeToLive);
}
@Override
public void addAll(Map objects, long timeToLive, TimeUnit timeUnit) {
get(addAllAsync(objects, timeToLive, timeUnit));
}
@Override
public RFuture addAllAsync(Map objects) {
return addAllAsync(objects, 0, TimeUnit.MILLISECONDS);
}
@Override
public RFuture addAllAsync(Map objects, long timeToLive, TimeUnit timeUnit) {
return addAllAsync(objects, Duration.ofMillis(timeUnit.toMillis(timeToLive)));
}
@Override
public void addAll(Map objects, Duration timeToLive) {
get(addAllAsync(objects, timeToLive));
}
@Override
public RFuture addAllAsync(Map objects, Duration timeToLive) {
long expirationTime = System.currentTimeMillis();
if (timeToLive != null && !timeToLive.isZero()) {
expirationTime += timeToLive.toMillis();
} else {
expirationTime += TimeUnit.DAYS.toMillis(365 * 100);
}
List params = new ArrayList<>();
params.add(expirationTime);
for (Map.Entry entry : objects.entrySet()) {
params.add(entry.getKey());
byte[] random = getServiceManager().generateIdArray();
params.add(random);
encode(params, entry.getValue());
}
if (timeToLive != null && !timeToLive.isZero()) {
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
"for i = 2, #ARGV, 3 do " +
"local val = struct.pack('BBc0Lc0Lc0', 2, string.len(ARGV[i+1]), ARGV[i+1], string.len(ARGV[i+2]), ARGV[i+2], 0, ''); " +
"redis.call('zadd', KEYS[1], ARGV[i], val); " +
"redis.call('zadd', KEYS[2], ARGV[1], val); " +
"end; ",
Arrays.asList(getRawName(), timeoutSetName),
params.toArray());
}
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
"local expirationTime = ARGV[1]; " +
"local lastValues = redis.call('zrange', KEYS[2], -1, -1, 'withscores'); " +
"if (#lastValues > 0 and tonumber(lastValues[2]) > tonumber(ARGV[1])) then " +
"expirationTime = tonumber(lastValues[2]); " +
"end; " +
"for i = 2, #ARGV, 3 do " +
"local val = struct.pack('BBc0Lc0Lc0', 2, string.len(ARGV[i+1]), ARGV[i+1], string.len(ARGV[i+2]), ARGV[i+2], 0, ''); " +
"redis.call('zadd', KEYS[1], ARGV[i], val); " +
"redis.call('zadd', KEYS[2], expirationTime + 1, val); " +
"end; ",
Arrays.asList(getRawName(), timeoutSetName),
params.toArray());
}
@Override
public void addAll(Collection> entries) {
addAll(entries, null);
}
@Override
public RFuture addAllAsync(Collection> entries) {
return addAllAsync(entries, null);
}
@Override
public void addAll(Collection> entries, Duration timeToLive) {
get(addAllAsync(entries, timeToLive));
}
@Override
public RFuture addAllAsync(Collection> entries, Duration timeToLive) {
long expirationTime = System.currentTimeMillis();
if (timeToLive != null) {
expirationTime += timeToLive.toMillis();
} else {
expirationTime += TimeUnit.DAYS.toMillis(365 * 100);
}
List params = new ArrayList<>();
params.add(expirationTime);
for (TimeSeriesEntry entry : entries) {
params.add(entry.getTimestamp());
byte[] random = getServiceManager().generateIdArray();
if (entry.getLabel() == null) {
params.add(2);
} else {
params.add(3);
}
params.add(random);
encode(params, entry.getValue());
if (entry.getLabel() == null) {
params.add("");
} else {
encode(params, entry.getLabel());
}
}
if (timeToLive != null) {
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
"for i = 2, #ARGV, 5 do " +
"local val = struct.pack('BBc0Lc0Lc0', ARGV[i+1], " +
"string.len(ARGV[i+2]), ARGV[i+2], " +
"string.len(ARGV[i+3]), ARGV[i+3], " +
"string.len(ARGV[i+4]), ARGV[i+4]); " +
"redis.call('zadd', KEYS[1], ARGV[i], val); " +
"redis.call('zadd', KEYS[2], ARGV[1], val); " +
"end; ",
Arrays.asList(getRawName(), timeoutSetName),
params.toArray());
}
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
"local expirationTime = ARGV[1]; " +
"local lastValues = redis.call('zrange', KEYS[2], -1, -1, 'withscores'); " +
"if (#lastValues > 0 and tonumber(lastValues[2]) > tonumber(ARGV[1])) then " +
"expirationTime = tonumber(lastValues[2]); " +
"end; " +
"for i = 2, #ARGV, 5 do " +
"local val = struct.pack('BBc0Lc0Lc0', ARGV[i+1]," +
"string.len(ARGV[i+2]), ARGV[i+2], " +
"string.len(ARGV[i+3]), ARGV[i+3], " +
"string.len(ARGV[i+4]), ARGV[i+4]); " +
"redis.call('zadd', KEYS[1], ARGV[i], val); " +
"redis.call('zadd', KEYS[2], expirationTime + 1, val); " +
"end; ",
Arrays.asList(getRawName(), timeoutSetName),
params.toArray());
}
@Override
public int size() {
return get(sizeAsync());
}
@Override
public RFuture sizeAsync() {
return commandExecutor.evalReadAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local values = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1]);" +
"return redis.call('zcard', KEYS[1]) - #values;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis());
}
@Override
public V get(long timestamp) {
return get(getAsync(timestamp));
}
@Override
public RFuture getAsync(long timestamp) {
return commandExecutor.evalReadAsync(getRawName(), codec, RedisCommands.EVAL_OBJECT,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[2]);" +
"if #values == 0 then " +
"return nil;" +
"end;" +
"local expirationDate = redis.call('zscore', KEYS[2], values[1]); " +
"if expirationDate ~= false and tonumber(expirationDate) <= tonumber(ARGV[1]) then " +
"return nil;" +
"end;" +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[1]); " +
"return val;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), timestamp);
}
@Override
public TimeSeriesEntry getEntry(long timestamp) {
return get(getEntryAsync(timestamp));
}
@Override
public RFuture> getEntryAsync(long timestamp) {
return commandExecutor.evalReadAsync(getRawName(), codec, EVAL_ENTRY,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[2]);" +
"if #values == 0 then " +
"return nil;" +
"end;" +
"local expirationDate = redis.call('zscore', KEYS[2], values[1]); " +
"if expirationDate ~= false and tonumber(expirationDate) <= tonumber(ARGV[1]) then " +
"return nil;" +
"end;" +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[1]); " +
"if n == 2 then " +
"return {n, ARGV[2], val};" +
"end;" +
"return {n, ARGV[2], val, label};",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), timestamp);
}
@Override
public boolean remove(long timestamp) {
return get(removeAsync(timestamp));
}
@Override
public RFuture removeAsync(long timestamp) {
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_BOOLEAN,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[2]);" +
"if #values == 0 then " +
"return 0;" +
"end;" +
"local expirationDate = redis.call('zscore', KEYS[2], values[1]); " +
"if expirationDate ~= false and tonumber(expirationDate) <= tonumber(ARGV[1]) then " +
"return 0;" +
"end;" +
"redis.call('zrem', KEYS[2], values[1]); " +
"redis.call('zrem', KEYS[1], values[1]); " +
"return 1;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), timestamp);
}
@Override
public V getAndRemove(long timestamp) {
return get(getAndRemoveAsync(timestamp));
}
@Override
public RFuture getAndRemoveAsync(long timestamp) {
return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_OBJECT,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[2]);" +
"if #values == 0 then " +
"return nil;" +
"end;" +
"local expirationDate = redis.call('zscore', KEYS[2], values[1]); " +
"if expirationDate ~= false and tonumber(expirationDate) <= tonumber(ARGV[1]) then " +
"return nil;" +
"end;" +
"redis.call('zrem', KEYS[2], values[1]); " +
"redis.call('zrem', KEYS[1], values[1]); " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[1]); " +
"return val;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), timestamp);
}
@Override
public TimeSeriesEntry getAndRemoveEntry(long timestamp) {
return get(getAndRemoveEntryAsync(timestamp));
}
@Override
public RFuture> getAndRemoveEntryAsync(long timestamp) {
return commandExecutor.evalWriteAsync(getRawName(), codec, EVAL_ENTRY,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[2]);" +
"if #values == 0 then " +
"return nil;" +
"end;" +
"local expirationDate = redis.call('zscore', KEYS[2], values[1]); " +
"if expirationDate ~= false and tonumber(expirationDate) <= tonumber(ARGV[1]) then " +
"return nil;" +
"end;" +
"redis.call('zrem', KEYS[2], values[1]); " +
"redis.call('zrem', KEYS[1], values[1]); " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[1]); " +
"if n == 2 then " +
"return {n, ARGV[2], val};" +
"end;" +
"return {n, ARGV[2], val, label};",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), timestamp);
}
@Override
public V last() {
return get(lastAsync());
}
@Override
public RFuture lastAsync() {
return listAsync(-1, 1, RedisCommands.EVAL_FIRST_LIST);
}
@Override
public TimeSeriesEntry lastEntry() {
return get(lastEntryAsync());
}
@Override
public RFuture> lastEntryAsync() {
return listEntriesAsync(-1, 1, EVAL_FIRST_ENTRY);
}
@Override
public RFuture> lastAsync(int count) {
return listAsync(-1, count, RedisCommands.EVAL_LIST_REVERSE);
}
@Override
public V first() {
return get(firstAsync());
}
@Override
public RFuture firstAsync() {
return listAsync(0, 1, RedisCommands.EVAL_FIRST_LIST);
}
@Override
public TimeSeriesEntry firstEntry() {
return get(firstEntryAsync());
}
@Override
public RFuture> firstEntryAsync() {
return listEntriesAsync(0, 1, EVAL_FIRST_ENTRY);
}
@Override
public RFuture> firstAsync(int count) {
return listAsync(0, count, RedisCommands.EVAL_LIST);
}
@Override
public Collection first(int count) {
return get(listAsync(0, count, RedisCommands.EVAL_LIST));
}
@Override
public Collection> firstEntries(int count) {
return get(firstEntriesAsync(count));
}
@Override
public RFuture>> firstEntriesAsync(int count) {
return listEntriesAsync(0, count, EVAL_ENTRIES);
}
@Override
public Collection last(int count) {
return get(lastAsync(count));
}
@Override
public Collection> lastEntries(int count) {
return get(lastEntriesAsync(count));
}
@Override
public RFuture>> lastEntriesAsync(int count) {
return listEntriesAsync(-2, count, EVAL_ENTRIES_REVERSE);
}
@Override
public Long firstTimestamp() {
return get(firstTimestampAsync());
}
@Override
public RFuture firstTimestampAsync() {
return listTimestampAsync(0, 1, RedisCommands.EVAL_FIRST_LIST);
}
@Override
public Long lastTimestamp() {
return get(lastTimestampAsync());
}
@Override
public RFuture lastTimestampAsync() {
return listTimestampAsync(-1, 1, RedisCommands.EVAL_FIRST_LIST);
}
private RFuture listTimestampAsync(int startScore, int limit, RedisCommand> evalCommandType) {
return commandExecutor.evalReadAsync(getRawName(), LongCodec.INSTANCE, evalCommandType,
"local values;" +
"if ARGV[2] == '0' then " +
"values = redis.call('zrangebyscore', KEYS[2], ARGV[1], '+inf', 'limit', 0, ARGV[3]);" +
"else " +
"values = redis.call('zrevrangebyscore', KEYS[2], '+inf', ARGV[1], 'limit', 0, ARGV[3]);" +
"end; " +
"local result = {}; " +
"for i, v in ipairs(values) do " +
"local t = redis.call('zscore', KEYS[1], v); " +
"table.insert(result, t);" +
"end;" +
"return result;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startScore, limit);
}
private RFuture listAsync(int startScore, int limit, RedisCommand> evalCommandType) {
return commandExecutor.evalReadAsync(getRawName(), codec, evalCommandType,
"local values;" +
"if ARGV[2] == '0' then " +
"values = redis.call('zrangebyscore', KEYS[2], ARGV[1], '+inf', 'limit', 0, ARGV[3]);" +
"else " +
"values = redis.call('zrevrangebyscore', KEYS[2], '+inf', ARGV[1], 'limit', 0, ARGV[3]);" +
"end; " +
"local result = {}; " +
"for i, v in ipairs(values) do " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', v); " +
"table.insert(result, val);" +
"end;" +
"return result;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startScore, limit);
}
private RFuture listEntriesAsync(int startScore, int limit, RedisCommand> evalCommandType) {
return commandExecutor.evalReadAsync(getRawName(), codec, evalCommandType,
"local values;" +
"if ARGV[2] == '0' then " +
"values = redis.call('zrangebyscore', KEYS[2], ARGV[1], '+inf', 'withscores', 'limit', 0, ARGV[3]);" +
"else " +
"values = redis.call('zrevrangebyscore', KEYS[2], '+inf', ARGV[1], 'withscores', 'limit', 0, ARGV[3]);" +
"end; " +
"local result = {}; " +
"for i=1, #values, 2 do " +
"local score = redis.call('zscore', KEYS[1], values[i]); " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[i]); " +
"table.insert(result, val);" +
"if n == 2 then " +
"label = 0; " +
"end; " +
"table.insert(result, label);" +
"table.insert(result, n);" +
"table.insert(result, score);" +
"end;" +
"return result;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startScore, limit);
}
@Override
public int removeRange(long startTimestamp, long endTimestamp) {
return get(removeRangeAsync(startTimestamp, endTimestamp));
}
@Override
public RFuture removeRangeAsync(long startTimestamp, long endTimestamp) {
return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local values = redis.call('zrangebyscore', KEYS[1], ARGV[2], ARGV[3]);" +
"local counter = 0; " +
"for i, v in ipairs(values) do " +
"local expirationDate = redis.call('zscore', KEYS[2], v); " +
"if tonumber(expirationDate) > tonumber(ARGV[1]) then " +
"counter = counter + 1; " +
"redis.call('zrem', KEYS[2], v); " +
"redis.call('zrem', KEYS[1], v); " +
"end;" +
"end;" +
"return counter;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startTimestamp, endTimestamp);
}
@Override
public Collection range(long startTimestamp, long endTimestamp, int limit) {
return get(rangeAsync(startTimestamp, endTimestamp, limit));
}
@Override
public Collection range(long startTimestamp, long endTimestamp) {
return get(rangeAsync(startTimestamp, endTimestamp));
}
@Override
public Collection> entryRange(long startTimestamp, long endTimestamp) {
return get(entryRangeAsync(false, startTimestamp, endTimestamp, 0));
}
@Override
public Collection> entryRangeReversed(long startTimestamp, long endTimestamp) {
return get(entryRangeAsync(true, startTimestamp, endTimestamp, 0));
}
@Override
public RFuture>> entryRangeReversedAsync(long startTimestamp, long endTimestamp) {
return entryRangeAsync(true, startTimestamp, endTimestamp, 0);
}
private static final RedisCommand EVAL_FIRST_ENTRY = new RedisCommand<>("EVAL", new TimeSeriesFirstEntryReplayDecoder() {});
private static final RedisCommand>> EVAL_ENTRIES =
new RedisCommand<>("EVAL", new TimeSeriesEntryReplayDecoder());
private static final RedisCommand>> EVAL_ENTRIES_REVERSE =
new RedisCommand<>("EVAL", new TimeSeriesEntryReplayDecoder(true));
private static final RedisCommand> EVAL_ENTRY =
new RedisCommand<>("EVAL", new TimeSeriesSingleEntryReplayDecoder());
@Override
public RFuture>> entryRangeAsync(long startTimestamp, long endTimestamp) {
return entryRangeAsync(false, startTimestamp, endTimestamp, 0);
}
private RFuture>> entryRangeAsync(boolean reverse, long startTimestamp, long endTimestamp, int limit) {
return commandExecutor.evalReadAsync(getRawName(), codec, EVAL_ENTRIES,
"local result = {}; " +
"local from = ARGV[2]; " +
"local to = ARGV[3]; " +
"local limit = tonumber(ARGV[4]); " +
"local cmd = 'zrangebyscore'; " +
"if ARGV[5] ~= '0' then " +
"from = ARGV[3]; " +
"to = ARGV[2]; " +
"cmd = 'zrevrangebyscore';" +
"end; " +
"while true do " +
"local values;" +
"if ARGV[4] ~= '0' then " +
"values = redis.call(cmd, KEYS[1], from, to, 'withscores', 'limit', 0, limit);" +
"else " +
"values = redis.call(cmd, KEYS[1], from, to, 'withscores');" +
"end; " +
"for i=1, #values, 2 do " +
"local expirationDate = redis.call('zscore', KEYS[2], values[i]);" +
"if tonumber(expirationDate) > tonumber(ARGV[1]) then " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[i]); " +
"table.insert(result, val);" +
"if n == 2 then " +
"label = 0; " +
"end; " +
"table.insert(result, label);" +
"table.insert(result, n);" +
"table.insert(result, values[i+1]);" +
"end;" +
"end;" +
"if limit == 0 or #result/4 == tonumber(ARGV[4]) or #values/2 < limit then " +
"return result;" +
"end;" +
"from = '(' .. values[#values];" +
"limit = tonumber(ARGV[4]) - #result/4;" +
"end;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startTimestamp, endTimestamp, limit, Boolean.compare(reverse, false), encode((Object) null));
}
@Override
public Collection rangeReversed(long startTimestamp, long endTimestamp, int limit) {
return get(rangeReversedAsync(startTimestamp, endTimestamp, limit));
}
@Override
public RFuture> rangeAsync(long startTimestamp, long endTimestamp) {
return rangeAsync(startTimestamp, endTimestamp, 0);
}
@Override
public RFuture> rangeAsync(long startTimestamp, long endTimestamp, int limit) {
return rangeAsync(false, startTimestamp, endTimestamp, limit);
}
@Override
public Collection rangeReversed(long startTimestamp, long endTimestamp) {
return get(rangeReversedAsync(startTimestamp, endTimestamp));
}
@Override
public RFuture> rangeReversedAsync(long startTimestamp, long endTimestamp) {
return rangeReversedAsync(startTimestamp, endTimestamp, 0);
}
@Override
public RFuture> rangeReversedAsync(long startTimestamp, long endTimestamp, int limit) {
return rangeAsync(true, startTimestamp, endTimestamp, limit);
}
private RFuture> rangeAsync(boolean reverse, long startTimestamp, long endTimestamp, int limit) {
return commandExecutor.evalReadAsync(getRawName(), codec, RedisCommands.EVAL_LIST,
"local result = {}; " +
"local from = ARGV[2]; " +
"local to = ARGV[3]; " +
"local limit = tonumber(ARGV[4]); " +
"local cmd = 'zrangebyscore'; " +
"if ARGV[5] ~= '0' then " +
"from = ARGV[3]; " +
"to = ARGV[2]; " +
"cmd = 'zrevrangebyscore';" +
"end; " +
"while true do " +
"local values;" +
"if ARGV[4] ~= '0' then " +
"values = redis.call(cmd, KEYS[1], from, to, 'withscores', 'limit', 0, limit);" +
"else " +
"values = redis.call(cmd, KEYS[1], from, to, 'withscores');" +
"end; " +
"for i=1, #values, 2 do " +
"local expirationDate = redis.call('zscore', KEYS[2], values[i]);" +
"if tonumber(expirationDate) > tonumber(ARGV[1]) then " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[i]); " +
"table.insert(result, val);" +
"end;" +
"end;" +
"if limit == 0 or #result == tonumber(ARGV[4]) or #values/2 < tonumber(limit) then " +
"return result;" +
"end;" +
"from = '(' .. values[#values];" +
"limit = tonumber(ARGV[4]) - #result;" +
"end;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startTimestamp, endTimestamp, limit, Boolean.compare(reverse, false));
}
@Override
public Collection> entryRange(long startTimestamp, long endTimestamp, int limit) {
return get(entryRangeAsync(startTimestamp, endTimestamp, limit));
}
@Override
public RFuture>> entryRangeAsync(long startTimestamp, long endTimestamp, int limit) {
return entryRangeAsync(false, startTimestamp, endTimestamp, limit);
}
@Override
public Collection> entryRangeReversed(long startTimestamp, long endTimestamp, int limit) {
return get(entryRangeReversedAsync(startTimestamp, endTimestamp, limit));
}
@Override
public RFuture>> entryRangeReversedAsync(long startTimestamp, long endTimestamp, int limit) {
return entryRangeAsync(true, startTimestamp, endTimestamp, limit);
}
@Override
public Collection pollFirst(int count) {
return get(pollFirstAsync(count));
}
@Override
public Collection pollLast(int count) {
return get(pollLastAsync(count));
}
@Override
public RFuture> pollFirstAsync(int count) {
if (count <= 0) {
return new CompletableFutureWrapper<>(Collections.emptyList());
}
return pollAsync(0, count, RedisCommands.EVAL_LIST);
}
@Override
public RFuture> pollLastAsync(int count) {
if (count <= 0) {
return new CompletableFutureWrapper<>(Collections.emptyList());
}
return pollAsync(-1, count, RedisCommands.EVAL_LIST_REVERSE);
}
@Override
public Collection> pollFirstEntries(int count) {
return get(pollFirstEntriesAsync(count));
}
@Override
public RFuture>> pollFirstEntriesAsync(int count) {
if (count <= 0) {
return new CompletableFutureWrapper<>(Collections.emptyList());
}
return pollEntriesAsync(0, count, EVAL_ENTRIES);
}
@Override
public Collection> pollLastEntries(int count) {
return get(pollLastEntriesAsync(count));
}
@Override
public RFuture>> pollLastEntriesAsync(int count) {
if (count <= 0) {
return new CompletableFutureWrapper<>(Collections.emptyList());
}
return pollEntriesAsync(-1, count, EVAL_ENTRIES_REVERSE);
}
@Override
public V pollFirst() {
return get(pollFirstAsync());
}
@Override
public V pollLast() {
return get(pollLastAsync());
}
@Override
public RFuture pollFirstAsync() {
return pollAsync(0, 1, RedisCommands.EVAL_FIRST_LIST);
}
@Override
public RFuture pollLastAsync() {
return pollAsync(-1, 1, RedisCommands.EVAL_FIRST_LIST);
}
@Override
public TimeSeriesEntry pollFirstEntry() {
return get(pollFirstEntryAsync());
}
@Override
public RFuture> pollFirstEntryAsync() {
return pollEntriesAsync(0, 1, EVAL_FIRST_ENTRY);
}
@Override
public TimeSeriesEntry pollLastEntry() {
return get(pollLastEntryAsync());
}
@Override
public RFuture> pollLastEntryAsync() {
return pollEntriesAsync(-1, 1, EVAL_FIRST_ENTRY);
}
private RFuture pollAsync(int startScore, int limit, RedisCommand> command) {
return commandExecutor.evalWriteAsync(getRawName(), codec, command,
"local values;" +
"if ARGV[2] == '0' then " +
"values = redis.call('zrangebyscore', KEYS[2], ARGV[1], '+inf', 'limit', 0, ARGV[3]);" +
"else " +
"values = redis.call('zrevrangebyscore', KEYS[2], '+inf', ARGV[1], 'limit', 0, ARGV[3]);" +
"end; " +
"local result = {}; " +
"for i, v in ipairs(values) do " +
"redis.call('zrem', KEYS[2], v); " +
"redis.call('zrem', KEYS[1], v); " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', v); " +
"table.insert(result, val);" +
"end;" +
"return result;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startScore, limit);
}
private RFuture pollEntriesAsync(int startScore, int limit, RedisCommand> command) {
return commandExecutor.evalWriteAsync(getRawName(), codec, command,
"local values;" +
"if ARGV[2] == '0' then " +
"values = redis.call('zrangebyscore', KEYS[2], ARGV[1], '+inf', 'withscores', 'limit', 0, ARGV[3]);" +
"else " +
"values = redis.call('zrevrangebyscore', KEYS[2], '+inf', ARGV[1], 'withscores', 'limit', 0, ARGV[3]);" +
"end; " +
"local result = {}; " +
"for i=1, #values, 2 do " +
"local score = redis.call('zscore', KEYS[1], values[i]); " +
"redis.call('zrem', KEYS[2], values[i]); " +
"redis.call('zrem', KEYS[1], values[i]); " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', values[i]); " +
"table.insert(result, val);" +
"if n == 2 then " +
"label = 0; " +
"end; " +
"table.insert(result, label);" +
"table.insert(result, n);" +
"table.insert(result, score);" +
"end;" +
"return result;",
Arrays.asList(getRawName(), timeoutSetName),
System.currentTimeMillis(), startScore, limit);
}
public ListScanResult scanIterator(String name, RedisClient client, String startPos, int count) {
RFuture> f = scanIteratorAsync(name, client, startPos, count);
return get(f);
}
public RFuture> scanIteratorAsync(String name, RedisClient client, String startPos, int count) {
List params = new ArrayList<>();
params.add(startPos);
params.add(System.currentTimeMillis());
params.add(count);
return commandExecutor.evalReadAsync(client, name, codec, RedisCommands.EVAL_SCAN,
"local result = {}; "
+ "local res = redis.call('zrange', KEYS[1], ARGV[1], tonumber(ARGV[1]) + tonumber(ARGV[3]) - 1); "
+ "for i, value in ipairs(res) do "
+ "local expirationDate = redis.call('zscore', KEYS[2], value); " +
"if tonumber(expirationDate) > tonumber(ARGV[2]) then " +
"local n, t, val, label = struct.unpack('BBc0Lc0Lc0', value); " +
"table.insert(result, val);" +
"end;"
+ "end;" +
"local nextPos = tonumber(ARGV[1]) + tonumber(ARGV[3]); " +
"if #res < tonumber(ARGV[3]) then " +
"nextPos = 0;" +
"end;"
+ "return {tostring(nextPos), result};",
Arrays.asList(name, timeoutSetName),
params.toArray());
}
@Override
public Iterator iterator(int count) {
return new RedissonBaseIterator() {
@Override
protected ListScanResult iterator(RedisClient client, String nextIterPos) {
return scanIterator(getRawName(), client, nextIterPos, count);
}
@Override
protected void remove(Object value) {
throw new UnsupportedOperationException();
}
};
}
@Override
public Iterator iterator() {
return iterator(10);
}
@Override
public Stream stream() {
return toStream(iterator());
}
@Override
public Stream stream(int count) {
return toStream(iterator(count));
}
@Override
public void destroy() {
if (evictionScheduler != null) {
evictionScheduler.remove(getRawName());
}
removeListeners();
}
@Override
public RFuture deleteAsync() {
return deleteAsync(getRawName(), timeoutSetName);
}
@Override
public RFuture expireAsync(long timeToLive, TimeUnit timeUnit, String param, String... keys) {
return super.expireAsync(timeToLive, timeUnit, param, getRawName(), timeoutSetName);
}
@Override
protected RFuture expireAtAsync(long timestamp, String param, String... keys) {
return super.expireAtAsync(timestamp, getRawName(), timeoutSetName);
}
@Override
public RFuture clearExpireAsync() {
return clearExpireAsync(getRawName(), timeoutSetName);
}
@Override
public RFuture sizeInMemoryAsync() {
List keys = Arrays.asList(getRawName(), timeoutSetName);
return super.sizeInMemoryAsync(keys);
}
@Override
public RFuture copyAsync(List keys, int database, boolean replace) {
String newName = (String) keys.get(1);
List kks = Arrays.asList(getRawName(), timeoutSetName,
newName, getTimeoutSetName(newName));
return super.copyAsync(kks, database, replace);
}
@Override
public RFuture renameAsync(String nn) {
String newName = mapName(nn);
List kks = Arrays.asList(getRawName(), timeoutSetName,
newName, getTimeoutSetName(newName));
return renameAsync(commandExecutor, kks, () -> {
setName(nn);
this.timeoutSetName = getTimeoutSetName(newName);
});
}
@Override
public RFuture renamenxAsync(String nn) {
String newName = mapName(nn);
List kks = Arrays.asList(getRawName(), timeoutSetName,
newName, getTimeoutSetName(newName));
return renamenxAsync(commandExecutor, kks, value -> {
if (value) {
setName(nn);
this.timeoutSetName = getTimeoutSetName(newName);
}
});
}
}