| /* |
| * 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 java.util.Collections.emptySet; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import org.apache.geode.annotations.VisibleForTesting; |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.redis.internal.executor.set.RedisSetCommands; |
| import org.apache.geode.redis.internal.executor.set.RedisSetCommandsFunctionInvoker; |
| |
| class NullRedisSet extends RedisSet { |
| |
| NullRedisSet() { |
| super(new HashSet<>()); |
| } |
| |
| @Override |
| public boolean isNull() { |
| return true; |
| } |
| |
| @Override |
| Collection<ByteArrayWrapper> spop(Region<ByteArrayWrapper, RedisData> region, |
| ByteArrayWrapper key, int popCount) { |
| return emptyList(); |
| } |
| |
| @Override |
| Collection<ByteArrayWrapper> srandmember(int count) { |
| return emptyList(); |
| } |
| |
| @Override |
| public boolean sismember(ByteArrayWrapper member) { |
| return false; |
| } |
| |
| @Override |
| public int scard() { |
| return 0; |
| } |
| |
| @Override |
| long sadd(ArrayList<ByteArrayWrapper> membersToAdd, |
| Region<ByteArrayWrapper, RedisData> region, ByteArrayWrapper key) { |
| region.create(key, new RedisSet(membersToAdd)); |
| return membersToAdd.size(); |
| } |
| |
| @Override |
| long srem(ArrayList<ByteArrayWrapper> membersToRemove, |
| Region<ByteArrayWrapper, RedisData> region, ByteArrayWrapper key) { |
| return 0; |
| } |
| |
| @Override |
| @VisibleForTesting |
| Set<ByteArrayWrapper> smembers() { |
| // some callers want to be able to modify the set returned |
| return new HashSet<>(); |
| } |
| |
| private enum SetOp { |
| UNION, INTERSECTION, DIFF |
| } |
| |
| public int sunionstore(CommandHelper helper, ByteArrayWrapper destination, |
| ArrayList<ByteArrayWrapper> setKeys) { |
| return doSetOp(SetOp.UNION, helper, destination, setKeys); |
| } |
| |
| public int sinterstore(CommandHelper helper, ByteArrayWrapper destination, |
| ArrayList<ByteArrayWrapper> setKeys) { |
| return doSetOp(SetOp.INTERSECTION, helper, destination, setKeys); |
| } |
| |
| public int sdiffstore(CommandHelper helper, ByteArrayWrapper destination, |
| ArrayList<ByteArrayWrapper> setKeys) { |
| return doSetOp(SetOp.DIFF, helper, destination, setKeys); |
| } |
| |
| private int doSetOp(SetOp setOp, CommandHelper helper, |
| ByteArrayWrapper destination, ArrayList<ByteArrayWrapper> setKeys) { |
| ArrayList<Set<ByteArrayWrapper>> nonDestinationSets = |
| fetchSets(helper.getRegion(), setKeys, destination); |
| return helper.getStripedExecutor() |
| .execute(destination, |
| () -> doSetOpWhileLocked(setOp, helper, destination, nonDestinationSets)); |
| } |
| |
| private int doSetOpWhileLocked(SetOp setOp, CommandHelper helper, |
| ByteArrayWrapper destination, |
| ArrayList<Set<ByteArrayWrapper>> nonDestinationSets) { |
| Set<ByteArrayWrapper> result = computeSetOp(setOp, nonDestinationSets, helper, destination); |
| if (result.isEmpty()) { |
| helper.getRegion().remove(destination); |
| return 0; |
| } else { |
| helper.getRegion().put(destination, new RedisSet(result)); |
| return result.size(); |
| } |
| } |
| |
| private Set<ByteArrayWrapper> computeSetOp(SetOp setOp, |
| ArrayList<Set<ByteArrayWrapper>> nonDestinationSets, |
| CommandHelper helper, |
| ByteArrayWrapper destination) { |
| Set<ByteArrayWrapper> result = null; |
| if (nonDestinationSets.isEmpty()) { |
| return emptySet(); |
| } |
| for (Set<ByteArrayWrapper> set : nonDestinationSets) { |
| if (set == null) { |
| set = helper.getRedisSet(destination).smembers(); |
| } |
| if (result == null) { |
| result = set; |
| } else { |
| switch (setOp) { |
| case UNION: |
| result.addAll(set); |
| break; |
| case INTERSECTION: |
| result.retainAll(set); |
| break; |
| case DIFF: |
| result.removeAll(set); |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Gets the set data for the given keys, excluding the destination if it was in setKeys. |
| * The result will have an element for each corresponding key and a null element if |
| * the corresponding key is the destination. |
| * This is all done outside the striped executor to prevent a deadlock. |
| */ |
| private ArrayList<Set<ByteArrayWrapper>> fetchSets( |
| Region<ByteArrayWrapper, RedisData> region, |
| ArrayList<ByteArrayWrapper> setKeys, |
| ByteArrayWrapper destination) { |
| ArrayList<Set<ByteArrayWrapper>> result = new ArrayList<>(setKeys.size()); |
| RedisSetCommands redisSetCommands = new RedisSetCommandsFunctionInvoker(region); |
| for (ByteArrayWrapper key : setKeys) { |
| if (key.equals(destination)) { |
| result.add(null); |
| } else { |
| result.add(redisSetCommands.smembers(key)); |
| } |
| } |
| return result; |
| } |
| } |