/* * 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.ignite.internal.processors.cache.index; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.Ignition; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.cache.CacheWriteSynchronizationMode; import org.apache.ignite.cache.QueryEntity; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.apache.ignite.cache.query.SqlQuery; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.configuration.MemoryConfiguration; import org.apache.ignite.configuration.MemoryPolicyConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import javax.cache.Cache; import java.io.Serializable; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; /** * Tests for dynamic index creation. */ @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) public abstract class DynamicIndexAbstractSelfTest extends AbstractSchemaSelfTest { /** Attribute to filter node out of cache data nodes. */ protected static final String ATTR_FILTERED = "FILTERED"; /** Key range limit for "before" step. */ protected static final int KEY_BEFORE = 100; /** Key range limit for "after" step. */ protected static final int KEY_AFTER = 200; /** SQL to check index on the field 1. */ protected static final String SQL_SIMPLE_FIELD_1 = "SELECT * FROM " + TBL_NAME + " WHERE " + FIELD_NAME_1 + " >= ?"; /** SQL to check composite index */ protected static final String SQL_COMPOSITE = "SELECT * FROM " + TBL_NAME + " WHERE " + FIELD_NAME_1 + " >= ? AND " + alias(FIELD_NAME_2) + " >= ?"; /** SQL to check index on the field 2. */ protected static final String SQL_SIMPLE_FIELD_2 = "SELECT * FROM " + TBL_NAME + " WHERE " + alias(FIELD_NAME_2) + " >= ?"; /** Argument for simple SQL (1). */ protected static final int SQL_ARG_1 = 40; /** Argument for simple SQL (2). */ protected static final int SQL_ARG_2 = 80; /** {@inheritDoc} */ @Override protected void afterTestsStopped() throws Exception { stopAllGrids(); super.afterTestsStopped(); } /** * Create server configuration. * * @param idx Index. * @return Configuration. * @throws Exception If failed. */ protected IgniteConfiguration serverConfiguration(int idx) throws Exception { return serverConfiguration(idx, false); } /** * Create server configuration. * * @param idx Index. * @param filter Whether to filter the node out of cache. * @return Configuration. * @throws Exception If failed. */ protected IgniteConfiguration serverConfiguration(int idx, boolean filter) throws Exception { IgniteConfiguration cfg = commonConfiguration(idx); if (filter) cfg.setUserAttributes(Collections.singletonMap(ATTR_FILTERED, true)); return cfg; } /** * Create client configuration. * * @param idx Index. * @return Configuration. * @throws Exception If failed. */ protected IgniteConfiguration clientConfiguration(int idx) throws Exception { return commonConfiguration(idx).setClientMode(true); } /** * Create common node configuration. * * @param idx Index. * @return Configuration. * @throws Exception If failed. */ protected IgniteConfiguration commonConfiguration(int idx) throws Exception { IgniteConfiguration cfg = super.getConfiguration(getTestIgniteInstanceName(idx)); cfg.setDiscoverySpi(new TcpDiscoverySpi()); cfg.setMarshaller(new BinaryMarshaller()); MemoryConfiguration memCfg = new MemoryConfiguration() .setDefaultMemoryPolicyName("default") .setMemoryPolicies( new MemoryPolicyConfiguration() .setName("default") .setMaxSize(32 * 1024 * 1024L) .setInitialSize(32 * 1024 * 1024L) ); cfg.setMemoryConfiguration(memCfg); return optimize(cfg); } /** * @return Default cache configuration. */ protected CacheConfiguration<KeyClass, ValueClass> cacheConfiguration() { CacheConfiguration ccfg = new CacheConfiguration().setName(CACHE_NAME); QueryEntity entity = new QueryEntity(); entity.setKeyType(KeyClass.class.getName()); entity.setValueType(ValueClass.class.getName()); entity.setKeyFieldName(FIELD_KEY_ALIAS); entity.addQueryField(FIELD_KEY_ALIAS, entity.getKeyType(), null); entity.addQueryField(FIELD_KEY, Long.class.getName(), null); entity.addQueryField(FIELD_NAME_1, Long.class.getName(), null); entity.addQueryField(FIELD_NAME_2, Long.class.getName(), null); entity.setKeyFields(Collections.singleton(FIELD_KEY)); entity.setAliases(Collections.singletonMap(FIELD_NAME_2, alias(FIELD_NAME_2))); ccfg.setQueryEntities(Collections.singletonList(entity)); ccfg.setNodeFilter(new NodeFilter()); ccfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); ccfg.setBackups(1); return ccfg; } /** * Ensure index is used in plan. * * @param idxName Index name. * @param sql SQL. * @param args Arguments. */ protected static void assertIndexUsed(String idxName, String sql, Object... args) { for (Ignite node : Ignition.allGrids()) assertIndexUsed((IgniteEx)node, idxName, sql, args); } /** * Ensure index is used in plan. * * @param node Node. * @param idxName Index name. * @param sql SQL. * @param args Arguments. */ protected static void assertIndexUsed(IgniteEx node, String idxName, String sql, Object... args) { SqlFieldsQuery qry = new SqlFieldsQuery("EXPLAIN " + sql); if (args != null && args.length > 0) qry.setArgs(args); String plan = (String)node.cache(CACHE_NAME).query(qry).getAll().get(0).get(0); assertTrue("Index is not used: " + plan, plan.toLowerCase().contains(idxName.toLowerCase())); } /** * Ensure index is not used in plan. * * @param idxName Index name. * @param sql SQL. * @param args Arguments. */ protected static void assertIndexNotUsed(String idxName, String sql, Object... args) { for (Ignite node : Ignition.allGrids()) assertIndexNotUsed((IgniteEx)node, idxName, sql, args); } /** * Ensure index is not used in plan. * * @param node Node. * @param idxName Index name. * @param sql SQL. * @param args Arguments. */ protected static void assertIndexNotUsed(IgniteEx node, String idxName, String sql, Object... args) { SqlFieldsQuery qry = new SqlFieldsQuery("EXPLAIN " + sql); if (args != null && args.length > 0) qry.setArgs(args); String plan = (String)node.cache(CACHE_NAME).query(qry).getAll().get(0).get(0); assertFalse("Index is used: " + plan, plan.contains(idxName)); } /** * Create key object. * * @param ignite Ignite instance. * @param id ID. * @return Key object. */ protected static BinaryObject key(Ignite ignite, long id) { return ignite.binary().builder(KeyClass.class.getName()).setField(FIELD_KEY, id).build(); } /** * Create value object. * * @param ignite Ignite instance. * @param id ID. * @return Value object. */ protected static BinaryObject value(Ignite ignite, long id) { return ignite.binary().builder(ValueClass.class.getName()) .setField(FIELD_NAME_1, id) .setField(FIELD_NAME_2, id) .build(); } /** * Create key/value entry for the given key. * * @param ignite Ignite instance. * @param id ID. * @return Entry. */ protected static T2<BinaryObject, BinaryObject> entry(Ignite ignite, long id) { return new T2<>(key(ignite, id), value(ignite, id)); } /** * Get common cache. * * @param node Node. * @return Cache. */ protected static IgniteCache<BinaryObject, BinaryObject> cache(Ignite node) { return node.cache(CACHE_NAME).withKeepBinary(); } /** * Get key. * * @param node Node. * @param id ID. */ protected static BinaryObject get(Ignite node, int id) { BinaryObject key = key(node, id); return cache(node).get(key); } /** * Put key range. * * @param node Node. * @param from From key. * @param to To key. */ protected static void put(Ignite node, int from, int to) { try (IgniteDataStreamer streamer = node.dataStreamer(CACHE_NAME)) { streamer.allowOverwrite(true); streamer.keepBinary(true); for (int i = from; i < to; i++) { BinaryObject key = key(node, i); BinaryObject val = value(node, i); streamer.addData(key, val); } streamer.flush(); } } /** * Put key to cache. * * @param node Node. * @param id ID. */ protected static void put(Ignite node, long id) { BinaryObject key = key(node, id); BinaryObject val = value(node, id); cache(node).put(key, val); } /** * Remove key range. * * @param node Node. * @param from From key. * @param to To key. */ protected static void remove(Ignite node, int from, int to) { for (int i = from; i < to; i++) remove(node, i); } /** * Remove key form cache. * * @param node Node. * @param id ID. */ protected static void remove(Ignite node, long id) { BinaryObject key = key(node, id); cache(node).remove(key); } /** * @return Random string. */ protected static String randomString() { return "random" + UUID.randomUUID().toString().replace("-", ""); } /** * Assert SQL simple data state. * * @param sql SQL query. * @param expSize Expected size. */ protected static void assertSqlSimpleData(String sql, int expSize) { for (Ignite node : Ignition.allGrids()) assertSqlSimpleData(node, sql, expSize); } /** * Assert SQL simple data state. * * @param node Node. * @param sql SQL query. * @param expSize Expected size. */ protected static void assertSqlSimpleData(Ignite node, String sql, int expSize) { SqlQuery qry = new SqlQuery(tableName(ValueClass.class), sql).setArgs(SQL_ARG_1); List<Cache.Entry<BinaryObject, BinaryObject>> res = node.cache(CACHE_NAME).withKeepBinary().query(qry).getAll(); Set<Long> ids = new HashSet<>(); for (Cache.Entry<BinaryObject, BinaryObject> entry : res) { long id = entry.getKey().field(FIELD_KEY); long field1 = entry.getValue().field(FIELD_NAME_1); long field2 = entry.getValue().field(FIELD_NAME_2); assertTrue(field1 >= SQL_ARG_1); assertEquals(id, field1); assertEquals(id, field2); assertTrue(ids.add(id)); } assertEquals("Size mismatch [node=" + node.name() + ", exp=" + expSize + ", actual=" + res.size() + ", ids=" + ids + ']', expSize, res.size()); } /** * Assert SQL simple data state. * * @param node Node. * @param sql SQL query. * @param expSize Expected size. */ protected static void assertSqlCompositeData(Ignite node, String sql, int expSize) { SqlQuery qry = new SqlQuery(tableName(ValueClass.class), sql).setArgs(SQL_ARG_1, SQL_ARG_2); List<Cache.Entry<BinaryObject, BinaryObject>> res = node.cache(CACHE_NAME).withKeepBinary().query(qry).getAll(); Set<Long> ids = new HashSet<>(); for (Cache.Entry<BinaryObject, BinaryObject> entry : res) { long id = entry.getKey().field(FIELD_KEY); long field1 = entry.getValue().field(FIELD_NAME_1); long field2 = entry.getValue().field(FIELD_NAME_2); assertTrue(field1 >= SQL_ARG_2); assertEquals(id, field1); assertEquals(id, field2); assertTrue(ids.add(id)); } assertEquals("Size mismatch [exp=" + expSize + ", actual=" + res.size() + ", ids=" + ids + ']', expSize, res.size()); } /** * Node filter. */ protected static class NodeFilter implements IgnitePredicate<ClusterNode>, Serializable { /** */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ @Override public boolean apply(ClusterNode node) { return node.attribute(ATTR_FILTERED) == null; } } }