/* * 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; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.atomic.AtomicInteger; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.cache.QueryEntity; import org.apache.ignite.cache.QueryIndex; import org.apache.ignite.cache.affinity.Affinity; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.processors.query.h2.sql.AbstractH2CompareQueryTest; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; import static org.apache.ignite.cache.CacheMode.REPLICATED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; /** * */ public class IgniteCacheJoinPartitionedAndReplicatedCollocationTest extends AbstractH2CompareQueryTest { /** */ private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); /** */ private static final String PERSON_CACHE = "person"; /** */ private static final String ACCOUNT_CACHE = "acc"; /** */ private boolean client; /** */ private boolean h2DataInserted; /** {@inheritDoc} */ @Override protected void createCaches() { } /** {@inheritDoc} */ @Override protected void initCacheAndDbData() throws Exception { // No-op. } /** {@inheritDoc} */ @Override protected void checkAllDataEquals() throws Exception { // No-op. } /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); TcpDiscoverySpi spi = ((TcpDiscoverySpi)cfg.getDiscoverySpi()); spi.setIpFinder(IP_FINDER); cfg.setClientMode(client); return cfg; } /** * @return Cache configuration. */ private CacheConfiguration personCache() { CacheConfiguration ccfg = configuration(PERSON_CACHE, 0); // Person cache is replicated. ccfg.setCacheMode(REPLICATED); QueryEntity entity = new QueryEntity(); entity.setKeyType(Integer.class.getName()); entity.setValueType(Person.class.getName()); entity.addQueryField("name", String.class.getName(), null); ccfg.setQueryEntities(F.asList(entity)); return ccfg; } /** * @param backups Number of backups. * @return Cache configuration. */ private CacheConfiguration accountCache(int backups) { CacheConfiguration ccfg = configuration(ACCOUNT_CACHE, backups); QueryEntity entity = new QueryEntity(); entity.setKeyType(Integer.class.getName()); entity.setValueType(Account.class.getName()); entity.addQueryField("personId", Integer.class.getName(), null); entity.addQueryField("name", String.class.getName(), null); entity.setIndexes(F.asList(new QueryIndex("personId"))); ccfg.setQueryEntities(F.asList(entity)); return ccfg; } /** * @param name Cache name. * @param backups Number of backups. * @return Cache configuration. */ private CacheConfiguration configuration(String name, int backups) { CacheConfiguration ccfg = new CacheConfiguration(DEFAULT_CACHE_NAME); ccfg.setName(name); ccfg.setWriteSynchronizationMode(FULL_SYNC); ccfg.setAtomicityMode(ATOMIC); ccfg.setBackups(backups); return ccfg; } /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { super.beforeTestsStarted(); client = true; startGrid(SRVS); } /** {@inheritDoc} */ @Override protected void afterTestsStopped() throws Exception { stopAllGrids(); super.afterTestsStopped(); } /** {@inheritDoc} */ @Override protected Statement initializeH2Schema() throws SQLException { Statement st = super.initializeH2Schema(); st.execute("CREATE SCHEMA \"person\""); st.execute("CREATE SCHEMA \"acc\""); st.execute("create table \"person\".PERSON" + " (_key int not null," + " _val other not null," + " name varchar(255))"); st.execute("create table \"acc\".ACCOUNT" + " (_key int not null," + " _val other not null," + " personId int," + " name varchar(255))"); return st; } /** * @throws Exception If failed. */ public void testJoin() throws Exception { Ignite client = grid(SRVS); client.createCache(personCache()); checkJoin(0); h2DataInserted = true; checkJoin(1); checkJoin(2); } /** * @param accBackups Account cache backups. * @throws Exception If failed. */ private void checkJoin(int accBackups) throws Exception { Ignite client = grid(SRVS); IgniteCache<Object, Object> personCache = client.cache(PERSON_CACHE); Affinity<Object> aff = client.affinity(PERSON_CACHE); AtomicInteger pKey = new AtomicInteger(100_000); AtomicInteger accKey = new AtomicInteger(); ClusterNode node0 = ignite(0).cluster().localNode(); ClusterNode node1 = ignite(1).cluster().localNode(); try { IgniteCache<Object, Object> accCache = client.createCache(accountCache(accBackups)); Integer pKey1 = keyForNode(aff, pKey, node0); // No accounts. insert(personCache, pKey1, new Person("p1")); Integer pKey2 = keyForNode(aff, pKey, node0); // 1 collocated account. insert(personCache, pKey2, new Person("p2")); insert(accCache, keyForNode(aff, accKey, node0), new Account(pKey2, "a-p2")); Integer pKey3 = keyForNode(aff, pKey, node0); // 1 non-collocated account. insert(personCache, pKey3, new Person("p3")); insert(accCache, keyForNode(aff, accKey, node1), new Account(pKey3, "a-p3")); Integer pKey4 = keyForNode(aff, pKey, node0); // 1 collocated, 1 non-collocated account. insert(personCache, pKey4, new Person("p4")); insert(accCache, keyForNode(aff, accKey, node0), new Account(pKey4, "a-p4-1")); insert(accCache, keyForNode(aff, accKey, node1), new Account(pKey4, "a-p4-2")); Integer pKey5 = keyForNode(aff, pKey, node0); // 2 collocated accounts. insert(personCache, pKey5, new Person("p5")); insert(accCache, keyForNode(aff, accKey, node0), new Account(pKey5, "a-p5-1")); insert(accCache, keyForNode(aff, accKey, node0), new Account(pKey5, "a-p5-1")); Integer pKey6 = keyForNode(aff, pKey, node0); // 2 non-collocated accounts. insert(personCache, pKey6, new Person("p6")); insert(accCache, keyForNode(aff, accKey, node1), new Account(pKey6, "a-p5-1")); insert(accCache, keyForNode(aff, accKey, node1), new Account(pKey6, "a-p5-1")); Integer[] keys = {pKey1, pKey2, pKey3, pKey4, pKey5, pKey6}; for (int i = 0; i < keys.length; i++) { log.info("Test key: " + i); Integer key = keys[i]; checkQuery("select p._key, p.name, a.name " + "from \"person\".Person p, \"acc\".Account a " + "where p._key = a.personId and p._key=?", accCache, true, key); checkQuery("select p._key, p.name, a.name " + "from \"acc\".Account a, \"person\".Person p " + "where p._key = a.personId and p._key=?", accCache, true, key); checkQuery("select p._key, p.name, a.name " + "from \"person\".Person p right outer join \"acc\".Account a " + "on (p._key = a.personId) and p._key=?", accCache, true, key); checkQuery("select p._key, p.name, a.name " + "from \"acc\".Account a left outer join \"person\".Person p " + "on (p._key = a.personId) and p._key=?", accCache, true, key); // checkQuery("select p._key, p.name, a.name " + // "from \"acc\".Account a right outer join \"person\".Person p " + // "on (p._key = a.personId) and p._key=?", accCache, true, key); // // checkQuery("select p._key, p.name, a.name " + // "from \"person\".Person p left outer join \"acc\".Account a " + // "on (p._key = a.personId) and p._key=?", accCache, true, key); } } finally { client.destroyCache(ACCOUNT_CACHE); personCache.removeAll(); } } /** * @param cache Cache. * @param key Key. * @param p Person. * @throws Exception If failed. */ private void insert(IgniteCache<Object, Object> cache, int key, Person p) throws Exception { cache.put(key, p); if (h2DataInserted) return; try(PreparedStatement st = conn.prepareStatement("insert into \"person\".PERSON " + "(_key, _val, name) values(?, ?, ?)")) { st.setObject(1, key); st.setObject(2, p); st.setObject(3, p.name); st.executeUpdate(); } } /** * @param cache Cache. * @param key Key. * @param a Account. * @throws Exception If failed. */ private void insert(IgniteCache<Object, Object> cache, int key, Account a) throws Exception { cache.put(key, a); if (h2DataInserted) return; try(PreparedStatement st = conn.prepareStatement("insert into \"acc\".ACCOUNT " + "(_key, _val, personId, name) values(?, ?, ?, ?)")) { st.setObject(1, key); st.setObject(2, a); st.setObject(3, a.personId); st.setObject(4, a.name); st.executeUpdate(); } } /** * @param sql SQL. * @param cache Cache. * @param enforceJoinOrder Enforce join order flag. * @param args Arguments. * @throws Exception If failed. */ private void checkQuery(String sql, IgniteCache<Object, Object> cache, boolean enforceJoinOrder, Object... args) throws Exception { String plan = (String)cache.query(new SqlFieldsQuery("explain " + sql) .setArgs(args) .setDistributedJoins(true) .setEnforceJoinOrder(enforceJoinOrder)) .getAll().get(0).get(0); log.info("Plan: " + plan); compareQueryRes0(cache, sql, true, enforceJoinOrder, args, Ordering.RANDOM); } /** * */ private static class Account implements Serializable { /** */ int personId; /** */ String name; /** * @param personId Person ID. * @param name Name. */ public Account(int personId, String name) { this.personId = personId; this.name = name; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(Account.class, this); } } /** * */ private static class Person implements Serializable { /** */ String name; /** * @param name Name. */ public Person(String name) { this.name = name; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(Person.class, this); } } }