/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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 com.hazelcast.query.impl; import com.hazelcast.config.Config; import com.hazelcast.config.EvictionPolicy; import com.hazelcast.config.MapConfig; import com.hazelcast.config.MapIndexConfig; import com.hazelcast.config.MapStoreConfig; import com.hazelcast.config.MaxSizeConfig; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.core.MapLoader; import com.hazelcast.instance.Node; import com.hazelcast.map.impl.MapContainer; import com.hazelcast.map.impl.MapService; import com.hazelcast.map.impl.MapServiceContext; import com.hazelcast.nio.ObjectDataInput; import com.hazelcast.nio.ObjectDataOutput; import com.hazelcast.nio.serialization.DataSerializable; import com.hazelcast.query.EntryObject; import com.hazelcast.query.Predicate; import com.hazelcast.query.PredicateBuilder; import com.hazelcast.query.Predicates; import com.hazelcast.spi.properties.GroupProperty; import com.hazelcast.test.HazelcastParallelClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.annotation.ParallelTest; import com.hazelcast.test.annotation.QuickTest; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import static com.hazelcast.config.MaxSizeConfig.MaxSizePolicy.PER_PARTITION; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @RunWith(HazelcastParallelClassRunner.class) @Category({QuickTest.class, ParallelTest.class}) public class IndexIntegrationTest extends HazelcastTestSupport { @Test public void loadFromStore_whenEvicted() { // GIVEN String name = randomString(); String attributeName = "currency"; String currency = "dollar"; long amount = 5L; Config config = new Config(); config.setProperty(GroupProperty.PARTITION_COUNT.getName(), "1"); MapConfig mapConfig = config.getMapConfig(name); mapConfig.setEvictionPolicy(EvictionPolicy.LFU); mapConfig.setMinEvictionCheckMillis(0); // size=1 means each put/load will trigger eviction MaxSizeConfig maxSizeConfig = new MaxSizeConfig(1, PER_PARTITION); mapConfig.setMaxSizeConfig(maxSizeConfig); // Dummy map loader which returns a Trade object with amount=5, currency=dollar MapStoreConfig mapStoreConfig = mapConfig.getMapStoreConfig(); mapStoreConfig.setEnabled(true); mapStoreConfig.setImplementation(new DummyLoader(amount, currency)); HazelcastInstance instance = createHazelcastInstance(config); IMap<String, Trade> map = instance.getMap(name); map.addIndex(attributeName, false); // WHEN // This `get` will trigger load from map-loader but since eviction kicks in, entry will get removed // We should be able to get the value loaded from store but index should be removed Trade trade = map.get(randomString()); map.get(randomString()); // THEN assertEquals(1, map.size()); assertEquals(5L, (long) trade.amount); assertEquals(currency, trade.currency); Index index = getIndexOfAttributeForMap(instance, name, attributeName); Set<QueryableEntry> dollars = index.getRecords(currency); assertEquals(1, dollars.size()); } @Test public void putRemove_withIndex_whereAttributeIsNull() { // GIVEN MapIndexConfig mapIndexConfig = new MapIndexConfig(); mapIndexConfig.setAttribute("amount"); mapIndexConfig.setOrdered(false); MapConfig mapConfig = new MapConfig().setName("map"); mapConfig.addMapIndexConfig(mapIndexConfig); Config config = new Config(); config.addMapConfig(mapConfig); Trade trade = new Trade(); trade.setCurrency("EUR"); trade.setAmount(null); HazelcastInstance instance = createHazelcastInstance(config); IMap<Integer, Trade> map = instance.getMap("map"); // WHEN map.put(1, trade); map.remove(1); EntryObject e = new PredicateBuilder().getEntryObject(); Predicate predicate = e.get("amount").isNull(); Collection<Trade> values = map.values(predicate); // THEN assertEquals(0, values.size()); assertNull(map.get(1)); } @Test(timeout = 1000 * 60) public void putAndQuery_whenMultipleMappingFound_thenDoNotReturnDuplicatedEntry() { HazelcastInstance instance = createHazelcastInstance(); IMap<Integer, Body> map = instance.getMap(randomMapName()); map.addIndex("limbArray[any].fingerCount", true); Limb leftHand = new Limb("hand", new Nail("red")); Limb rightHand = new Limb("hand"); Body body = new Body("body", leftHand, rightHand); map.put(1, body); Predicate predicate = Predicates.greaterEqual("limbArray[any].fingerCount", 0); Collection<Body> values = map.values(predicate); assertThat(values, hasSize(1)); } @Test public void foo_methodGetters() { HazelcastInstance hazelcastInstance = createHazelcastInstance(); IMap<Integer, SillySequence> map = hazelcastInstance.getMap(randomName()); SillySequence sillySequence = new SillySequence(0, 100); map.put(0, sillySequence); Predicate predicate = Predicates.equal("payload[any]", 3); Collection<SillySequence> result = map.values(predicate); assertThat(result, hasSize(1)); } @Test public void foo_fieldGetters() { HazelcastInstance hazelcastInstance = createHazelcastInstance(); IMap<Integer, SillySequence> map = hazelcastInstance.getMap(randomName()); SillySequence sillySequence = new SillySequence(0, 100); map.put(0, sillySequence); Predicate predicate = Predicates.equal("payloadField[any]", 3); Collection<SillySequence> result = map.values(predicate); assertThat(result, hasSize(1)); } private static Index getIndexOfAttributeForMap(HazelcastInstance instance, String mapName, String attribute) { Node node = getNode(instance); MapService service = node.nodeEngine.getService(MapService.SERVICE_NAME); MapServiceContext mapServiceContext = service.getMapServiceContext(); MapContainer mapContainer = mapServiceContext.getMapContainer(mapName); Indexes indexes = mapContainer.getIndexes(); return indexes.getIndex(attribute); } static class Body implements Serializable { String name; Limb[] limbArray = new Limb[0]; Collection<Limb> limbCollection = new ArrayList<Limb>(); Body(String name, Limb... limbs) { this.name = name; this.limbCollection = Arrays.asList(limbs); this.limbArray = limbs; } } static class Limb implements Serializable { String name; Nail[] nailArray = new Nail[0]; int fingerCount; Collection<Nail> nailCollection = new ArrayList<Nail>(); Limb(String name, Nail... nails) { this.name = name; this.nailCollection = Arrays.asList(nails); this.fingerCount = nails.length; this.nailArray = nails; } } static class Nail implements Serializable { String colour; private Nail(String colour) { this.colour = colour; } } @SuppressWarnings("unused") public static class Trade implements Serializable { private String currency; private Long amount; public Trade() { } public String getCurrency() { return currency; } public void setCurrency(String currency) { this.currency = currency; } public Long getAmount() { return amount; } public void setAmount(Long amount) { this.amount = amount; } } @SuppressWarnings("unused") static class SillySequence implements DataSerializable { int count; Collection<Integer> payloadField; SillySequence() { } SillySequence(int from, int count) { this.count = count; this.payloadField = new ArrayList<Integer>(count); int to = from + count; for (int i = from; i < to; i++) { payloadField.add(i); } } public Collection<Integer> getPayload() { return payloadField; } public int getCount() { return count; } @Override public void writeData(ObjectDataOutput out) throws IOException { out.writeInt(count); out.writeObject(payloadField); } @Override public void readData(ObjectDataInput in) throws IOException { count = in.readInt(); payloadField = in.readObject(); } } static class DummyLoader implements MapLoader<String, Trade> { long amount; String currency; public DummyLoader(long amount, String currency) { this.amount = amount; this.currency = currency; } @Override public Trade load(String key) { Trade trade = new Trade(); trade.setAmount(amount); trade.setCurrency(currency); return trade; } @Override public Map<String, Trade> loadAll(Collection<String> keys) { Map<String, Trade> map = new HashMap<String, Trade>(); for (String key : keys) { map.put(key, load(key)); } return map; } @Override public Iterable<String> loadAllKeys() { return null; } } }