http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SCardExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SCardExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SCardExecutor.java new file mode 100644 index 0000000..149a7eb --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SCardExecutor.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class SCardExecutor extends SetExecutor { + + private final int NOT_EXISTS = 0; + + @SuppressWarnings("unchecked") + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SCARD)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_SET, context); + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + + if (keyRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + return; + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), keyRegion.size())); + } + +}
http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffExecutor.java new file mode 100644 index 0000000..5712237 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffExecutor.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + +import java.util.List; +import java.util.Set; + +public class SDiffExecutor extends SetOpExecutor { + + @Override + protected boolean isStorage() { + return false; + } + + @Override + protected Set<ByteArrayWrapper> setOp(Set<ByteArrayWrapper> firstSet, + List<Set<ByteArrayWrapper>> setList) { + if (firstSet == null) + return null; + for (Set<ByteArrayWrapper> set : setList) + firstSet.removeAll(set); + return firstSet; + } + + @Override + public String getArgsError() { + return ArityDef.SDIFF; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffStoreExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffStoreExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffStoreExecutor.java new file mode 100644 index 0000000..63064da --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SDiffStoreExecutor.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + + +public class SDiffStoreExecutor extends SDiffExecutor { + + @Override + protected boolean isStorage() { + return true; + } + + @Override + public String getArgsError() { + return ArityDef.SDIFFSTORE; + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterExecutor.java new file mode 100644 index 0000000..cc4bbe3 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterExecutor.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + +import java.util.List; +import java.util.Set; + +public class SInterExecutor extends SetOpExecutor { + + @Override + protected boolean isStorage() { + return false; + } + + @Override + protected Set<ByteArrayWrapper> setOp(Set<ByteArrayWrapper> firstSet, + List<Set<ByteArrayWrapper>> setList) { + if (firstSet == null) + return null; + for (Set<ByteArrayWrapper> set : setList) { + if (set == null || set.isEmpty()) + return null; + firstSet.retainAll(set); + } + return firstSet; + } + + @Override + public String getArgsError() { + return ArityDef.SINTER; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterStoreExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterStoreExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterStoreExecutor.java new file mode 100644 index 0000000..bef36ff --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SInterStoreExecutor.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + + +public class SInterStoreExecutor extends SInterExecutor { + + @Override + protected boolean isStorage() { + return true; + } + + @Override + public String getArgsError() { + return ArityDef.SINTERSTORE; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SIsMemberExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SIsMemberExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SIsMemberExecutor.java new file mode 100644 index 0000000..38593c1 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SIsMemberExecutor.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class SIsMemberExecutor extends SetExecutor { + + private final int EXISTS = 1; + + private final int NOT_EXISTS = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 3) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SISMEMBER)); + return; + } + + ByteArrayWrapper key = command.getKey(); + ByteArrayWrapper member = new ByteArrayWrapper(commandElems.get(2)); + + checkDataType(key, RedisDataType.REDIS_SET, context); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + + if (keyRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + return; + } + + if (keyRegion.containsKey(member)) + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), EXISTS)); + else + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java new file mode 100644 index 0000000..73abb97 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SMembersExecutor extends SetExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SMEMBERS)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_SET, context); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + + if (keyRegion == null) { + command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator())); + return; + } + + Set<ByteArrayWrapper> members = new HashSet(keyRegion.keySet()); // Emulate copy on read + + command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), members)); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMoveExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMoveExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMoveExecutor.java new file mode 100644 index 0000000..4917b7b --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SMoveExecutor.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class SMoveExecutor extends SetExecutor { + + private final int MOVED = 1; + + private final int NOT_MOVED = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SMOVE)); + return; + } + + ByteArrayWrapper source = command.getKey(); + ByteArrayWrapper destination = new ByteArrayWrapper(commandElems.get(2)); + ByteArrayWrapper mem = new ByteArrayWrapper(commandElems.get(3)); + + checkDataType(source, RedisDataType.REDIS_SET, context); + checkDataType(destination, RedisDataType.REDIS_SET, context); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> sourceRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(source); + + if (sourceRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_MOVED)); + return; + } + + Object oldVal = sourceRegion.get(mem); + sourceRegion.remove(mem); + + if (oldVal == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_MOVED)); + return; + } + + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> destinationRegion = + (Region<ByteArrayWrapper, Boolean>) getOrCreateRegion(context, destination, + RedisDataType.REDIS_SET); + destinationRegion.put(mem, true); + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), MOVED)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java new file mode 100644 index 0000000..bf1ffd9 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + +import java.util.List; +import java.util.Random; + +public class SPopExecutor extends SetExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SPOP)); + return; + } + + ByteArrayWrapper key = command.getKey(); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + if (keyRegion == null || keyRegion.isEmpty()) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + + Random rand = new Random(); + + ByteArrayWrapper[] entries = keyRegion.keySet().toArray(new ByteArrayWrapper[keyRegion.size()]); + + ByteArrayWrapper pop = entries[rand.nextInt(entries.length)]; + + keyRegion.remove(pop); + if (keyRegion.isEmpty()) { + context.getRegionProvider().removeKey(key); + } + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), pop.toBytes())); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java new file mode 100644 index 0000000..61f6305 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +public class SRandMemberExecutor extends SetExecutor { + + private final static String ERROR_NOT_NUMERIC = "The count provided must be numeric"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 2) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SRANDMEMBER)); + return; + } + + ByteArrayWrapper key = command.getKey(); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + + int count = 1; + + if (commandElems.size() > 2) { + try { + count = Coder.bytesToInt(commandElems.get(2)); + } catch (NumberFormatException e) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERIC)); + return; + } + } + + if (keyRegion == null || count == 0) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + + int members = keyRegion.size(); + + if (members <= count && count != 1) { + command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), + new HashSet<ByteArrayWrapper>(keyRegion.keySet()))); + return; + } + + Random rand = new Random(); + + ByteArrayWrapper[] entries = keyRegion.keySet().toArray(new ByteArrayWrapper[members]); + + if (count == 1) { + ByteArrayWrapper randEntry = entries[rand.nextInt(entries.length)]; + command.setResponse( + Coder.getBulkStringResponse(context.getByteBufAllocator(), randEntry.toBytes())); + } else if (count > 0) { + Set<ByteArrayWrapper> randEntries = new HashSet<ByteArrayWrapper>(); + do { + ByteArrayWrapper s = entries[rand.nextInt(entries.length)]; + randEntries.add(s); + } while (randEntries.size() < count); + command.setResponse( + Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), randEntries)); + } else { + count = -count; + List<ByteArrayWrapper> randEntries = new ArrayList<ByteArrayWrapper>(); + for (int i = 0; i < count; i++) { + ByteArrayWrapper s = entries[rand.nextInt(entries.length)]; + randEntries.add(s); + } + command.setResponse( + Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), randEntries)); + } + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRemExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRemExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRemExecutor.java new file mode 100644 index 0000000..f9a97f0 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SRemExecutor.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class SRemExecutor extends SetExecutor { + + private final int NONE_REMOVED = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SREM)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_SET, context); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + + if (keyRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NONE_REMOVED)); + return; + } + + int numRemoved = 0; + + for (int i = 2; i < commandElems.size(); i++) { + Object oldVal; + oldVal = keyRegion.remove(new ByteArrayWrapper(commandElems.get(i))); + if (oldVal != null) + numRemoved++; + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), numRemoved)); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SScanExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SScanExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SScanExecutor.java new file mode 100644 index 0000000..585f75d --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SScanExecutor.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.executor.AbstractScanExecutor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class SScanExecutor extends AbstractScanExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SSCAN)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_SET, context); + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, Boolean> keyRegion = + (Region<ByteArrayWrapper, Boolean>) context.getRegionProvider().getRegion(key); + if (keyRegion == null) { + command.setResponse( + Coder.getScanResponse(context.getByteBufAllocator(), new ArrayList<String>())); + return; + } + byte[] cAr = commandElems.get(2); + String cursorString = Coder.bytesToString(cAr); + int cursor = 0; + Pattern matchPattern = null; + String globMatchPattern = null; + int count = DEFUALT_COUNT; + try { + cursor = Integer.parseInt(cursorString); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_CURSOR)); + return; + } + if (cursor < 0) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_CURSOR)); + return; + } + + if (commandElems.size() > 4) { + try { + byte[] bytes = commandElems.get(3); + String tmp = Coder.bytesToString(bytes); + if (tmp.equalsIgnoreCase("MATCH")) { + bytes = commandElems.get(4); + globMatchPattern = Coder.bytesToString(bytes); + } else if (tmp.equalsIgnoreCase("COUNT")) { + bytes = commandElems.get(4); + count = Coder.bytesToInt(bytes); + } + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_COUNT)); + return; + } + } + + if (commandElems.size() > 6) { + try { + byte[] bytes = commandElems.get(5); + String tmp = Coder.bytesToString(bytes); + if (tmp.equalsIgnoreCase("COUNT")) { + bytes = commandElems.get(6); + count = Coder.bytesToInt(bytes); + } + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_COUNT)); + return; + } + } + + if (count < 0) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_COUNT)); + return; + } + + try { + matchPattern = convertGlobToRegex(globMatchPattern); + } catch (PatternSyntaxException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ERROR_ILLEGAL_GLOB)); + return; + } + + @SuppressWarnings("unchecked") + List<ByteArrayWrapper> returnList = + (List<ByteArrayWrapper>) getIteration(new ArrayList(keyRegion.keySet()), matchPattern, + count, cursor); + + command.setResponse(Coder.getScanResponse(context.getByteBufAllocator(), returnList)); + } + + @SuppressWarnings("unchecked") + @Override + protected List<?> getIteration(Collection<?> list, Pattern matchPattern, int count, int cursor) { + List<Object> returnList = new ArrayList<Object>(); + int size = list.size(); + int beforeCursor = 0; + int numElements = 0; + int i = -1; + for (ByteArrayWrapper value : (Collection<ByteArrayWrapper>) list) { + String key = Coder.bytesToString(value.toBytes()); + i++; + if (beforeCursor < cursor) { + beforeCursor++; + continue; + } else if (numElements < count) { + if (matchPattern != null) { + if (matchPattern.matcher(key).matches()) { + returnList.add(value); + numElements++; + } + } else { + returnList.add(value); + numElements++; + } + } else + break; + } + + if (i == size - 1) + returnList.add(0, String.valueOf(0)); + else + returnList.add(0, String.valueOf(i)); + return returnList; + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionExecutor.java new file mode 100644 index 0000000..15705c1 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionExecutor.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SUnionExecutor extends SetOpExecutor { + + @Override + protected boolean isStorage() { + return false; + } + + @Override + protected Set<ByteArrayWrapper> setOp(Set<ByteArrayWrapper> firstSet, + List<Set<ByteArrayWrapper>> setList) { + Set<ByteArrayWrapper> addSet = firstSet; + for (Set<ByteArrayWrapper> set : setList) { + if (addSet == null) { + addSet = new HashSet<ByteArrayWrapper>(set); + continue; + } + addSet.addAll(set); + } + return addSet; + } + + @Override + public String getArgsError() { + return ArityDef.SUNION; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionStoreExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionStoreExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionStoreExecutor.java new file mode 100644 index 0000000..96b07e0 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SUnionStoreExecutor.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + + +public class SUnionStoreExecutor extends SUnionExecutor { + + @Override + protected boolean isStorage() { + return true; + } + + @Override + public String getArgsError() { + return ArityDef.SUNIONSTORE; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetExecutor.java new file mode 100644 index 0000000..0227e88 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetExecutor.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.redis.internal.executor.AbstractExecutor; + +public abstract class SetExecutor extends AbstractExecutor { + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java new file mode 100644 index 0000000..df757af --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.set; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.Extendable; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.RegionProvider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public abstract class SetOpExecutor extends SetExecutor implements Extendable { + + @SuppressWarnings("unchecked") + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + int setsStartIndex = isStorage() ? 2 : 1; + if (commandElems.size() < setsStartIndex + 1) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), getArgsError())); + return; + } + RegionProvider rC = context.getRegionProvider(); + ByteArrayWrapper destination = null; + if (isStorage()) + destination = command.getKey(); + + ByteArrayWrapper firstSetKey = new ByteArrayWrapper(commandElems.get(setsStartIndex++)); + if (!isStorage()) + checkDataType(firstSetKey, RedisDataType.REDIS_SET, context); + Region<ByteArrayWrapper, Boolean> region = + (Region<ByteArrayWrapper, Boolean>) rC.getRegion(firstSetKey); + Set<ByteArrayWrapper> firstSet = null; + if (region != null) { + firstSet = new HashSet<ByteArrayWrapper>(region.keySet()); + } + ArrayList<Set<ByteArrayWrapper>> setList = new ArrayList<Set<ByteArrayWrapper>>(); + for (int i = setsStartIndex; i < commandElems.size(); i++) { + ByteArrayWrapper key = new ByteArrayWrapper(commandElems.get(i)); + checkDataType(key, RedisDataType.REDIS_SET, context); + region = (Region<ByteArrayWrapper, Boolean>) rC.getRegion(key); + if (region != null) + setList.add(region.keySet()); + else if (this instanceof SInterExecutor) + setList.add(null); + } + if (setList.isEmpty()) { + if (isStorage()) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), 0)); + context.getRegionProvider().removeKey(destination); + } else { + if (firstSet == null) + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + else + command.setResponse( + Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), firstSet)); + } + return; + } + + Set<ByteArrayWrapper> resultSet = setOp(firstSet, setList); + if (isStorage()) { + Region<ByteArrayWrapper, Boolean> newRegion = null; // (Region<ByteArrayWrapper, Boolean>) + // rC.getRegion(destination); + rC.removeKey(destination); + if (resultSet != null) { + Map<ByteArrayWrapper, Boolean> map = new HashMap<ByteArrayWrapper, Boolean>(); + for (ByteArrayWrapper entry : resultSet) + map.put(entry, Boolean.TRUE); + if (!map.isEmpty()) { + newRegion = (Region<ByteArrayWrapper, Boolean>) rC.getOrCreateRegion(destination, + RedisDataType.REDIS_SET, context); + newRegion.putAll(map); + } + command + .setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), resultSet.size())); + } else { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), 0)); + } + } else { + if (resultSet == null || resultSet.isEmpty()) + command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator())); + else + command.setResponse( + Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), resultSet)); + } + } + + protected abstract boolean isStorage(); + + protected abstract Set<ByteArrayWrapper> setOp(Set<ByteArrayWrapper> firstSet, + List<Set<ByteArrayWrapper>> setList); +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetExecutor.java new file mode 100644 index 0000000..5330c80 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/SortedSetExecutor.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.executor.AbstractExecutor; + +public abstract class SortedSetExecutor extends AbstractExecutor { + + @Override + protected Region<ByteArrayWrapper, DoubleWrapper> getOrCreateRegion( + ExecutionHandlerContext context, ByteArrayWrapper key, RedisDataType type) { + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, DoubleWrapper> r = (Region<ByteArrayWrapper, DoubleWrapper>) context + .getRegionProvider().getOrCreateRegion(key, type, context); + return r; + } + + protected Region<ByteArrayWrapper, DoubleWrapper> getRegion(ExecutionHandlerContext context, + ByteArrayWrapper key) { + @SuppressWarnings("unchecked") + Region<ByteArrayWrapper, DoubleWrapper> r = + (Region<ByteArrayWrapper, DoubleWrapper>) context.getRegionProvider().getRegion(key); + return r; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZAddExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZAddExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZAddExecutor.java new file mode 100644 index 0000000..40fda1b --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZAddExecutor.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ZAddExecutor extends SortedSetExecutor { + + private final String ERROR_NOT_NUMERICAL = "The inteded score is not a float"; + + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 4 || commandElems.size() % 2 == 1) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZADD)); + return; + } + + ByteArrayWrapper key = command.getKey(); + int numberOfAdds = 0; + + if (commandElems.size() > 4) { + Map<ByteArrayWrapper, DoubleWrapper> map = new HashMap<ByteArrayWrapper, DoubleWrapper>(); + for (int i = 2; i < commandElems.size(); i++) { + byte[] scoreArray = commandElems.get(i++); + byte[] memberArray = commandElems.get(i); + + Double score; + try { + score = Coder.bytesToDouble(scoreArray); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERICAL)); + return; + } + + map.put(new ByteArrayWrapper(memberArray), new DoubleWrapper(score)); + numberOfAdds++; + } + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = + getOrCreateRegion(context, key, RedisDataType.REDIS_SORTEDSET); + keyRegion.putAll(map); + } else { + byte[] scoreArray = commandElems.get(2); + byte[] memberArray = commandElems.get(3); + Double score; + try { + score = Coder.bytesToDouble(scoreArray); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERICAL)); + return; + } + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = + getOrCreateRegion(context, key, RedisDataType.REDIS_SORTEDSET); + Object oldVal = keyRegion.put(new ByteArrayWrapper(memberArray), new DoubleWrapper(score)); + + if (oldVal == null) + numberOfAdds = 1; + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), numberOfAdds)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCardExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCardExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCardExecutor.java new file mode 100644 index 0000000..ad8130e --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCardExecutor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class ZCardExecutor extends SortedSetExecutor { + + private final int NOT_EXISTS = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZCARD)); + return; + } + + ByteArrayWrapper key = command.getKey(); + + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = getRegion(context, key); + checkDataType(key, RedisDataType.REDIS_SORTEDSET, context); + + if (keyRegion == null) + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + else + command + .setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), keyRegion.size())); + + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCountExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCountExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCountExecutor.java new file mode 100644 index 0000000..21c62c8 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZCountExecutor.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.FunctionDomainException; +import org.apache.geode.cache.query.NameResolutionException; +import org.apache.geode.cache.query.Query; +import org.apache.geode.cache.query.QueryInvocationTargetException; +import org.apache.geode.cache.query.SelectResults; +import org.apache.geode.cache.query.TypeMismatchException; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.executor.SortedSetQuery; + +import java.util.List; + +public class ZCountExecutor extends SortedSetExecutor { + + private final String ERROR_NOT_NUMERIC = "The number provided is not numeric"; + + private final int NOT_EXISTS = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZCOUNT)); + return; + } + + ByteArrayWrapper key = command.getKey(); + + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = getRegion(context, key); + checkDataType(key, RedisDataType.REDIS_SORTEDSET, context); + + if (keyRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + return; + } + + boolean startInclusive = true; + boolean stopInclusive = true; + double start; + double stop; + + byte[] startArray = commandElems.get(2); + byte[] stopArray = commandElems.get(3); + String startString = Coder.bytesToString(startArray); + String stopString = Coder.bytesToString(stopArray); + + if (startArray[0] == Coder.OPEN_BRACE_ID) { + startString = startString.substring(1); + startInclusive = false; + } + if (stopArray[0] == Coder.OPEN_BRACE_ID) { + stopString = stopString.substring(1); + stopInclusive = false; + } + + try { + start = Coder.stringToDouble(startString); + stop = Coder.stringToDouble(stopString); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERIC)); + return; + } + + + int count; + try { + count = getCount(key, keyRegion, context, start, stop, startInclusive, stopInclusive); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), count)); + } + + private int getCount(ByteArrayWrapper key, Region<ByteArrayWrapper, DoubleWrapper> keyRegion, + ExecutionHandlerContext context, double start, double stop, boolean startInclusive, + boolean stopInclusive) throws FunctionDomainException, TypeMismatchException, + NameResolutionException, QueryInvocationTargetException { + if (start == Double.NEGATIVE_INFINITY && stop == Double.POSITIVE_INFINITY) + return keyRegion.size(); + else if (start == Double.POSITIVE_INFINITY || stop == Double.NEGATIVE_INFINITY) + return 0; + + Query query; + Object[] params; + if (start == Double.NEGATIVE_INFINITY) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZCOUNTNINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZCOUNTNINF, context); + } + params = new Object[] {stop}; + } else if (stop == Double.POSITIVE_INFINITY) { + if (startInclusive) { + query = getQuery(key, SortedSetQuery.ZCOUNTPINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZCOUNTPINF, context); + } + params = new Object[] {start}; + } else { + if (startInclusive) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZCOUNTSTISI, context); + } else { + query = getQuery(key, SortedSetQuery.ZCOUNTSTI, context); + } + } else { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZCOUNTSI, context); + } else { + query = getQuery(key, SortedSetQuery.ZCOUNT, context); + } + } + params = new Object[] {start, stop}; + } + + SelectResults<?> results = (SelectResults<?>) query.execute(params); + + return (Integer) results.asList().get(0); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java new file mode 100644 index 0000000..ff3195a --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; + +import java.util.List; + +public class ZIncrByExecutor extends SortedSetExecutor { + + private final String ERROR_NOT_NUMERIC = "The number provided is not numeric"; + private final String ERROR_NAN = "This increment is illegal because it would result in a NaN"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() != 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZINCRBY)); + return; + } + + ByteArrayWrapper key = command.getKey(); + + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = + getOrCreateRegion(context, key, RedisDataType.REDIS_SORTEDSET); + + ByteArrayWrapper member = new ByteArrayWrapper(commandElems.get(3)); + + double incr; + + try { + byte[] incrArray = commandElems.get(2); + incr = Coder.bytesToDouble(incrArray); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERIC)); + return; + } + + DoubleWrapper score = keyRegion.get(member); + + if (score == null) { + keyRegion.put(member, new DoubleWrapper(incr)); + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), incr)); + return; + } + double result = score.score + incr; + if (Double.isNaN(result)) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NAN)); + return; + } + score.score = result; + keyRegion.put(member, score); + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), score.score)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java new file mode 100644 index 0000000..df326c7 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZLexCountExecutor.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.Query; +import org.apache.geode.cache.query.SelectResults; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.executor.SortedSetQuery; + +import java.util.List; + +public class ZLexCountExecutor extends SortedSetExecutor { + + private final String ERROR_ILLEGAL_SYNTAX = + "The min and max strings must either start with a (, [ or be - or +"; + + private final int NOT_EXISTS = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 4) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZLEXCOUNT)); + return; + } + + ByteArrayWrapper key = command.getKey(); + + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = getRegion(context, key); + checkDataType(key, RedisDataType.REDIS_SORTEDSET, context); + + if (keyRegion == null) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_EXISTS)); + return; + } + + boolean minInclusive = false; + boolean maxInclusive = false; + + byte[] minArray = commandElems.get(2); + byte[] maxArray = commandElems.get(3); + String startString = Coder.bytesToString(minArray); + String stopString = Coder.bytesToString(maxArray); + + if (minArray[0] == Coder.OPEN_BRACE_ID) { + startString = startString.substring(1); + minInclusive = false; + } else if (minArray[0] == Coder.OPEN_BRACKET_ID) { + startString = startString.substring(1); + minInclusive = true; + } else if (minArray[0] != Coder.HYPHEN_ID) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_SYNTAX)); + return; + } + + if (maxArray[0] == Coder.OPEN_BRACE_ID) { + stopString = stopString.substring(1); + maxInclusive = false; + } else if (maxArray[0] == Coder.OPEN_BRACKET_ID) { + stopString = stopString.substring(1); + maxInclusive = true; + } else if (maxArray[0] != Coder.PLUS_ID) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_SYNTAX)); + return; + } + + + int count; + try { + count = getCount(key, keyRegion, context, Coder.stringToByteArrayWrapper(startString), + Coder.stringToByteArrayWrapper(stopString), minInclusive, maxInclusive); + } catch (Exception e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), e.toString())); + return; + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), count)); + } + + private int getCount(ByteArrayWrapper key, Region<ByteArrayWrapper, DoubleWrapper> keyRegion, + ExecutionHandlerContext context, ByteArrayWrapper start, ByteArrayWrapper stop, + boolean startInclusive, boolean stopInclusive) throws Exception { + if (start.equals("-") && stop.equals("+")) + return keyRegion.size(); + else if (start.equals("+") || stop.equals("-")) + return 0; + + Query query; + Object[] params; + if (start.equals("-")) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTNINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTNINF, context); + } + params = new Object[] {stop}; + } else if (stop.equals("+")) { + if (startInclusive) { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTPINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTPINF, context); + } + params = new Object[] {start}; + } else { + if (startInclusive) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTSTISI, context); + } else { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTSTI, context); + } + } else { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZLEXCOUNTSI, context); + } else { + query = getQuery(key, SortedSetQuery.ZLEXCOUNT, context); + } + } + params = new Object[] {start, stop}; + } + + SelectResults<?> results = (SelectResults<?>) query.execute(params); + + return (Integer) results.asList().get(0); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZRangeByLexExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZRangeByLexExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZRangeByLexExecutor.java new file mode 100644 index 0000000..e41b730 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZRangeByLexExecutor.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.sortedset; + +import io.netty.buffer.ByteBuf; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.query.FunctionDomainException; +import org.apache.geode.cache.query.NameResolutionException; +import org.apache.geode.cache.query.Query; +import org.apache.geode.cache.query.QueryInvocationTargetException; +import org.apache.geode.cache.query.SelectResults; +import org.apache.geode.cache.query.TypeMismatchException; +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.DoubleWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.executor.SortedSetQuery; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class ZRangeByLexExecutor extends SortedSetExecutor { + + private final String ERROR_NOT_NUMERIC = "The index provided is not numeric"; + + private final String ERROR_ILLEGAL_SYNTAX = + "The min and max strings must either start with a (, [ or be - or +"; + + private final String ERROR_LIMIT = "The offset and count cannot be negative"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + if (commandElems.size() < 4) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ZRANGEBYLEX)); + return; + } + + boolean existsLimit = false; + + if (commandElems.size() >= 7) { + byte[] fifthElem = commandElems.get(4); + existsLimit = Coder.bytesToString(fifthElem).equalsIgnoreCase("LIMIT"); + } + + int offset = 0; + int limit = 0; + + if (existsLimit) { + try { + byte[] offsetArray = commandElems.get(5); + byte[] limitArray = commandElems.get(6); + offset = Coder.bytesToInt(offsetArray); + limit = Coder.bytesToInt(limitArray); + } catch (NumberFormatException e) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_NUMERIC)); + return; + } + } + + if (offset < 0 || limit < 0) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_LIMIT)); + return; + } + + ByteArrayWrapper key = command.getKey(); + Region<ByteArrayWrapper, DoubleWrapper> keyRegion = + getOrCreateRegion(context, key, RedisDataType.REDIS_SORTEDSET); + + if (keyRegion == null) { + command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator())); + return; + } + + boolean minInclusive = false; + boolean maxInclusive = false; + + byte[] minArray = commandElems.get(2); + byte[] maxArray = commandElems.get(3); + String startString = Coder.bytesToString(minArray); + String stopString = Coder.bytesToString(maxArray); + if (minArray[0] == Coder.OPEN_BRACE_ID) { + startString = startString.substring(1); + minInclusive = false; + } else if (minArray[0] == Coder.OPEN_BRACKET_ID) { + startString = startString.substring(1); + minInclusive = true; + } else if (minArray[0] != Coder.HYPHEN_ID) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_SYNTAX)); + return; + } + + if (maxArray[0] == Coder.OPEN_BRACE_ID) { + stopString = stopString.substring(1); + maxInclusive = false; + } else if (maxArray[0] == Coder.OPEN_BRACKET_ID) { + stopString = stopString.substring(1); + maxInclusive = true; + } else if (maxArray[0] != Coder.PLUS_ID) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_SYNTAX)); + return; + } + Collection<ByteArrayWrapper> list = null; + if (!(existsLimit && limit == 0)) { + try { + list = getRange(key, keyRegion, context, Coder.stringToByteArrayWrapper(startString), + Coder.stringToByteArrayWrapper(stopString), minInclusive, maxInclusive, offset, limit); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + if (list == null) + command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator())); + else + command.setResponse(getCustomBulkStringArrayResponse(list, context)); + } + + private List<ByteArrayWrapper> getRange(ByteArrayWrapper key, + Region<ByteArrayWrapper, DoubleWrapper> keyRegion, ExecutionHandlerContext context, + ByteArrayWrapper start, ByteArrayWrapper stop, boolean startInclusive, boolean stopInclusive, + int offset, int limit) throws FunctionDomainException, TypeMismatchException, + NameResolutionException, QueryInvocationTargetException { + if (start.equals("-") && stop.equals("+")) { + List<ByteArrayWrapper> l = new ArrayList<ByteArrayWrapper>(keyRegion.keySet()); + int size = l.size(); + Collections.sort(l); + if (limit == 0) + limit += size; + l = l.subList(Math.min(size, offset), Math.min(offset + limit, size)); + return l; + } else if (start.equals("+") || stop.equals("-")) + return null; + + Query query; + Object[] params; + if (start.equals("-")) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXNINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXNINF, context); + } + params = new Object[] {stop, INFINITY_LIMIT}; + } else if (stop.equals("+")) { + if (startInclusive) { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXPINFI, context); + } else { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXPINF, context); + } + params = new Object[] {start, INFINITY_LIMIT}; + } else { + if (startInclusive) { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXSTISI, context); + } else { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXSTI, context); + } + } else { + if (stopInclusive) { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEXSI, context); + } else { + query = getQuery(key, SortedSetQuery.ZRANGEBYLEX, context); + } + } + params = new Object[] {start, stop, INFINITY_LIMIT}; + } + if (limit > 0) + params[params.length - 1] = (limit + offset); + SelectResults<ByteArrayWrapper> results = + (SelectResults<ByteArrayWrapper>) query.execute(params); + List<ByteArrayWrapper> list = results.asList(); + int size = list.size(); + return list.subList(Math.min(size, offset), size); + + } + + private final ByteBuf getCustomBulkStringArrayResponse(Collection<ByteArrayWrapper> items, + ExecutionHandlerContext context) { + Iterator<ByteArrayWrapper> it = items.iterator(); + ByteBuf response = context.getByteBufAllocator().buffer(); + response.writeByte(Coder.ARRAY_ID); + response.writeBytes(Coder.intToBytes(items.size())); + response.writeBytes(Coder.CRLFar); + + while (it.hasNext()) { + ByteArrayWrapper next = it.next(); + byte[] byteAr = next.toBytes(); + response.writeByte(Coder.BULK_STRING_ID); + response.writeBytes(Coder.intToBytes(byteAr.length)); + response.writeBytes(Coder.CRLFar); + response.writeBytes(byteAr); + response.writeBytes(Coder.CRLFar); + } + + return response; + } + +}
