| /* |
| * 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.compatibility.persistence; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Random; |
| import java.util.Set; |
| import org.apache.ignite.Ignite; |
| import org.apache.ignite.IgniteCache; |
| import org.apache.ignite.cache.CacheAtomicityMode; |
| import org.apache.ignite.cache.query.SqlFieldsQuery; |
| import org.apache.ignite.cache.query.annotations.QuerySqlField; |
| import org.apache.ignite.cluster.ClusterState; |
| import org.apache.ignite.compatibility.testframework.junits.Dependency; |
| import org.apache.ignite.configuration.BinaryConfiguration; |
| import org.apache.ignite.configuration.CacheConfiguration; |
| import org.apache.ignite.configuration.DataRegionConfiguration; |
| import org.apache.ignite.configuration.DataStorageConfiguration; |
| import org.apache.ignite.configuration.IgniteConfiguration; |
| import org.apache.ignite.configuration.WALMode; |
| import org.apache.ignite.internal.IgniteEx; |
| import org.apache.ignite.lang.IgniteInClosure; |
| import org.jetbrains.annotations.NotNull; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| |
| /** |
| * Tests that upgrade version on persisted inline index is successfull. |
| */ |
| @RunWith(Parameterized.class) |
| public class InlineIndexCompatibilityTest extends IgnitePersistenceCompatibilityAbstractTest { |
| /** */ |
| private static final String TEST_CACHE_NAME = InlineIndexCompatibilityTest.class.getSimpleName(); |
| |
| /** */ |
| private static final int ROWS_CNT = 100; |
| |
| /** Index to test. */ |
| private static final String INDEX_NAME = "intval1_val_intval2"; |
| |
| /** Index to test with configured inline size. */ |
| private static final String INDEX_SIZED_NAME = "intval1_val_intval2_sized"; |
| |
| /** Parametrized run param: Ignite version. */ |
| @Parameterized.Parameter(0) |
| public String igniteVer; |
| |
| /** Parametrized run param: Inline size is configured by user. */ |
| @Parameterized.Parameter(1) |
| public boolean cfgInlineSize; |
| |
| /** Test run configurations: Ignite version, Inline size configuration. */ |
| @Parameterized.Parameters(name = "ver={0}, cfgInlineSize={1}") |
| public static Collection<Object[]> runConfig() { |
| return Arrays.asList(new Object[][] { |
| /** 2.6.0 is a last version where POJO inlining isn't enabled. */ |
| {"2.6.0", false}, |
| {"2.6.0", true}, |
| |
| {"2.7.0", false}, |
| {"2.7.0", true}, |
| |
| {"2.7.6", false}, |
| {"2.7.6", true}, |
| |
| {"2.8.0", false}, |
| {"2.8.0", true}, |
| |
| {"2.8.1", false}, |
| {"2.8.1", true}, |
| |
| {"2.9.0", false}, |
| {"2.9.0", true}, |
| |
| {"2.9.1", false}, |
| {"2.9.1", true} |
| }); |
| } |
| |
| /** */ |
| @Test |
| public void testQueryOldInlinedIndex() throws Exception { |
| PostStartupClosure closure = cfgInlineSize ? new PostStartupClosureSized() : new PostStartupClosure(); |
| String idxName = cfgInlineSize ? INDEX_SIZED_NAME : INDEX_NAME; |
| |
| doTestStartupWithOldVersion(igniteVer, closure, idxName); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override @NotNull protected Collection<Dependency> getDependencies(String igniteVer) { |
| Collection<Dependency> dependencies = super.getDependencies(igniteVer); |
| |
| if ("2.6.0".equals(igniteVer)) |
| dependencies.add(new Dependency("h2", "com.h2database", "h2", "1.4.195", false)); |
| |
| dependencies.add(new Dependency("indexing", "ignite-indexing", false)); |
| |
| return dependencies; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected Set<String> getExcluded(String ver, Collection<Dependency> dependencies) { |
| Set<String> excluded = super.getExcluded(ver, dependencies); |
| |
| if ("2.6.0".equals(ver)) |
| excluded.add("h2"); |
| |
| return excluded; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { |
| IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); |
| |
| cfg.setPeerClassLoadingEnabled(false); |
| |
| cfg.setDataStorageConfiguration( |
| new DataStorageConfiguration() |
| .setDefaultDataRegionConfiguration( |
| new DataRegionConfiguration() |
| .setPersistenceEnabled(true) |
| .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) |
| ) |
| // Disable WAL to skip filling index with reading WAL. Instead just start on previous persisted files. |
| .setWalMode(WALMode.NONE)); |
| |
| cfg.setBinaryConfiguration( |
| new BinaryConfiguration() |
| .setCompactFooter(true) |
| ); |
| |
| return cfg; |
| } |
| |
| /** |
| * Tests opportunity to read data from previous Ignite DB version. |
| * |
| * @param igniteVer 3-digits version of ignite |
| * @throws Exception If failed. |
| */ |
| protected void doTestStartupWithOldVersion(String igniteVer, PostStartupClosure closure, String idxName) throws Exception { |
| try { |
| startGrid(1, igniteVer, |
| new PersistenceBasicCompatibilityTest.ConfigurationClosure(true), |
| closure); |
| |
| stopAllGrids(); |
| |
| IgniteEx ignite = startGrid(0); |
| |
| assertEquals(1, ignite.context().discovery().topologyVersion()); |
| |
| ignite.cluster().state(ClusterState.ACTIVE); |
| |
| validateResultingCacheData(ignite.cache(TEST_CACHE_NAME), idxName); |
| } |
| finally { |
| stopAllGrids(); |
| } |
| } |
| |
| /** |
| * Asserts cache contained all expected values as it was saved before. |
| * |
| * @param cache Cache to check. |
| * @param idxName Name of index to check. |
| */ |
| private void validateResultingCacheData(IgniteCache<Object, Object> cache, String idxName) { |
| validateRandomRow(cache, idxName); |
| validateRandomRange(cache, idxName); |
| } |
| |
| /** */ |
| private void validateRandomRow(IgniteCache<Object, Object> cache, String idxName) { |
| int val = new Random().nextInt(ROWS_CNT); |
| |
| // Select by quering complex index. |
| SqlFieldsQuery qry = new SqlFieldsQuery( |
| "SELECT * FROM \"" + TEST_CACHE_NAME + "\".EntityValueValue v " + |
| "WHERE v.intVal1 = ? and v.val = ? and v.intVal2 = ?;") |
| .setArgs(val, new EntityValue(val + 2), val + 1); |
| |
| checkIndexUsed(cache, qry, idxName); |
| |
| List<List<?>> result = cache.query(qry).getAll(); |
| |
| assertTrue(result.size() == 1); |
| |
| List<?> row = result.get(0); |
| |
| assertTrue(row.get(0).equals(new EntityValue(val + 2))); |
| assertTrue(row.get(1).equals(val)); |
| assertTrue(row.get(2).equals(val + 1)); |
| } |
| |
| /** */ |
| private void validateRandomRange(IgniteCache<Object, Object> cache, String idxName) { |
| int pivot = new Random().nextInt(ROWS_CNT); |
| |
| // Select by quering complex index. |
| SqlFieldsQuery qry = new SqlFieldsQuery( |
| "SELECT * FROM \"" + TEST_CACHE_NAME + "\".EntityValueValue v " + |
| "WHERE v.intVal1 > ? and v.val > ? and v.intVal2 > ? " + |
| "ORDER BY v.val, v.intVal1, v.intVal2;") |
| .setArgs(pivot, new EntityValue(pivot), pivot); |
| |
| checkIndexUsed(cache, qry, idxName); |
| |
| List<List<?>> result = cache.query(qry).getAll(); |
| |
| // For strict comparison. There was an issues with >= comparison for some versions. |
| pivot += 1; |
| |
| assertTrue(result.size() == ROWS_CNT - pivot); |
| |
| for (int i = 0; i < ROWS_CNT - pivot; i++) { |
| List<?> row = result.get(i); |
| |
| assertTrue(row.get(0).equals(new EntityValue(pivot + i + 2))); |
| assertTrue(row.get(1).equals(pivot + i)); |
| assertTrue(row.get(2).equals(pivot + i + 1)); |
| } |
| } |
| |
| /** */ |
| private void checkIndexUsed(IgniteCache<?, ?> cache, SqlFieldsQuery qry, String idxName) { |
| assertTrue("Query does not use index.", queryPlan(cache, qry).toLowerCase().contains(idxName.toLowerCase())); |
| } |
| |
| /** */ |
| public static class PostStartupClosure implements IgniteInClosure<Ignite> { |
| /** {@inheritDoc} */ |
| @Override public void apply(Ignite ignite) { |
| ignite.active(true); |
| |
| CacheConfiguration<Object, Object> cacheCfg = new CacheConfiguration<>(); |
| cacheCfg.setName(TEST_CACHE_NAME); |
| cacheCfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL); |
| cacheCfg.setBackups(1); |
| |
| cacheCfg.setIndexedTypes(Integer.class, EntityValueValue.class); |
| |
| IgniteCache<Object, Object> cache = ignite.createCache(cacheCfg); |
| |
| saveCacheData(cache); |
| |
| ignite.active(false); |
| |
| try { |
| Thread.sleep(1_000); |
| } |
| catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Create a complex index (int, pojo, int). Check that middle POJO object is correctly available from inline. |
| * |
| * @param cache to be filled with data. Results may be validated in {@link #validateResultingCacheData(IgniteCache, String)}. |
| */ |
| protected void saveCacheData(IgniteCache<Object, Object> cache) { |
| for (int i = 0; i < ROWS_CNT; i++) |
| cache.put(i, new EntityValueValue(new EntityValue(i + 2), i, i + 1)); |
| |
| // Create index (int, pojo, int). |
| cache.query(new SqlFieldsQuery( |
| "CREATE INDEX " + INDEX_NAME + " ON \"" + TEST_CACHE_NAME + "\".EntityValueValue " + |
| "(intVal1, val, intVal2)")).getAll(); |
| } |
| } |
| |
| /** */ |
| public static class PostStartupClosureSized extends PostStartupClosure { |
| /** {@inheritDoc} */ |
| @Override protected void saveCacheData(IgniteCache<Object, Object> cache) { |
| for (int i = 0; i < ROWS_CNT; i++) |
| cache.put(i, new EntityValueValue(new EntityValue(i + 2), i, i + 1)); |
| |
| // Create index (int, pojo, int) with configured inline size. |
| cache.query(new SqlFieldsQuery( |
| "CREATE INDEX " + INDEX_SIZED_NAME + " ON \"" + TEST_CACHE_NAME + "\".EntityValueValue " + |
| "(intVal1, val, intVal2) " + |
| "INLINE_SIZE 100")).getAll(); |
| } |
| } |
| |
| /** POJO object aimed to be inlined. */ |
| public static class EntityValue { |
| /** */ |
| private final int val; |
| |
| /** */ |
| public EntityValue(int val) { |
| this.val = val; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return "EV[value=" + val + "]"; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int hashCode() { |
| return val; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean equals(Object other) { |
| return val == ((EntityValue) other).val; |
| } |
| } |
| |
| /** Represents a cache value with 3 fields (POJO, int, int). */ |
| public static class EntityValueValue { |
| /** */ |
| @QuerySqlField |
| private final EntityValue val; |
| |
| /** */ |
| @QuerySqlField |
| private final int intVal1; |
| |
| /** */ |
| @QuerySqlField |
| private final int intVal2; |
| |
| /** */ |
| public EntityValueValue(EntityValue val, int val1, int val2) { |
| this.val = val; |
| intVal1 = val1; |
| intVal2 = val2; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return "EVV[value=" + val + "]"; |
| } |
| } |
| } |