| /* |
| * 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.Ignition; |
| import org.apache.ignite.cache.QueryEntity; |
| import org.apache.ignite.cache.QueryIndex; |
| import org.apache.ignite.cache.QueryIndexType; |
| import org.apache.ignite.cache.query.annotations.QuerySqlField; |
| import org.apache.ignite.cluster.ClusterNode; |
| import org.apache.ignite.internal.IgniteEx; |
| import org.apache.ignite.internal.IgniteInterruptedCheckedException; |
| import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor; |
| import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor; |
| import org.apache.ignite.internal.processors.query.GridQueryProcessor; |
| import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; |
| import org.apache.ignite.internal.processors.query.QueryIndexDescriptorImpl; |
| import org.apache.ignite.internal.processors.query.QueryTypeDescriptorImpl; |
| import org.apache.ignite.internal.processors.query.QueryUtils; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.lang.IgniteBiTuple; |
| import org.apache.ignite.lang.IgnitePredicate; |
| import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Tests for dynamic schema changes. |
| */ |
| @SuppressWarnings("unchecked") |
| public class AbstractSchemaSelfTest extends GridCommonAbstractTest { |
| /** Cache. */ |
| protected static final String CACHE_NAME = "cache"; |
| |
| /** Table name. */ |
| protected static final String TBL_NAME = tableName(ValueClass.class); |
| |
| /** Table name 2. */ |
| protected static final String TBL_NAME_2 = tableName(ValueClass2.class); |
| |
| /** Index name 1. */ |
| protected static final String IDX_NAME_1 = "idx_1"; |
| |
| /** Index name 2. */ |
| protected static final String IDX_NAME_2 = "idx_2"; |
| |
| /** Index name 3. */ |
| protected static final String IDX_NAME_3 = "idx_3"; |
| |
| /** Key ID field. */ |
| protected static final String FIELD_KEY = "id"; |
| |
| /** Field 1. */ |
| protected static final String FIELD_NAME_1 = "field1"; |
| |
| /** Field 1. */ |
| protected static final String FIELD_NAME_2 = "field2"; |
| |
| /** Field 3. */ |
| protected static final String FIELD_NAME_3 = "field3"; |
| |
| /** Key alias */ |
| protected static final String FIELD_KEY_ALIAS = "key"; |
| |
| /** |
| * Get type on the given node for the given cache and table name. Type must exist. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @return Type. |
| */ |
| protected static QueryTypeDescriptorImpl typeExisting(IgniteEx node, String cacheName, String tblName) { |
| QueryTypeDescriptorImpl res = type(node, cacheName, tblName); |
| |
| assertNotNull(res); |
| |
| return res; |
| } |
| |
| /** |
| * Get type on the given node for the given cache and table name. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @return Type. |
| */ |
| @Nullable protected static QueryTypeDescriptorImpl type(IgniteEx node, String cacheName, String tblName) { |
| return types(node, cacheName).get(tblName); |
| } |
| |
| /** |
| * Get available types on the given node for the given cache. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @return Map from table name to type. |
| */ |
| protected static Map<String, QueryTypeDescriptorImpl> types(IgniteEx node, String cacheName) { |
| Map<String, QueryTypeDescriptorImpl> res = new HashMap<>(); |
| |
| Collection<GridQueryTypeDescriptor> descs = node.context().query().types(cacheName); |
| |
| for (GridQueryTypeDescriptor desc : descs) { |
| QueryTypeDescriptorImpl desc0 = (QueryTypeDescriptorImpl)desc; |
| |
| res.put(desc0.tableName(), desc0); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Assert index state on all <b>affinity</b> nodes. |
| * |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| * @param fields Fields. |
| */ |
| protected static void assertIndex(String cacheName, String tblName, String idxName, |
| IgniteBiTuple<String, Boolean>... fields) { |
| assertIndex(cacheName, false, tblName, idxName, fields); |
| } |
| |
| /** |
| * Assert index state on all nodes. |
| * |
| * @param cacheName Cache name. |
| * @param checkNonAffinityNodes Whether existence of {@link GridQueryIndexDescriptor} must be checked on non |
| * affinity nodes as well. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| * @param fields Fields. |
| */ |
| protected static void assertIndex(String cacheName, boolean checkNonAffinityNodes, String tblName, String idxName, |
| IgniteBiTuple<String, Boolean>... fields) { |
| for (Ignite node : Ignition.allGrids()) |
| assertIndex(node, checkNonAffinityNodes, cacheName, tblName, idxName, fields); |
| } |
| |
| /** |
| * Assert index state on particular node. |
| * |
| * @param node Node. |
| * @param checkNonAffinityNode Whether existence of {@link GridQueryIndexDescriptor} must be checked regardless of |
| * whether this node is affinity node or not. |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| * @param fields Fields. |
| */ |
| protected static void assertIndex(Ignite node, boolean checkNonAffinityNode, String cacheName, String tblName, |
| String idxName, IgniteBiTuple<String, Boolean>... fields) { |
| IgniteEx node0 = (IgniteEx)node; |
| |
| assertIndexDescriptor(node0, cacheName, tblName, idxName, fields); |
| |
| if (checkNonAffinityNode || affinityNode(node0, cacheName)) { |
| QueryTypeDescriptorImpl typeDesc = typeExisting(node0, cacheName, tblName); |
| |
| assertIndex(typeDesc, idxName, fields); |
| } |
| } |
| |
| /** |
| * Make sure index exists in cache descriptor. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| * @param fields Fields. |
| */ |
| protected static void assertIndexDescriptor(IgniteEx node, String cacheName, String tblName, String idxName, |
| IgniteBiTuple<String, Boolean>... fields) { |
| awaitCompletion(); |
| |
| DynamicCacheDescriptor desc = node.context().cache().cacheDescriptor(cacheName); |
| |
| assert desc != null; |
| |
| for (QueryEntity entity : desc.schema().entities()) { |
| if (F.eq(tblName, QueryUtils.tableName(entity))) { |
| for (QueryIndex idx : entity.getIndexes()) { |
| if (F.eq(QueryUtils.indexName(entity, idx), idxName)) { |
| LinkedHashMap<String, Boolean> idxFields = idx.getFields(); |
| |
| assertEquals(idxFields.size(), fields.length); |
| |
| int i = 0; |
| |
| for (String idxField : idxFields.keySet()) { |
| assertEquals(idxField, fields[i].get1()); |
| assertEquals(idxFields.get(idxField), fields[i].get2()); |
| |
| i++; |
| } |
| |
| return; |
| } |
| } |
| } |
| } |
| |
| fail("Index not found [node=" + node.name() + ", cacheName=" + cacheName + ", tlbName=" + tblName + |
| ", idxName=" + idxName + ']'); |
| } |
| |
| /** |
| * Assert index state. |
| * |
| * @param typeDesc Type descriptor. |
| * @param idxName Index name. |
| * @param fields Fields (order is important). |
| */ |
| protected static void assertIndex(QueryTypeDescriptorImpl typeDesc, String idxName, |
| IgniteBiTuple<String, Boolean>... fields) { |
| QueryIndexDescriptorImpl idxDesc = typeDesc.index(idxName); |
| |
| assertNotNull(idxDesc); |
| |
| assertEquals(idxName, idxDesc.name()); |
| assertEquals(typeDesc, idxDesc.typeDescriptor()); |
| assertEquals(QueryIndexType.SORTED, idxDesc.type()); |
| |
| List<String> fieldNames = new ArrayList<>(idxDesc.fields()); |
| |
| assertEquals(fields.length, fieldNames.size()); |
| |
| for (int i = 0; i < fields.length; i++) { |
| String expFieldName = fields[i].get1(); |
| boolean expFieldAsc = fields[i].get2(); |
| |
| assertEquals("Index field mismatch [pos=" + i + ", expField=" + expFieldName + |
| ", actualField=" + fieldNames.get(i) + ']', expFieldName, fieldNames.get(i)); |
| |
| boolean fieldAsc = !idxDesc.descending(expFieldName); |
| |
| assertEquals("Index field sort mismatch [pos=" + i + ", field=" + expFieldName + |
| ", expAsc=" + expFieldAsc + ", actualAsc=" + fieldAsc + ']', expFieldAsc, fieldAsc); |
| } |
| } |
| |
| /** |
| * Assert index doesn't exist on all nodes. |
| * |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| */ |
| protected static void assertNoIndex(String cacheName, String tblName, String idxName) { |
| for (Ignite node : Ignition.allGrids()) |
| assertNoIndex((IgniteEx)node, cacheName, tblName, idxName); |
| } |
| |
| /** |
| * Assert index doesn't exist on particular node. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @param tblName Table name. |
| * @param idxName Index name. |
| */ |
| protected static void assertNoIndex(Ignite node, String cacheName, String tblName, String idxName) { |
| IgniteEx node0 = (IgniteEx)node; |
| |
| assertNoIndexDescriptor(node0, cacheName, idxName); |
| |
| if (affinityNode(node0, cacheName)) { |
| QueryTypeDescriptorImpl typeDesc = typeExisting(node0, cacheName, tblName); |
| |
| assertNoIndex(typeDesc, idxName); |
| } |
| } |
| |
| /** |
| * Assert index doesn't exist in particular node's cache descriptor. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @param idxName Index name. |
| */ |
| protected static void assertNoIndexDescriptor(IgniteEx node, String cacheName, String idxName) { |
| awaitCompletion(); |
| |
| DynamicCacheDescriptor desc = node.context().cache().cacheDescriptor(cacheName); |
| |
| if (desc == null) |
| return; |
| |
| for (QueryEntity entity : desc.schema().entities()) { |
| for (QueryIndex idx : entity.getIndexes()) { |
| if (F.eq(idxName, QueryUtils.indexName(entity, idx))) |
| fail("Index exists: " + idxName); |
| } |
| } |
| } |
| |
| /** |
| * Await completion (hopefully) of pending operations. |
| */ |
| private static void awaitCompletion() { |
| try { |
| U.sleep(100); |
| } |
| catch (IgniteInterruptedCheckedException e) { |
| fail(); |
| } |
| } |
| |
| /** |
| * Assert index doesn't exist. |
| * |
| * @param typeDesc Type descriptor. |
| * @param idxName Index name. |
| */ |
| protected static void assertNoIndex(QueryTypeDescriptorImpl typeDesc, String idxName) { |
| assertNull(typeDesc.index(idxName)); |
| } |
| |
| /** |
| * Check whether this is affinity node for cache. |
| * |
| * @param node Node. |
| * @param cacheName Cache name. |
| * @return {@code True} if affinity node. |
| */ |
| private static boolean affinityNode(IgniteEx node, String cacheName) { |
| if (node.configuration().isClientMode()) |
| return false; |
| |
| DynamicCacheDescriptor cacheDesc = node.context().cache().cacheDescriptor(cacheName); |
| |
| IgnitePredicate<ClusterNode> filter = cacheDesc.cacheConfiguration().getNodeFilter(); |
| |
| return filter == null || filter.apply(node.localNode()); |
| } |
| |
| /** |
| * Get table name for class. |
| * |
| * @param cls Class. |
| * @return Table name. |
| */ |
| protected static String tableName(Class cls) { |
| return cls.getSimpleName(); |
| } |
| |
| /** |
| * Convenient method for index creation. |
| * |
| * @param name Name. |
| * @param fields Fields. |
| * @return Index. |
| */ |
| protected static QueryIndex index(String name, IgniteBiTuple<String, Boolean>... fields) { |
| QueryIndex idx = new QueryIndex(); |
| |
| idx.setName(name); |
| |
| LinkedHashMap<String, Boolean> fields0 = new LinkedHashMap<>(); |
| |
| for (IgniteBiTuple<String, Boolean> field : fields) |
| fields0.put(field.getKey(), field.getValue()); |
| |
| idx.setFields(fields0); |
| |
| return idx; |
| } |
| |
| /** |
| * Get query processor. |
| * |
| * @param node Node. |
| * @return Query processor. |
| */ |
| protected static GridQueryProcessor queryProcessor(Ignite node) { |
| return ((IgniteEx)node).context().query(); |
| } |
| |
| /** |
| * Field for index state check (ascending). |
| * |
| * @param name Name. |
| * @return Field. |
| */ |
| protected static IgniteBiTuple<String, Boolean> field(String name) { |
| return field(name, true); |
| } |
| |
| /** |
| * Field for index state check. |
| * |
| * @param name Name. |
| * @param asc Ascending flag. |
| * @return Field. |
| */ |
| protected static IgniteBiTuple<String, Boolean> field(String name, boolean asc) { |
| return F.t(name, asc); |
| } |
| |
| /** |
| * @param fieldName Field name. |
| * @return Alias. |
| */ |
| protected static String alias(String fieldName) { |
| return fieldName + "_alias"; |
| } |
| |
| /** |
| * Key class. |
| */ |
| public static class KeyClass { |
| /** ID. */ |
| @QuerySqlField |
| private long id; |
| |
| /** |
| * Constructor. |
| * |
| * @param id ID. |
| */ |
| public KeyClass(long id) { |
| this.id = id; |
| } |
| |
| /** |
| * @return ID. |
| */ |
| public long id() { |
| return id; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| KeyClass keyClass = (KeyClass) o; |
| |
| return id == keyClass.id; |
| |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int hashCode() { |
| return (int) (id ^ (id >>> 32)); |
| } |
| } |
| |
| /** |
| * Key class. |
| */ |
| public static class ValueClass { |
| /** Field 1. */ |
| @QuerySqlField |
| private String field1; |
| |
| /** |
| * Constructor. |
| * |
| * @param field1 Field 1. |
| */ |
| public ValueClass(String field1) { |
| this.field1 = field1; |
| } |
| |
| /** |
| * @return Field 1 |
| */ |
| public String field1() { |
| return field1; |
| } |
| } |
| |
| /** |
| * Key class. |
| */ |
| public static class ValueClass2 { |
| /** Field 1. */ |
| @QuerySqlField(name = "field1") |
| private String field; |
| |
| /** |
| * Constructor. |
| * |
| * @param field Field 1. |
| */ |
| public ValueClass2(String field) { |
| this.field = field; |
| } |
| |
| /** |
| * @return Field 1 |
| */ |
| public String field() { |
| return field; |
| } |
| } |
| |
| /** |
| * Runnable which can throw checked exceptions. |
| */ |
| protected interface RunnableX { |
| /** |
| * Do run. |
| * |
| * @throws Exception If failed. |
| */ |
| public void run() throws Exception; |
| } |
| } |