http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java new file mode 100644 index 0000000..d63c02f --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java @@ -0,0 +1,96 @@ +/* + * 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.string; + +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.Arrays; +import java.util.List; + +public class GetRangeExecutor extends StringExecutor { + + private final String ERROR_NOT_INT = "The indexes provided must be numeric values"; + + private final int startIndex = 2; + + private final int stopIndex = 3; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.GETRANGE)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_STRING, context); + ByteArrayWrapper valueWrapper = r.get(key); + + if (valueWrapper == null) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + + byte[] value = valueWrapper.toBytes(); + int length = value.length; + + long start; + long end; + + + try { + byte[] startI = commandElems.get(startIndex); + byte[] stopI = commandElems.get(stopIndex); + start = Coder.bytesToLong(startI); + end = Coder.bytesToLong(stopI); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_INT)); + return; + } + start = getBoundedStartIndex(start, length); + end = getBoundedEndIndex(end, length); + + /* + * If the properly formatted indexes are illegal, send nil + */ + if (start > end || start == length) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + /* + * 1 is added to end because the end in copyOfRange is exclusive but in Redis it is inclusive + */ + if (end != length) + end++; + byte[] returnRange = Arrays.copyOfRange(value, (int) start, (int) end); + if (returnRange == null || returnRange.length == 0) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), returnRange)); + + } +}
http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java new file mode 100644 index 0000000..b0edfd4 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java @@ -0,0 +1,58 @@ +/* + * 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.string; + +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; + +public class GetSetExecutor extends StringExecutor { + + private final int VALUE_INDEX = 2; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.GETSET)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + + byte[] newCharValue = commandElems.get(VALUE_INDEX); + ByteArrayWrapper newValueWrapper = new ByteArrayWrapper(newCharValue); + + ByteArrayWrapper oldValueWrapper = r.get(key); + r.put(key, newValueWrapper); + + if (oldValueWrapper == null) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + } else { + command.setResponse( + Coder.getBulkStringResponse(context.getByteBufAllocator(), oldValueWrapper.toBytes())); + } + + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByExecutor.java new file mode 100644 index 0000000..0dac15b --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByExecutor.java @@ -0,0 +1,108 @@ +/* + * 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.string; + +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; + +public class IncrByExecutor extends StringExecutor { + + private final String ERROR_VALUE_NOT_USABLE = + "The value at this key cannot be incremented numerically"; + + private final String ERROR_INCREMENT_NOT_USABLE = "The increment on this key must be numeric"; + + private final String ERROR_OVERFLOW = "This incrementation cannot be performed due to overflow"; + + private final int INCREMENT_INDEX = 2; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.INCRBY)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + ByteArrayWrapper valueWrapper = r.get(key); + + /* + * Try increment + */ + + byte[] incrArray = commandElems.get(INCREMENT_INDEX); + Long increment; + + try { + increment = Coder.bytesToLong(incrArray); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_INCREMENT_NOT_USABLE)); + return; + } + + /* + * Value does not exist + */ + + if (valueWrapper == null) { + r.put(key, new ByteArrayWrapper(incrArray)); + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), increment)); + return; + } + + /* + * Value exists + */ + + String stringValue = Coder.bytesToString(valueWrapper.toBytes()); + Long value; + try { + value = Long.parseLong(stringValue); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_VALUE_NOT_USABLE)); + return; + } + + /* + * Check for overflow + */ + if (value >= 0 && increment > (Long.MAX_VALUE - value)) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_OVERFLOW)); + return; + } + + value += increment; + + stringValue = "" + value; + r.put(key, new ByteArrayWrapper(Coder.stringToBytes(stringValue))); + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), value)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java new file mode 100644 index 0000000..e1ff80f --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java @@ -0,0 +1,128 @@ +/* + * 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.string; + +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 java.util.List; + +public class IncrByFloatExecutor extends StringExecutor { + + private final String ERROR_VALUE_NOT_USABLE = + "Invalid value at this key and cannot be incremented numerically"; + + private final String ERROR_INCREMENT_NOT_USABLE = + "The increment on this key must be a valid floating point numeric"; + + private final String ERROR_OVERFLOW = "This incrementation cannot be performed due to overflow"; + + private final int INCREMENT_INDEX = 2; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + if (commandElems.size() < 3) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.INCRBYFLOAT)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + ByteArrayWrapper valueWrapper = r.get(key); + + /* + * Try increment + */ + + byte[] incrArray = commandElems.get(INCREMENT_INDEX); + String doub = Coder.bytesToString(incrArray).toLowerCase(); + if (doub.contains("inf") || doub.contains("nan")) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), + "Increment would produce NaN or infinity")); + return; + } else if (valueWrapper != null && valueWrapper.toString().contains(" ")) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_VALUE_NOT_USABLE)); + return; + } + + + Double increment; + + try { + increment = Coder.stringToDouble(doub); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_INCREMENT_NOT_USABLE)); + return; + } + + /* + * Value does not exist + */ + + if (valueWrapper == null) { + r.put(key, new ByteArrayWrapper(incrArray)); + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), increment)); + return; + } + + /* + * Value exists + */ + + String stringValue = Coder.bytesToString(valueWrapper.toBytes()); + + Double value; + try { + value = Coder.stringToDouble(stringValue); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_VALUE_NOT_USABLE)); + return; + } + + /* + * Check for overflow + */ + if (value >= 0 && increment > (Double.MAX_VALUE - value)) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_OVERFLOW)); + return; + } + + double result = value + increment; + if (Double.isNaN(result) || Double.isInfinite(result)) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ERROR_NAN_INF_INCR)); + return; + } + value += increment; + + stringValue = "" + value; + r.put(key, new ByteArrayWrapper(Coder.stringToBytes(stringValue))); + + command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), value)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrExecutor.java new file mode 100644 index 0000000..1e915b2 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/IncrExecutor.java @@ -0,0 +1,91 @@ +/* + * 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.string; + +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; + +public class IncrExecutor extends StringExecutor { + + private final String ERROR_VALUE_NOT_USABLE = + "The value at this key cannot be incremented numerically"; + + private final String ERROR_OVERFLOW = "This incrementation cannot be performed due to overflow"; + + private final int INIT_VALUE_INT = 1; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.INCR)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + ByteArrayWrapper valueWrapper = r.get(key); + + /* + * Value does not exist + */ + + if (valueWrapper == null) { + byte[] newValue = {Coder.NUMBER_1_BYTE}; + r.put(key, new ByteArrayWrapper(newValue)); + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), INIT_VALUE_INT)); + return; + } + + /* + * Value exists + */ + + String stringValue = valueWrapper.toString(); + + Long value; + try { + value = Long.parseLong(stringValue); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_VALUE_NOT_USABLE)); + return; + } + + if (value == Long.MAX_VALUE) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_OVERFLOW)); + return; + } + + value++; + + stringValue = "" + value; + r.put(key, new ByteArrayWrapper(Coder.stringToBytes(stringValue))); + + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), value)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java new file mode 100644 index 0000000..dab7eae --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java @@ -0,0 +1,67 @@ +/* + * 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.string; + +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.Collection; +import java.util.List; +import java.util.Map; + +public class MGetExecutor extends StringExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.MGET)); + return; + } + + Collection<ByteArrayWrapper> keys = new ArrayList<ByteArrayWrapper>(); + for (int i = 1; i < commandElems.size(); i++) { + byte[] keyArray = commandElems.get(i); + ByteArrayWrapper key = new ByteArrayWrapper(keyArray); + /* + * try { checkDataType(key, RedisDataType.REDIS_STRING, context); } catch + * (RedisDataTypeMismatchException e) { keys.ad continue; } + */ + keys.add(key); + } + + Map<ByteArrayWrapper, ByteArrayWrapper> results = r.getAll(keys); + + Collection<ByteArrayWrapper> values = new ArrayList<ByteArrayWrapper>(); + + /* + * This is done to preserve order in the output + */ + for (ByteArrayWrapper key : keys) + values.add(results.get(key)); + + command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), values)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java new file mode 100644 index 0000000..bf06497 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.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.string; + +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.RedisDataTypeMismatchException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MSetExecutor extends StringExecutor { + + private final String SUCCESS = "OK"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3 || commandElems.size() % 2 == 0) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.MSET)); + return; + } + + Map<ByteArrayWrapper, ByteArrayWrapper> map = new HashMap<ByteArrayWrapper, ByteArrayWrapper>(); + for (int i = 1; i < commandElems.size(); i += 2) { + byte[] keyArray = commandElems.get(i); + ByteArrayWrapper key = new ByteArrayWrapper(keyArray); + try { + checkAndSetDataType(key, context); + } catch (RedisDataTypeMismatchException e) { + continue; + } + byte[] value = commandElems.get(i + 1); + map.put(key, new ByteArrayWrapper(value)); + } + r.putAll(map); + + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetNXExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetNXExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetNXExecutor.java new file mode 100644 index 0000000..eefffe9 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/MSetNXExecutor.java @@ -0,0 +1,86 @@ +/* + * 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.string; + +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.RedisDataTypeMismatchException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MSetNXExecutor extends StringExecutor { + + private final int SET = 1; + + private final int NOT_SET = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3 || commandElems.size() % 2 == 0) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.MSETNX)); + return; + } + + boolean hasEntry = false; + + Map<ByteArrayWrapper, ByteArrayWrapper> map = new HashMap<ByteArrayWrapper, ByteArrayWrapper>(); + for (int i = 1; i < commandElems.size(); i += 2) { + byte[] keyArray = commandElems.get(i); + ByteArrayWrapper key = new ByteArrayWrapper(keyArray); + try { + checkDataType(key, context); + } catch (RedisDataTypeMismatchException e) { + hasEntry = true; + break; + } + byte[] value = commandElems.get(i + 1); + map.put(key, new ByteArrayWrapper(value)); + if (r.containsKey(key)) { + hasEntry = true; + break; + } + } + boolean successful = false; + if (!hasEntry) { + successful = true; + for (ByteArrayWrapper k : map.keySet()) { + try { + checkAndSetDataType(k, context); + } catch (RedisDataTypeMismatchException e) { + successful = false; + break; + } + } + r.putAll(map); + } + if (successful) { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), SET)); + } else { + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_SET)); + } + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/PSetEXExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/PSetEXExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/PSetEXExecutor.java new file mode 100644 index 0000000..c8e6111 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/PSetEXExecutor.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.string; + +import org.apache.geode.redis.internal.RedisConstants.ArityDef; + + +public class PSetEXExecutor extends SetEXExecutor { + + @Override + public boolean timeUnitMillis() { + return true; + } + + @Override + public String getArgsError() { + return ArityDef.PSETEX; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetBitExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetBitExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetBitExecutor.java new file mode 100644 index 0000000..7cef3e0 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetBitExecutor.java @@ -0,0 +1,108 @@ +/* + * 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.string; + +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; + +public class SetBitExecutor extends StringExecutor { + + private final String ERROR_NOT_INT = "The number provided must be numeric"; + + private final String ERROR_VALUE = "The value is out of range, must be 0 or 1"; + + private final String ERROR_ILLEGAL_OFFSET = + "The offset is out of range, must be greater than or equal to 0 and at most 4294967295 (512MB)"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SETBIT)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + ByteArrayWrapper wrapper = r.get(key); + + long offset; + int value; + int returnBit = 0; + try { + byte[] offAr = commandElems.get(2); + byte[] valAr = commandElems.get(3); + offset = Coder.bytesToLong(offAr); + value = Coder.bytesToInt(valAr); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_INT)); + return; + } + + if (value != 0 && value != 1) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_VALUE)); + return; + } + + if (offset < 0 || offset > 4294967295L) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_OFFSET)); + return; + } + + int byteIndex = (int) (offset / 8); + offset %= 8; + + if (wrapper == null) { + byte[] bytes = new byte[byteIndex + 1]; + if (value == 1) + bytes[byteIndex] = (byte) (0x80 >> offset); + r.put(key, new ByteArrayWrapper(bytes)); + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), 0)); + } else { + + byte[] bytes = wrapper.toBytes(); + if (byteIndex < bytes.length) + returnBit = (bytes[byteIndex] & (0x80 >> offset)) >> (7 - offset); + else + returnBit = 0; + + if (byteIndex < bytes.length) { + bytes[byteIndex] = value == 1 ? (byte) (bytes[byteIndex] | (0x80 >> offset)) + : (byte) (bytes[byteIndex] & ~(0x80 >> offset)); + r.put(key, new ByteArrayWrapper(bytes)); + } else { + byte[] newBytes = new byte[byteIndex + 1]; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length); + newBytes[byteIndex] = value == 1 ? (byte) (newBytes[byteIndex] | (0x80 >> offset)) + : (byte) (newBytes[byteIndex] & ~(0x80 >> offset)); + r.put(key, new ByteArrayWrapper(newBytes)); + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), returnBit)); + } + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetEXExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetEXExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetEXExecutor.java new file mode 100644 index 0000000..71498c6 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetEXExecutor.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.string; + +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.RedisConstants.ArityDef; +import org.apache.geode.redis.internal.executor.AbstractExecutor; + +import java.util.List; + +public class SetEXExecutor extends StringExecutor implements Extendable { + + private final String ERROR_SECONDS_NOT_A_NUMBER = + "The expiration argument provided was not a number"; + + private final String ERROR_SECONDS_NOT_LEGAL = "The expiration argument must be greater than 0"; + + private final String SUCCESS = "OK"; + + private final int VALUE_INDEX = 3; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), getArgsError())); + return; + } + + ByteArrayWrapper key = command.getKey(); + byte[] value = commandElems.get(VALUE_INDEX); + + byte[] expirationArray = commandElems.get(2); + long expiration; + try { + expiration = Coder.bytesToLong(expirationArray); + } catch (NumberFormatException e) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_SECONDS_NOT_A_NUMBER)); + return; + } + + if (expiration <= 0) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_SECONDS_NOT_LEGAL)); + return; + } + + if (!timeUnitMillis()) + expiration *= AbstractExecutor.millisInSecond; + + checkAndSetDataType(key, context); + r.put(key, new ByteArrayWrapper(value)); + + context.getRegionProvider().setExpiration(key, expiration); + + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS)); + + } + + protected boolean timeUnitMillis() { + return false; + } + + @Override + public String getArgsError() { + return ArityDef.SETEX; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetExecutor.java new file mode 100644 index 0000000..b83e4c1 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetExecutor.java @@ -0,0 +1,155 @@ +/* + * 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.string; + +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.executor.AbstractExecutor; + +import java.util.List; + +public class SetExecutor extends StringExecutor { + + private final String SUCCESS = "OK"; + + private final int VALUE_INDEX = 2; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SET)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, context); + byte[] value = commandElems.get(VALUE_INDEX); + ByteArrayWrapper valueWrapper = new ByteArrayWrapper(value); + + boolean NX = false; // Set only if not exists + boolean XX = false; // Set only if exists + long expiration = 0L; + + if (commandElems.size() >= 6) { + String elem4; + String elem5; + String elem6; + + elem4 = Coder.bytesToString(commandElems.get(3)); + elem5 = Coder.bytesToString(commandElems.get(4)); + elem6 = Coder.bytesToString(commandElems.get(5)); + + if (elem4.equalsIgnoreCase("XX") || elem6.equalsIgnoreCase("XX")) + XX = true; + else if (elem4.equalsIgnoreCase("NX") || elem6.equalsIgnoreCase("NX")) + NX = true; + + if (elem4.equalsIgnoreCase("PX")) + expiration = getExpirationMillis(elem4, elem5); + else if (elem5.equalsIgnoreCase("PX")) + expiration = getExpirationMillis(elem5, elem6); + else if (elem4.equalsIgnoreCase("EX")) + expiration = getExpirationMillis(elem4, elem5); + else if (elem5.equalsIgnoreCase("EX")) + expiration = getExpirationMillis(elem5, elem6); + + } else if (commandElems.size() >= 5) { + String elem4; + String expiry; + + elem4 = Coder.bytesToString(commandElems.get(3)); + expiry = Coder.bytesToString(commandElems.get(4)); + + expiration = getExpirationMillis(elem4, expiry); + } else if (commandElems.size() >= 4) { + byte[] elem4 = commandElems.get(3); + if (elem4.length == 2 && Character.toUpperCase(elem4[1]) == 'X') { + if (Character.toUpperCase(elem4[0]) == 'N') + NX = true; + else if (Character.toUpperCase(elem4[0]) == 'X') + XX = true; + } + } + + boolean keyWasSet = false; + + if (NX) + keyWasSet = setNX(r, command, key, valueWrapper, context); + else if (XX) + keyWasSet = setXX(r, command, key, valueWrapper, context); + else { + checkAndSetDataType(key, context); + r.put(key, valueWrapper); + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS)); + keyWasSet = true; + } + + if (keyWasSet && expiration > 0L) { + context.getRegionProvider().setExpiration(key, expiration); + } + + } + + private boolean setNX(Region<ByteArrayWrapper, ByteArrayWrapper> r, Command command, + ByteArrayWrapper key, ByteArrayWrapper valueWrapper, ExecutionHandlerContext context) { + checkAndSetDataType(key, context); + Object oldValue = r.putIfAbsent(key, valueWrapper); + if (oldValue != null) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return false; + } else { + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS)); + return true; + } + } + + private boolean setXX(Region<ByteArrayWrapper, ByteArrayWrapper> r, Command command, + ByteArrayWrapper key, ByteArrayWrapper valueWrapper, ExecutionHandlerContext context) { + if (r.containsKey(key)) { + checkAndSetDataType(key, context); + r.put(key, valueWrapper); + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS)); + return true; + } else { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return false; + } + } + + private long getExpirationMillis(String expx, String expirationString) { + long expiration = 0L; + try { + expiration = Long.parseLong(expirationString); + } catch (NumberFormatException e) { + return 0L; + } + + if (expx.equalsIgnoreCase("EX")) + return expiration * AbstractExecutor.millisInSecond; + else if (expx.equalsIgnoreCase("PX")) + return expiration; + else + return 0L; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetNXExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetNXExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetNXExecutor.java new file mode 100644 index 0000000..eb4c75b --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetNXExecutor.java @@ -0,0 +1,58 @@ +/* + * 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.string; + +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; + +public class SetNXExecutor extends StringExecutor { + + private final int SET = 1; + + private final int NOT_SET = 0; + + private final int VALUE_INDEX = 2; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 3) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SETNX)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + byte[] value = commandElems.get(VALUE_INDEX); + + Object oldValue = r.putIfAbsent(key, new ByteArrayWrapper(value)); + + if (oldValue != null) + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), NOT_SET)); + else + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), SET)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetRangeExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetRangeExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetRangeExecutor.java new file mode 100644 index 0000000..e1545d0 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/SetRangeExecutor.java @@ -0,0 +1,96 @@ +/* + * 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.string; + +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; + +public class SetRangeExecutor extends StringExecutor { + + private final String ERROR_NOT_INT = "The number provided must be numeric"; + + private final String ERROR_ILLEGAL_OFFSET = + "The offset is out of range, must be greater than or equal to 0 and the offset added to the length of the value must be less than 536870911 (512MB), the maximum allowed size"; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 4) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.SETRANGE)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkAndSetDataType(key, context); + ByteArrayWrapper wrapper = r.get(key); + + int offset; + byte[] value = commandElems.get(3); + try { + byte[] offAr = commandElems.get(2); + offset = Coder.bytesToInt(offAr); + } catch (NumberFormatException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_NOT_INT)); + return; + } + + int totalLength = offset + value.length; + if (offset < 0 || totalLength > 536870911) { + command + .setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_ILLEGAL_OFFSET)); + return; + } else if (value.length == 0) { + int length = wrapper == null ? 0 : wrapper.toBytes().length; + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), length)); + if (wrapper == null) + context.getRegionProvider().removeKey(key); + return; + } + + if (wrapper == null) { + byte[] bytes = new byte[totalLength]; + System.arraycopy(value, 0, bytes, offset, value.length); + r.put(key, new ByteArrayWrapper(bytes)); + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), bytes.length)); + } else { + + byte[] bytes = wrapper.toBytes(); + int returnLength; + if (totalLength < bytes.length) { + System.arraycopy(value, 0, bytes, offset, value.length); + r.put(key, new ByteArrayWrapper(bytes)); + returnLength = bytes.length; + } else { + byte[] newBytes = new byte[totalLength]; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length); + System.arraycopy(value, 0, newBytes, offset, value.length); + returnLength = newBytes.length; + r.put(key, new ByteArrayWrapper(newBytes)); + } + + command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), returnLength)); + } + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StringExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StringExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StringExecutor.java new file mode 100644 index 0000000..fb91f09 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StringExecutor.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.string; + +import org.apache.geode.redis.internal.ByteArrayWrapper; +import org.apache.geode.redis.internal.ExecutionHandlerContext; +import org.apache.geode.redis.internal.RedisDataType; +import org.apache.geode.redis.internal.RedisDataTypeMismatchException; +import org.apache.geode.redis.internal.executor.AbstractExecutor; + +public abstract class StringExecutor extends AbstractExecutor { + + protected final void checkAndSetDataType(ByteArrayWrapper key, ExecutionHandlerContext context) { + Object oldVal = context.getRegionProvider().metaPutIfAbsent(key, RedisDataType.REDIS_STRING); + if (oldVal == RedisDataType.REDIS_PROTECTED) + throw new RedisDataTypeMismatchException("The key name \"" + key + "\" is protected"); + if (oldVal != null && oldVal != RedisDataType.REDIS_STRING) + throw new RedisDataTypeMismatchException( + "The key name \"" + key + "\" is already used by a " + oldVal.toString()); + } + + protected void checkDataType(ByteArrayWrapper key, ExecutionHandlerContext context) { + RedisDataType currentType = context.getRegionProvider().getRedisDataType(key); + if (currentType == null) + return; + if (currentType == RedisDataType.REDIS_PROTECTED) + throw new RedisDataTypeMismatchException("The key name \"" + key + "\" is protected"); + if (currentType != RedisDataType.REDIS_STRING) + throw new RedisDataTypeMismatchException( + "The key name \"" + key + "\" is already used by a " + currentType.toString()); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StrlenExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StrlenExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StrlenExecutor.java new file mode 100644 index 0000000..345db20 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/string/StrlenExecutor.java @@ -0,0 +1,56 @@ +/* + * 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.string; + +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 StrlenExecutor extends StringExecutor { + + private final int KEY_DOES_NOT_EXIST = 0; + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + List<byte[]> commandElems = command.getProcessedCommand(); + + Region<ByteArrayWrapper, ByteArrayWrapper> r = context.getRegionProvider().getStringsRegion(); + + if (commandElems.size() < 2) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.STRLEN)); + return; + } + + ByteArrayWrapper key = command.getKey(); + checkDataType(key, RedisDataType.REDIS_STRING, context); + ByteArrayWrapper valueWrapper = r.get(key); + + + if (valueWrapper == null) + command + .setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), KEY_DOES_NOT_EXIST)); + else + command.setResponse( + Coder.getIntegerResponse(context.getByteBufAllocator(), valueWrapper.toBytes().length)); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/DiscardExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/DiscardExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/DiscardExecutor.java new file mode 100644 index 0000000..16bf575 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/DiscardExecutor.java @@ -0,0 +1,40 @@ +/* + * 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.transactions; + +import org.apache.geode.cache.CacheTransactionManager; +import org.apache.geode.cache.TransactionId; +import org.apache.geode.redis.internal.Coder; +import org.apache.geode.redis.internal.Command; +import org.apache.geode.redis.internal.ExecutionHandlerContext; + +public class DiscardExecutor extends TransactionExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + + CacheTransactionManager txm = context.getCacheTransactionManager(); + + if (context.hasTransaction()) { + TransactionId transactionId = context.getTransactionID(); + txm.resume(transactionId); + txm.rollback(); + context.clearTransaction(); + } + + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), "OK")); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/ExecExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/ExecExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/ExecExecutor.java new file mode 100644 index 0000000..9f3b402 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/ExecExecutor.java @@ -0,0 +1,86 @@ +/* + * 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.transactions; + +import io.netty.buffer.ByteBuf; +import org.apache.geode.cache.CacheTransactionManager; +import org.apache.geode.cache.CommitConflictException; +import org.apache.geode.cache.TransactionId; +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 java.util.Queue; + +public class ExecExecutor extends TransactionExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + + CacheTransactionManager txm = context.getCacheTransactionManager(); + + if (!context.hasTransaction()) { + command.setResponse(Coder.getNilResponse(context.getByteBufAllocator())); + return; + } + + TransactionId transactionId = context.getTransactionID(); + + txm.resume(transactionId); + + boolean hasError = hasError(context.getTransactionQueue()); + + if (hasError) + txm.rollback(); + else { + try { + txm.commit(); + } catch (CommitConflictException e) { + command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), + RedisConstants.ERROR_COMMIT_CONFLICT)); + context.clearTransaction(); + return; + } + } + + ByteBuf response = constructResponseExec(context); + command.setResponse(response); + + context.clearTransaction(); + } + + private ByteBuf constructResponseExec(ExecutionHandlerContext context) { + Queue<Command> cQ = context.getTransactionQueue(); + ByteBuf response = context.getByteBufAllocator().buffer(); + response.writeByte(Coder.ARRAY_ID); + response.writeBytes(Coder.intToBytes(cQ.size())); + response.writeBytes(Coder.CRLFar); + + for (Command c : cQ) { + ByteBuf r = c.getResponse(); + response.writeBytes(r); + } + return response; + } + + private boolean hasError(Queue<Command> queue) { + for (Command c : queue) { + if (c.hasError()) + return true; + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/MultiExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/MultiExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/MultiExecutor.java new file mode 100644 index 0000000..21a41d3 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/MultiExecutor.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.transactions; + +import org.apache.geode.cache.CacheTransactionManager; +import org.apache.geode.cache.TransactionId; +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; + +public class MultiExecutor extends TransactionExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + + CacheTransactionManager txm = context.getCacheTransactionManager(); + + command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), "OK")); + + if (context.hasTransaction()) { + throw new IllegalStateException(RedisConstants.ERROR_NESTED_MULTI); + } + + txm.begin(); + + TransactionId id = txm.suspend(); + + context.setTransactionID(id); + + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/TransactionExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/TransactionExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/TransactionExecutor.java new file mode 100644 index 0000000..27ade8f --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/TransactionExecutor.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.transactions; + +import org.apache.geode.redis.internal.executor.AbstractExecutor; + +public abstract class TransactionExecutor extends AbstractExecutor { + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/UnwatchExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/UnwatchExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/UnwatchExecutor.java new file mode 100644 index 0000000..2323550 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/UnwatchExecutor.java @@ -0,0 +1,30 @@ +/* + * 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.transactions; + +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; + +public class UnwatchExecutor extends TransactionExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ERROR_UNWATCH)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/WatchExecutor.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/WatchExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/WatchExecutor.java new file mode 100644 index 0000000..1dbe36e --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/transactions/WatchExecutor.java @@ -0,0 +1,30 @@ +/* + * 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.transactions; + +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; + +public class WatchExecutor extends TransactionExecutor { + + @Override + public void executeCommand(Command command, ExecutionHandlerContext context) { + command.setResponse( + Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ERROR_WATCH)); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/Bits.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/Bits.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/Bits.java new file mode 100644 index 0000000..92cef47 --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/Bits.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 Clearspring Technologies, Inc. + * + * 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.apache.geode.redis.internal.hll; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; + +public class Bits { + + public static int[] getBits(byte[] mBytes) throws IOException { + int bitSize = mBytes.length / 4; + int[] bits = new int[bitSize]; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mBytes)); + for (int i = 0; i < bitSize; i++) { + bits[i] = dis.readInt(); + } + return bits; + } + + /** + * This method might be better described as "byte array to int array" or "data input to int array" + */ + public static int[] getBits(DataInput dataIn, int byteLength) throws IOException { + int bitSize = byteLength / 4; + int[] bits = new int[bitSize]; + for (int i = 0; i < bitSize; i++) { + bits[i] = dataIn.readInt(); + } + return bits; + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/CardinalityMergeException.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/CardinalityMergeException.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/CardinalityMergeException.java new file mode 100644 index 0000000..561c3ef --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/CardinalityMergeException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2011 Clearspring Technologies, Inc. + * + * 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.apache.geode.redis.internal.hll; + +@SuppressWarnings("serial") +public abstract class CardinalityMergeException extends Exception { + + public CardinalityMergeException(String message) { + super(message); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/HyperLogLog.java ---------------------------------------------------------------------- diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/HyperLogLog.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/HyperLogLog.java new file mode 100644 index 0000000..b89512e --- /dev/null +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/hll/HyperLogLog.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2012 Clearspring Technologies, Inc. + * + * 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.apache.geode.redis.internal.hll; + +import org.apache.geode.redis.internal.executor.hll.HllExecutor; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.Serializable; + +/** + * Java implementation of HyperLogLog (HLL) algorithm from this paper: + * <p/> + * http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf + * <p/> + * HLL is an improved version of LogLog that is capable of estimating the cardinality of a set with + * accuracy = 1.04/sqrt(m) where m = 2^b. So we can control accuracy vs space usage by increasing or + * decreasing b. + * <p/> + * The main benefit of using HLL over LL is that it only requires 64% of the space that LL does to + * get the same accuracy. + * <p/> + * This implementation implements a single counter. If a large (millions) number of counters are + * required you may want to refer to: + * <p/> + * http://dsiutils.dsi.unimi.it/ + * <p/> + * It has a more complex implementation of HLL that supports multiple counters in a single object, + * drastically reducing the java overhead from creating a large number of objects. + * <p/> + * This implementation leveraged a javascript implementation that Yammer has been working on: + * <p/> + * https://github.com/yammer/probablyjs + * <p> + * Note that this implementation does not include the long range correction function defined in the + * original paper. Empirical evidence shows that the correction function causes more harm than good. + * </p> + * <p/> + * <p> + * Users have different motivations to use different types of hashing functions. Rather than try to + * keep up with all available hash functions and to remove the concern of causing future binary + * incompatibilities this class allows clients to offer the value in hashed int or long form. This + * way clients are free to change their hash function on their own time line. We recommend using + * Google's Guava Murmur3_128 implementation as it provides good performance and speed when high + * precision is required. In our tests the 32bit MurmurHash function included in this project is + * faster and produces better results than the 32 bit murmur3 implementation google provides. + * </p> + */ +public class HyperLogLog implements ICardinality, Serializable { + + private static final long serialVersionUID = -4661220245111112301L; + private final RegisterSet registerSet; + private final int log2m; + private final double alphaMM; + + + /** + * Create a new HyperLogLog instance using the specified standard deviation. + * + * @param rsd - the relative standard deviation for the counter. smaller values create counters + * that require more space. + */ + public HyperLogLog(double rsd) { + this(log2m(rsd)); + } + + private static int log2m(double rsd) { + return (int) (Math.log((1.106 / rsd) * (1.106 / rsd)) / Math.log(2)); + } + + /** + * Create a new HyperLogLog instance. The log2m parameter defines the accuracy of the counter. The + * larger the log2m the better the accuracy. + * <p/> + * accuracy = 1.04/sqrt(2^log2m) + * + * @param log2m - the number of bits to use as the basis for the HLL instance + */ + public HyperLogLog(int log2m) { + this(log2m, new RegisterSet(1 << log2m)); + } + + /** + * Creates a new HyperLogLog instance using the given registers. Used for unmarshalling a + * serialized instance and for merging multiple counters together. + * + * @param registerSet - the initial values for the register set + */ + @Deprecated + public HyperLogLog(int log2m, RegisterSet registerSet) { + if (log2m < 0 || log2m > 30) { + throw new IllegalArgumentException( + "log2m argument is " + log2m + " and is outside the range [0, 30]"); + } + this.registerSet = registerSet; + this.log2m = log2m; + int m = 1 << this.log2m; + + alphaMM = getAlphaMM(log2m, m); + } + + @Override + public boolean offerHashed(long hashedValue) { + // j becomes the binary address determined by the first b log2m of x + // j will be between 0 and 2^log2m + final int j = (int) (hashedValue >>> (Long.SIZE - log2m)); + final int r = + Long.numberOfLeadingZeros((hashedValue << this.log2m) | (1 << (this.log2m - 1)) + 1) + 1; + return registerSet.updateIfGreater(j, r); + } + + @Override + public boolean offerHashed(int hashedValue) { + // j becomes the binary address determined by the first b log2m of x + // j will be between 0 and 2^log2m + final int j = hashedValue >>> (Integer.SIZE - log2m); + final int r = + Integer.numberOfLeadingZeros((hashedValue << this.log2m) | (1 << (this.log2m - 1)) + 1) + 1; + return registerSet.updateIfGreater(j, r); + } + + @Override + public boolean offer(Object o) { + final int x = MurmurHash.hash(o); + return offerHashed(x); + } + + + @Override + public long cardinality() { + double registerSum = 0; + int count = registerSet.count; + double zeros = 0.0; + for (int j = 0; j < registerSet.count; j++) { + int val = registerSet.get(j); + registerSum += 1.0 / (1 << val); + if (val == 0) { + zeros++; + } + } + + double estimate = alphaMM * (1 / registerSum); + + if (estimate <= (5.0 / 2.0) * count) { + // Small Range Estimate + return Math.round(linearCounting(count, zeros)); + } else { + return Math.round(estimate); + } + } + + @Override + public int sizeof() { + return registerSet.size * 4; + } + + @Override + public byte[] getBytes() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutput dos = new DataOutputStream(baos); + writeBytes(dos); + + return baos.toByteArray(); + } + + private void writeBytes(DataOutput serializedByteStream) throws IOException { + serializedByteStream.writeInt(log2m); + serializedByteStream.writeInt(registerSet.size * 4); + for (int x : registerSet.readOnlyBits()) { + serializedByteStream.writeInt(x); + } + } + + /** + * Add all the elements of the other set to this set. + * <p/> + * This operation does not imply a loss of precision. + * + * @param other A compatible Hyperloglog instance (same log2m) + * @throws CardinalityMergeException if other is not compatible + */ + public void addAll(HyperLogLog other) throws CardinalityMergeException { + if (this.sizeof() != other.sizeof()) { + throw new HyperLogLogMergeException("Cannot merge estimators of different sizes"); + } + + registerSet.merge(other.registerSet); + } + + @Override + public ICardinality merge(ICardinality... estimators) throws CardinalityMergeException { + HyperLogLog merged = new HyperLogLog(HllExecutor.DEFAULT_HLL_STD_DEV);// new HyperLogLog(log2m, + // new + // RegisterSet(this.registerSet.count)); + merged.addAll(this); + + if (estimators == null) { + return merged; + } + + for (ICardinality estimator : estimators) { + if (!(estimator instanceof HyperLogLog)) { + throw new HyperLogLogMergeException("Cannot merge estimators of different class"); + } + HyperLogLog hll = (HyperLogLog) estimator; + merged.addAll(hll); + } + + return merged; + } + + private Object writeReplace() { + return new SerializationHolder(this); + } + + /** + * This class exists to support Externalizable semantics for HyperLogLog objects without having to + * expose a public constructor, public write/read methods, or pretend final fields aren't final. + * + * In short, Externalizable allows you to skip some of the more verbose meta-data default + * Serializable gets you, but still includes the class name. In that sense, there is some cost to + * this holder object because it has a longer class name. I imagine people who care about + * optimizing for that have their own work-around for long class names in general, or just use a + * custom serialization framework. Therefore we make no attempt to optimize that here (eg. by + * raising this from an inner class and giving it an unhelpful name). + */ + private static class SerializationHolder implements Externalizable { + + HyperLogLog hyperLogLogHolder; + + public SerializationHolder(HyperLogLog hyperLogLogHolder) { + this.hyperLogLogHolder = hyperLogLogHolder; + } + + /** + * required for Externalizable + */ + @SuppressWarnings("unused") + public SerializationHolder() { + + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + hyperLogLogHolder.writeBytes(out); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + hyperLogLogHolder = Builder.build(in); + } + + private Object readResolve() { + return hyperLogLogHolder; + } + } + + public static class Builder implements IBuilder<ICardinality>, Serializable { + + private static final long serialVersionUID = -979314356097156719L; + private double rsd; + + public Builder(double rsd) { + this.rsd = rsd; + } + + @Override + public HyperLogLog build() { + return new HyperLogLog(rsd); + } + + @Override + public int sizeof() { + int log2m = log2m(rsd); + int k = 1 << log2m; + return RegisterSet.getBits(k) * 4; + } + + public static HyperLogLog build(byte[] bytes) throws IOException { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + return build(new DataInputStream(bais)); + } + + public static HyperLogLog build(DataInput serializedByteStream) throws IOException { + int log2m = serializedByteStream.readInt(); + int byteArraySize = serializedByteStream.readInt(); + return new HyperLogLog(log2m, + new RegisterSet(1 << log2m, Bits.getBits(serializedByteStream, byteArraySize))); + } + } + + @SuppressWarnings("serial") + protected static class HyperLogLogMergeException extends CardinalityMergeException { + + public HyperLogLogMergeException(String message) { + super(message); + } + } + + protected static double getAlphaMM(final int p, final int m) { + // See the paper. + switch (p) { + case 4: + return 0.673 * m * m; + case 5: + return 0.697 * m * m; + case 6: + return 0.709 * m * m; + default: + return (0.7213 / (1 + 1.079 / m)) * m * m; + } + } + + protected static double linearCounting(int m, double V) { + return m * Math.log(m / V); + } +}
