This is an automated email from the ASF dual-hosted git repository. ringles pushed a commit to branch GEODE-9892-Create-Infrastructure-for-Redis-Lists in repository https://gitbox.apache.org/repos/asf/geode.git
commit 90b39edc4638e267929f3d1f750eae8d56e074e8 Author: Ray Ingles <ring...@vmware.com> AuthorDate: Tue Jan 4 14:26:48 2022 -0500 Initial (broken) commit --- .../geode/redis/internal/data/NullRedisList.java | 76 ++++++ .../geode/redis/internal/data/RedisList.java | 297 +++++++++++++++++++++ .../serialization/DataSerializableFixedID.java | 2 +- 3 files changed, 374 insertions(+), 1 deletion(-) diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.java new file mode 100644 index 0000000..1e1f1cf --- /dev/null +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/NullRedisList.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.data; + +import static java.util.Collections.emptyList; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.geode.cache.Region; + +class NullRedisList extends RedisList { + + NullRedisList() { + super(new HashSet<>()); + } + + @Override + public boolean isNull() { + return true; + } + + @Override + public Collection<byte[]> spop(Region<RedisKey, RedisData> region, RedisKey key, int popCount) { + return emptyList(); + } + + @Override + public Collection<byte[]> srandmember(int count) { + return emptyList(); + } + + @Override + public boolean sismember(byte[] member) { + return false; + } + + @Override + public int scard() { + return 0; + } + + @Override + public long sadd(List<byte[]> membersToAdd, Region<RedisKey, RedisData> region, RedisKey key) { + region.create(key, new RedisSet(membersToAdd)); + return membersToAdd.size(); + } + + @Override + public long srem(List<byte[]> membersToRemove, Region<RedisKey, RedisData> region, RedisKey key) { + return 0; + } + + @Override + public Set<byte[]> smembers() { + // some callers want to be able to modify the set returned + return Collections.emptySet(); + } +} diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java new file mode 100644 index 0000000..6a3bfbd --- /dev/null +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisList.java @@ -0,0 +1,297 @@ +/* + * 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.data; + +import static java.util.Collections.emptyList; +import static org.apache.geode.internal.JvmSizeUtils.memoryOverhead; +import static org.apache.geode.redis.internal.data.RedisDataType.REDIS_SET; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.Set; + +import it.unimi.dsi.fastutil.bytes.ByteArrays; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import org.apache.geode.DataSerializer; +import org.apache.geode.annotations.VisibleForTesting; +import org.apache.geode.cache.Region; +import org.apache.geode.internal.serialization.DeserializationContext; +import org.apache.geode.internal.serialization.KnownVersion; +import org.apache.geode.internal.serialization.SerializationContext; +import org.apache.geode.redis.internal.commands.executor.GlobPattern; +import org.apache.geode.redis.internal.data.collections.SizeableObjectOpenCustomHashSet; +import org.apache.geode.redis.internal.data.delta.AddByteArrays; +import org.apache.geode.redis.internal.data.delta.RemoveByteArrays; +import org.apache.geode.redis.internal.data.delta.ReplaceByteArrays; +import org.apache.geode.redis.internal.services.RegionProvider; + +public class RedisList extends AbstractRedisData { + protected static final int REDIS_LIST_OVERHEAD = memoryOverhead(RedisList.class); + + private List elements; + + public RedisList(Collection<byte[]> elements) { + this.elements = new ArrayList(elements.size()); + for (byte[] member : elements) { + appendElement(member); + } + } + + public RedisList(List members) { + this.elements = members; + } + + public RedisList(int expectedSize) { + elements = new ArrayList(expectedSize); + } + + /** + * For deserialization only. + */ + public RedisList() {} + + /** + * @param elementsToAdd members to add to this set; NOTE this list may by + * modified by this call + * @param region the region this instance is stored in + * @param key the name of the set to add to + * @return the number of members actually added + */ + public long lpush(List<byte[]> elementsToAdd, Region<RedisKey, RedisData> region, RedisKey key) { + for (byte[] element: elementsToAdd) { + prependElement(element); + } + int elementsAdded = elementsToAdd.size(); + if (elementsAdded != 0) { + storeChanges(region, key, new AddByteArrays(elementsToAdd)); + } + return elements.size(); + } + + /** + * @param region the region this instance is stored in + * @param key the name of the set to add to + * @param popCount the number of elements to remove and return + * @return the element(s) actually popped + */ + public Collection<byte[]> lpop(Region<RedisKey, RedisData> region, RedisKey key, int popCount) { + int originalSize = elements.size(); + if (originalSize == 0) { + return emptyList(); + } + + if (popCount >= originalSize) { + region.remove(key, this); + return this.elements; + } + + List<byte[]> popped = new ArrayList<>(); + while (popped.size() < popCount) { + popped.add((byte[]) elements.remove(0)); + } + if (!popped.isEmpty()) { + storeChanges(region, key, new RemoveByteArrays(popped)); + } + return popped; + } + + public int llen() { + return elements.size(); + } + + @Override + public void applyAddByteArrayDelta(byte[] bytes) { + appendElement(bytes); + } + + @Override + public void applyRemoveByteArrayDelta(byte[] bytes) { + membersRemove(bytes); + } + + @Override + public void applyReplaceByteArraysDelta(MemberSet members) { + persistNoDelta(); + this.elements = members; + } + + /** + * Since GII (getInitialImage) can come in and call toData while other threads + * are modifying this object, the striped executor will not protect toData. + * So any methods that modify "members" needs to be thread safe with toData. + */ + + @Override + public synchronized void toData(DataOutput out, SerializationContext context) throws IOException { + super.toData(out, context); + DataSerializer.writePrimitiveInt(elements.size(), out); + for (byte[] member : elements) { + DataSerializer.writeByteArray(member, out); + } + } + + @Override + public void fromData(DataInput in, DeserializationContext context) + throws IOException, ClassNotFoundException { + super.fromData(in, context); + int size = DataSerializer.readPrimitiveInt(in); + elements = new MemberSet(size); + for (int i = 0; i < size; ++i) { + elements.add(DataSerializer.readByteArray(in)); + } + } + + @Override + public int getDSFID() { + return REDIS_LIST_ID; + } + + @VisibleForTesting + synchronized boolean appendElement(byte[] elementToAdd) { + return elements.add(elementToAdd); + } + + @VisibleForTesting + synchronized boolean prependElement(byte[] elementToAdd) { + elements.add(0, elementToAdd); + return true; + } + + @VisibleForTesting + synchronized boolean membersRemove(byte[] memberToRemove) { + return elements.remove(memberToRemove); + } + + public long sadd(List<byte[]> membersToAdd, Region<RedisKey, RedisData> region, RedisKey key) { + membersToAdd.removeIf(memberToAdd -> !appendElement(memberToAdd)); + int membersAdded = membersToAdd.size(); + if (membersAdded != 0) { + storeChanges(region, key, new AddByteArrays(membersToAdd)); + } + return membersAdded; + } + + /** + * @param membersToRemove members to remove from this set; NOTE this list may by + * modified by this call + * @param region the region this instance is stored in + * @param key the name of the set to remove from + * @return the number of members actually removed + */ + public long srem(List<byte[]> membersToRemove, Region<RedisKey, RedisData> region, RedisKey key) { + membersToRemove.removeIf(memberToRemove -> !membersRemove(memberToRemove)); + int membersRemoved = membersToRemove.size(); + if (membersRemoved != 0) { + storeChanges(region, key, new RemoveByteArrays(membersToRemove)); + } + return membersRemoved; + } + + /** + * The returned set is NOT a copy and will be changed + * by future changes to this instance. + * + * @return a set containing all the members in this set + */ + public Set<byte[]> smembers() { + return Collections.unmodifiableSet(elements); + } + + @Override + public RedisDataType getType() { + return REDIS_SET; + } + + @Override + protected boolean removeFromRegion() { + return elements.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RedisList)) { + return false; + } + if (!super.equals(o)) { + return false; + } + RedisList redisSet = (RedisList) o; + + if (redisSet.elements.size() != elements.size()) { + return false; + } + for (byte[] member : elements) { + if (!redisSet.elements.contains(member)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), elements); + } + + @Override + public String toString() { + return "RedisSet{" + super.toString() + ", " + "size=" + elements.size() + '}'; + } + + @Override + public KnownVersion[] getSerializationVersions() { + return null; + } + + @Override + public int getSizeInBytes() { + return REDIS_LIST_OVERHEAD + elements.getSizeInBytes(); + } + + public static class MemberSet extends SizeableObjectOpenCustomHashSet<byte[]> { + public MemberSet() { + super(ByteArrays.HASH_STRATEGY); + } + + public MemberSet(int size) { + super(size, ByteArrays.HASH_STRATEGY); + } + + public MemberSet(Collection<byte[]> initialElements) { + super(initialElements, ByteArrays.HASH_STRATEGY); + } + + @Override + protected int sizeElement(byte[] element) { + return memoryOverhead(element); + } + } + +} diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/DataSerializableFixedID.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/DataSerializableFixedID.java index 73fbcad..2e29996 100644 --- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/DataSerializableFixedID.java +++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/DataSerializableFixedID.java @@ -690,7 +690,7 @@ public interface DataSerializableFixedID extends SerializationVersions, BasicSer short REDIS_STRING_ID = 2187; short REDIS_HASH_ID = 2188; short REDIS_NULL_DATA_ID = 2189; - // 2190 unused + short REDIS_LIST_ID = 2190; short REDIS_MEMBER_INFO_ID = 2191; short REDIS_SORTED_SET_ID = 2192; // NOTE, codes > 65535 will take 4 bytes to serialize