Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id D47E4200C84 for ; Thu, 20 Apr 2017 09:48:42 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id D3398160B91; Thu, 20 Apr 2017 07:48:42 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 46FDA160BCD for ; Thu, 20 Apr 2017 09:48:38 +0200 (CEST) Received: (qmail 18162 invoked by uid 500); 20 Apr 2017 07:48:37 -0000 Mailing-List: contact commits-help@ignite.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ignite.apache.org Delivered-To: mailing list commits@ignite.apache.org Received: (qmail 16452 invoked by uid 99); 20 Apr 2017 07:48:35 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 20 Apr 2017 07:48:35 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 04EE9F17B3; Thu, 20 Apr 2017 07:48:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sboikov@apache.org To: commits@ignite.apache.org Date: Thu, 20 Apr 2017 07:49:05 -0000 Message-Id: <1937887b523a4288a0bee43168bc266e@git.apache.org> In-Reply-To: <15b380a870d740a8b3efeb1ffa3a9ef9@git.apache.org> References: <15b380a870d740a8b3efeb1ffa3a9ef9@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [32/70] [abbrv] ignite git commit: IGNITE-4565: Implemented CREATE INDEX and DROP INDEX. This closes #1773. This closes #1804. archived-at: Thu, 20 Apr 2017 07:48:43 -0000 http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java index 98d227c..a9c1a20 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java @@ -17,12 +17,10 @@ package org.apache.ignite.internal.processors.query.h2.opt; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; import java.util.ArrayList; -import java.util.Collections; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; @@ -35,11 +33,9 @@ import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.KeyCacheObject; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory; -import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndex; import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteBiTuple; -import org.h2.api.TableEngine; import org.h2.command.ddl.CreateTableData; import org.h2.engine.Session; import org.h2.index.Index; @@ -50,7 +46,6 @@ import org.h2.result.SearchRow; import org.h2.result.SortOrder; import org.h2.table.IndexColumn; import org.h2.table.TableBase; -import org.h2.table.TableFilter; import org.h2.value.Value; import org.jetbrains.annotations.Nullable; import org.jsr166.ConcurrentHashMap8; @@ -72,7 +67,10 @@ public class GridH2Table extends TableBase { private final GridH2RowDescriptor desc; /** */ - private final ArrayList idxs; + private volatile ArrayList idxs; + + /** */ + private final Map tmpIdxs = new HashMap<>(); /** */ private final ReadWriteLock lock; @@ -81,7 +79,7 @@ public class GridH2Table extends TableBase { private boolean destroyed; /** */ - private final Set sessions = Collections.newSetFromMap(new ConcurrentHashMap8()); + private final ConcurrentMap sessions = new ConcurrentHashMap8<>(); /** */ private final AtomicReference actualSnapshot = new AtomicReference<>(); @@ -106,11 +104,12 @@ public class GridH2Table extends TableBase { * * @param createTblData Table description. * @param desc Row descriptor. + * @param rowFactory Row factory. * @param idxsFactory Indexes factory. * @param spaceName Space name. */ - public GridH2Table(CreateTableData createTblData, @Nullable GridH2RowDescriptor desc, IndexesFactory idxsFactory, - @Nullable String spaceName) { + public GridH2Table(CreateTableData createTblData, @Nullable GridH2RowDescriptor desc, H2RowFactory rowFactory, + GridH2SystemIndexFactory idxsFactory, @Nullable String spaceName) { super(createTblData); assert idxsFactory != null; @@ -143,18 +142,18 @@ public class GridH2Table extends TableBase { } } + this.rowFactory = rowFactory; + // Indexes must be created in the end when everything is ready. - rowFactory = idxsFactory.createRowFactory(this); - idxs = idxsFactory.createIndexes(this); + idxs = idxsFactory.createSystemIndexes(this); assert idxs != null; // Add scan index at 0 which is required by H2. - if (idxs.size() >= 2 - && index(0).getIndexType().isHash()) - idxs.add(0, new ScanIndex(index(1), index(0))); + if (idxs.size() >= 2 && index(0).getIndexType().isHash()) + idxs.add(0, new GridH2PrimaryScanIndex(this, index(1), index(0))); else - idxs.add(0, new ScanIndex(index(0), null)); + idxs.add(0, new GridH2PrimaryScanIndex(this, index(0), null)); snapshotEnabled = desc == null || desc.snapshotableIndex(); @@ -238,6 +237,8 @@ public class GridH2Table extends TableBase { desc.guard().begin(); try { + ensureNotDestroyed(); + GridH2AbstractKeyValueRow row = (GridH2AbstractKeyValueRow)pk.findOne(searchRow); if (row == null) @@ -266,13 +267,21 @@ public class GridH2Table extends TableBase { } /** {@inheritDoc} */ - @SuppressWarnings({"LockAcquiredButNotSafelyReleased", "SynchronizationOnLocalVariableOrMethodParameter", "unchecked"}) - @Override public boolean lock(@Nullable final Session ses, boolean exclusive, boolean force) { - if (ses != null) { - if (!sessions.add(ses)) - return false; + @Override public boolean lock(Session ses, boolean exclusive, boolean force) { + Boolean putRes = sessions.putIfAbsent(ses, exclusive); + + // In accordance with base method semantics, we'll return true if we were already exclusively locked + if (putRes != null) + return putRes; + + ses.addLock(this); + + lock(exclusive); + + if (destroyed) { + unlock(exclusive); - ses.addLock(this); + throw new IllegalStateException("Table " + identifier() + " already destroyed."); } if (snapshotInLock()) @@ -320,6 +329,8 @@ public class GridH2Table extends TableBase { } try { + ensureNotDestroyed(); + // Try again inside of the lock. snapshots = actualSnapshot.get(); @@ -362,12 +373,6 @@ public class GridH2Table extends TableBase { throw new IgniteInterruptedException("Thread got interrupted while trying to acquire table lock.", e); } - - if (destroyed) { - unlock(exclusive); - - throw new IllegalStateException("Table " + identifier() + " already destroyed."); - } } /** @@ -388,12 +393,6 @@ public class GridH2Table extends TableBase { throw new IgniteInterruptedException("Thread got interrupted while trying to acquire table lock.", e); } - if (destroyed) { - unlock(exclusive); - - throw new IllegalStateException("Table " + identifier() + " already destroyed."); - } - return true; } @@ -409,6 +408,14 @@ public class GridH2Table extends TableBase { } /** + * Check if table is not destroyed. + */ + private void ensureNotDestroyed() { + if (destroyed) + throw new IllegalStateException("Table " + identifier() + " already destroyed."); + } + + /** * Must be called inside of write lock because when using multiple indexes we have to ensure that all of them have * the same contents at snapshot taking time. * @@ -462,6 +469,8 @@ public class GridH2Table extends TableBase { lock(true); try { + ensureNotDestroyed(); + assert sessions.isEmpty() : sessions; destroyed = true; @@ -475,12 +484,16 @@ public class GridH2Table extends TableBase { } /** {@inheritDoc} */ - @Override public void unlock(@Nullable Session ses) { - if (ses != null && !sessions.remove(ses)) + @Override public void unlock(Session ses) { + Boolean exclusive = sessions.remove(ses); + + if (exclusive == null) return; if (snapshotInLock()) releaseSnapshots(); + + unlock(exclusive); } /** @@ -599,6 +612,8 @@ public class GridH2Table extends TableBase { desc.guard().begin(); try { + ensureNotDestroyed(); + GridH2IndexBase pk = pk(); if (!del) { @@ -623,18 +638,11 @@ public class GridH2Table extends TableBase { while (++i < len) { GridH2IndexBase idx = index(i); - assert !idx.getIndexType().isUnique() : "Unique indexes are not supported: " + idx; - - GridH2Row old2 = idx.put(row); - - if (old2 != null) { // Row was replaced in index. - if (!eq(pk, old2, old)) - throw new IllegalStateException("Row conflict should never happen, unique indexes are " + - "not supported [idx=" + idx + ", old=" + old + ", old2=" + old2 + ']'); - } - else if (old != null) // Row was not replaced, need to remove manually. - idx.removex(old); + addToIndex(idx, pk, row, old, false); } + + for (GridH2IndexBase idx : tmpIdxs.values()) + addToIndex(idx, pk, row, old, true); } else { // index(1) is PK, get full row from there (search row here contains only key but no other columns). @@ -656,6 +664,9 @@ public class GridH2Table extends TableBase { assert eq(pk, res, old) : "\n" + old + "\n" + res + "\n" + i + " -> " + index(i).getName(); } + for (GridH2IndexBase idx : tmpIdxs.values()) + idx.remove(old); + size.decrement(); } else @@ -676,6 +687,31 @@ public class GridH2Table extends TableBase { } /** + * Add row to index. + * + * @param idx Index to add row to. + * @param pk Primary key index. + * @param row Row to add to index. + * @param old Previous row state, if any. + * @param tmp {@code True} if this is proposed index which may be not consistent yet. + */ + private void addToIndex(GridH2IndexBase idx, Index pk, GridH2Row row, GridH2Row old, boolean tmp) { + assert !idx.getIndexType().isUnique() : "Unique indexes are not supported: " + idx; + + GridH2Row old2 = idx.put(row); + + if (old2 != null) { // Row was replaced in index. + if (!tmp) { + if (!eq(pk, old2, old)) + throw new IllegalStateException("Row conflict should never happen, unique indexes are " + + "not supported [idx=" + idx + ", old=" + old + ", old2=" + old2 + ']'); + } + } + else if (old != null) // Row was not replaced, need to remove manually. + idx.removex(old); + } + + /** * Check row equality. * * @param pk Primary key index. @@ -716,9 +752,119 @@ public class GridH2Table extends TableBase { } /** {@inheritDoc} */ - @Override public Index addIndex(Session ses, String s, int i, IndexColumn[] idxCols, IndexType idxType, - boolean b, String s1) { - throw DbException.getUnsupportedException("addIndex"); + @Override public Index addIndex(Session ses, String idxName, int idxId, IndexColumn[] cols, IndexType idxType, + boolean create, String idxComment) { + return commitUserIndex(ses, idxName); + } + + /** + * Add index that is in an intermediate state and is still being built, thus is not used in queries until it is + * promoted. + * + * @param idx Index to add. + * @throws IgniteCheckedException If failed. + */ + public void proposeUserIndex(Index idx) throws IgniteCheckedException { + assert idx instanceof GridH2IndexBase; + + lock(true); + + try { + ensureNotDestroyed(); + + for (Index oldIdx : idxs) { + if (F.eq(oldIdx.getName(), idx.getName())) + throw new IgniteCheckedException("Index already exists: " + idx.getName()); + } + + Index oldTmpIdx = tmpIdxs.put(idx.getName(), (GridH2IndexBase)idx); + + assert oldTmpIdx == null; + } + finally { + unlock(true); + } + } + + /** + * Promote temporary index to make it usable in queries. + * + * @param ses H2 session. + * @param idxName Index name. + * @return Temporary index with given name. + */ + private Index commitUserIndex(Session ses, String idxName) { + lock(true); + + try { + ensureNotDestroyed(); + + Index idx = tmpIdxs.remove(idxName); + + assert idx != null; + + ArrayList newIdxs = new ArrayList<>(idxs.size() + 1); + + newIdxs.addAll(idxs); + + newIdxs.add(idx); + + idxs = newIdxs; + + database.addSchemaObject(ses, idx); + + setModified(); + + return idx; + } + finally { + unlock(true); + } + } + + /** + * Remove user index without promoting it. + * + * @param idxName Index name. + */ + public void rollbackUserIndex(String idxName) { + lock(true); + + try { + ensureNotDestroyed(); + + GridH2IndexBase rmvIdx = tmpIdxs.remove(idxName); + + assert rmvIdx != null; + } + finally { + unlock(true); + } + } + + /** {@inheritDoc} */ + @Override public void removeIndex(Index h2Idx) { + lock(true); + + try { + ArrayList idxs = new ArrayList<>(this.idxs); + + for (int i = 2; i < idxs.size(); i++) { + GridH2IndexBase idx = (GridH2IndexBase)idxs.get(i); + + if (idx != h2Idx) + continue; + + idxs.remove(i); + + this.idxs = idxs; + + return; + } + } + finally { + unlock(true); + } } /** {@inheritDoc} */ @@ -848,136 +994,4 @@ public class GridH2Table extends TableBase { return rowFactory; } - /** - * H2 Table engine. - */ - @SuppressWarnings({"PublicInnerClass", "FieldAccessedSynchronizedAndUnsynchronized"}) - public static class Engine implements TableEngine { - /** */ - private static GridH2RowDescriptor rowDesc; - - /** */ - private static IndexesFactory idxsFactory; - - /** */ - private static GridH2Table resTbl; - - /** */ - private static String spaceName; - - /** {@inheritDoc} */ - @Override public TableBase createTable(CreateTableData createTblData) { - resTbl = new GridH2Table(createTblData, rowDesc, idxsFactory, spaceName); - - return resTbl; - } - - /** - * Creates table using given connection, DDL clause for given type descriptor and list of indexes. - * - * @param conn Connection. - * @param sql DDL clause. - * @param desc Row descriptor. - * @param factory Indexes factory. - * @param space Space name. - * @throws SQLException If failed. - * @return Created table. - */ - public static synchronized GridH2Table createTable(Connection conn, String sql, - @Nullable GridH2RowDescriptor desc, IndexesFactory factory, String space) - throws SQLException { - rowDesc = desc; - idxsFactory = factory; - spaceName = space; - - try { - try (Statement s = conn.createStatement()) { - s.execute(sql + " engine \"" + Engine.class.getName() + "\""); - } - - return resTbl; - } - finally { - resTbl = null; - idxsFactory = null; - rowDesc = null; - } - } - } - - /** - * Type which can create indexes list for given table. - */ - @SuppressWarnings({"PackageVisibleInnerClass", "PublicInnerClass"}) - public static interface IndexesFactory { - /** - * Create list of indexes. First must be primary key, after that all unique indexes and - * only then non-unique indexes. - * All indexes must be subtypes of {@link H2TreeIndex}. - * - * @param tbl Table to create indexes for. - * @return List of indexes. - */ - ArrayList createIndexes(GridH2Table tbl); - - /** - * @param tbl Table. - * @return Data store. - */ - H2RowFactory createRowFactory(GridH2Table tbl); - } - - /** - * Wrapper type for primary key. - */ - @SuppressWarnings("PackageVisibleInnerClass") - class ScanIndex extends GridH2ScanIndex { - /** */ - static final String SCAN_INDEX_NAME_SUFFIX = "__SCAN_"; - - /** */ - private final GridH2IndexBase hashIdx; - - /** - * Constructor. - */ - private ScanIndex(GridH2IndexBase treeIdx, GridH2IndexBase hashIdx) { - super(treeIdx); - - this.hashIdx = hashIdx; - } - - /** - * - */ - @Override protected GridH2IndexBase delegate() { - if (hashIdx != null) - return rebuildFromHashInProgress ? hashIdx : super.delegate(); - else { - assert !rebuildFromHashInProgress; - - return super.delegate(); - } - } - - /** {@inheritDoc} */ - @Override public double getCost(Session ses, int[] masks, TableFilter[] filters, int filter, - SortOrder sortOrder) { - long rows = getRowCountApproximation(); - double baseCost = getCostRangeIndex(masks, rows, filters, filter, sortOrder, true); - int mul = delegate().getDistributedMultiplier(ses, filters, filter); - - return mul * baseCost; - } - - /** {@inheritDoc} */ - @Override public String getPlanSQL() { - return delegate().getTable().getSQL() + "." + SCAN_INDEX_NAME_SUFFIX; - } - - /** {@inheritDoc} */ - @Override public String getName() { - return delegate().getName() + SCAN_INDEX_NAME_SUFFIX; - } - } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridLuceneIndex.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridLuceneIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridLuceneIndex.java index b73bb96..4395024 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridLuceneIndex.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridLuceneIndex.java @@ -116,15 +116,7 @@ public class GridLuceneIndex implements AutoCloseable { throw new IgniteCheckedException(e); } - GridQueryIndexDescriptor idx = null; - - for (GridQueryIndexDescriptor descriptor : type.indexes().values()) { - if (descriptor.type() == QueryIndexType.FULLTEXT) { - idx = descriptor; - - break; - } - } + GridQueryIndexDescriptor idx = type.textIndex(); if (idx != null) { Collection fields = idx.fields(); http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateIndex.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateIndex.java new file mode 100644 index 0000000..50d455c --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateIndex.java @@ -0,0 +1,121 @@ +/* + * 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.query.h2.sql; + +import java.util.Map; +import org.apache.ignite.cache.QueryIndex; +import org.apache.ignite.cache.QueryIndexType; +import org.h2.command.Parser; + +/** + * CREATE INDEX statement. + */ +public class GridSqlCreateIndex extends GridSqlStatement { + /** Schema name. */ + private String schemaName; + + /** Table name. */ + private String tblName; + + /** Attempt to create the index only if it does not exist. */ + private boolean ifNotExists; + + /** Index to create. */ + private QueryIndex idx; + + /** + * @return Schema name for new index. + */ + public String schemaName() { + return schemaName; + } + + /** + * @param schemaName Schema name for new index. + */ + public void schemaName(String schemaName) { + this.schemaName = schemaName; + } + + /** + * @return Table name. + */ + public String tableName() { + return tblName; + } + + /** + * @param tblName Table name. + */ + public void tableName(String tblName) { + this.tblName = tblName; + } + + /** + * @return whether attempt to create the index should be made only if it does not exist. + */ + public boolean ifNotExists() { + return ifNotExists; + } + + /** + * @param ifNotExists whether attempt to create the index should be made only if it does not exist. + */ + public void ifNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + /** + * @return Index to create. + */ + public QueryIndex index() { + return idx; + } + + /** + * @param idx Index to create. + */ + public void index(QueryIndex idx) { + this.idx = idx; + } + + /** {@inheritDoc} */ + @Override public String getSQL() { + StringBuilder sb = new StringBuilder("CREATE ") + .append(idx.getIndexType() == QueryIndexType.GEOSPATIAL ? "SPATIAL " : "") + .append("INDEX ").append(ifNotExists ? "IF NOT EXISTS " : "") + .append(Parser.quoteIdentifier(schemaName)).append('.') + .append(Parser.quoteIdentifier(idx.getName())).append(" ON ") + .append(Parser.quoteIdentifier(tblName)).append(" ("); + + boolean first = true; + + for (Map.Entry e : idx.getFields().entrySet()) { + if (first) + first = false; + else + sb.append(", "); + + sb.append(Parser.quoteIdentifier(e.getKey())).append(e.getValue() ? " ASC" : " DESC"); + } + + sb.append(')'); + + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlDropIndex.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlDropIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlDropIndex.java new file mode 100644 index 0000000..c844b49 --- /dev/null +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlDropIndex.java @@ -0,0 +1,82 @@ +/* + * 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.query.h2.sql; + +import org.h2.command.Parser; + +/** + * DROP INDEX statement. + */ +public class GridSqlDropIndex extends GridSqlStatement { + /** Index name. */ + private String name; + + /** Schema name. */ + private String schemaName; + + /** Attempt to drop the index only if it exists. */ + private boolean ifExists; + + /** + * @return Index name. + */ + public String name() { + return name; + } + + /** + * @param name Index name. + */ + public void name(String name) { + this.name = name; + } + + /** + * @return Schema name. + */ + public String schemaName() { + return schemaName; + } + + /** + * @param schemaName Schema name. + */ + public void schemaName(String schemaName) { + this.schemaName = schemaName; + } + + /** + * @return whether attempt to drop the index should be made only if it exists. + */ + public boolean ifExists() { + return ifExists; + } + + /** + * @param ifExists whether attempt to drop the index should be made only if it exists. + */ + public void ifExists(boolean ifExists) { + this.ifExists = ifExists; + } + + /** {@inheritDoc} */ + @Override public String getSQL() { + return "DROP INDEX " + (ifExists ? "IF EXISTS " : "") + Parser.quoteIdentifier(schemaName) + '.' + + Parser.quoteIdentifier(name); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java index 0f940e9..199a157 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java @@ -28,9 +28,16 @@ import java.util.List; import java.util.Map; import javax.cache.CacheException; import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.QueryIndex; +import org.apache.ignite.cache.QueryIndexType; +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; +import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.h2.command.Command; import org.h2.command.CommandContainer; import org.h2.command.Prepared; +import org.h2.command.ddl.CreateIndex; +import org.h2.command.ddl.DropIndex; +import org.h2.command.ddl.SchemaCommand; import org.h2.command.dml.Delete; import org.h2.command.dml.Explain; import org.h2.command.dml.Insert; @@ -63,8 +70,10 @@ import org.h2.expression.ValueExpression; import org.h2.index.ViewIndex; import org.h2.jdbc.JdbcPreparedStatement; import org.h2.result.SortOrder; +import org.h2.schema.Schema; import org.h2.table.Column; import org.h2.table.FunctionTable; +import org.h2.table.IndexColumn; import org.h2.table.RangeTable; import org.h2.table.Table; import org.h2.table.TableBase; @@ -302,6 +311,48 @@ public class GridSqlQueryParser { GridSqlQueryParser.getter(CommandContainer.class, "prepared"); /** */ + private static final Getter CREATE_INDEX_NAME = getter(CreateIndex.class, "indexName"); + + /** */ + private static final Getter CREATE_INDEX_TABLE_NAME = getter(CreateIndex.class, "tableName"); + + /** */ + private static final Getter CREATE_INDEX_COLUMNS = getter(CreateIndex.class, + "indexColumns"); + + /** */ + private static final Getter CREATE_INDEX_SPATIAL = getter(CreateIndex.class, "spatial"); + + /** */ + private static final Getter CREATE_INDEX_PRIMARY_KEY = getter(CreateIndex.class, + "primaryKey"); + + /** */ + private static final Getter CREATE_INDEX_UNIQUE = getter(CreateIndex.class, "unique"); + + /** */ + private static final Getter CREATE_INDEX_HASH = getter(CreateIndex.class, "hash"); + + /** */ + private static final Getter CREATE_INDEX_IF_NOT_EXISTS = getter(CreateIndex.class, + "ifNotExists"); + + /** */ + private static final Getter INDEX_COLUMN_NAME = getter(IndexColumn.class, "columnName"); + + /** */ + private static final Getter INDEX_COLUMN_SORT_TYPE = getter(IndexColumn.class, "sortType"); + + /** */ + private static final Getter DROP_INDEX_NAME = getter(DropIndex.class, "indexName"); + + /** */ + private static final Getter DROP_INDEX_IF_EXISTS = getter(DropIndex.class, "ifExists"); + + /** */ + private static final Getter SCHEMA_COMMAND_SCHEMA = getter(SchemaCommand.class, "schema"); + + /** */ private final IdentityHashMap h2ObjToGridObj = new IdentityHashMap<>(); /** */ @@ -659,6 +710,72 @@ public class GridSqlQueryParser { return res; } + + + /** + * Parse {@code DROP INDEX} statement. + * + * @param dropIdx {@code DROP INDEX} statement. + * @see H2 {@code DROP INDEX} spec. + */ + private GridSqlDropIndex parseDropIndex(DropIndex dropIdx) { + GridSqlDropIndex res = new GridSqlDropIndex(); + + res.name(DROP_INDEX_NAME.get(dropIdx)); + res.schemaName(SCHEMA_COMMAND_SCHEMA.get(dropIdx).getName()); + res.ifExists(DROP_INDEX_IF_EXISTS.get(dropIdx)); + + return res; + } + + /** + * Parse {@code CREATE INDEX} statement. + * + * @param createIdx {@code CREATE INDEX} statement. + * @see H2 {@code CREATE INDEX} spec. + */ + private GridSqlCreateIndex parseCreateIndex(CreateIndex createIdx) { + if (CREATE_INDEX_HASH.get(createIdx) || CREATE_INDEX_PRIMARY_KEY.get(createIdx) || + CREATE_INDEX_UNIQUE.get(createIdx)) + throw new IgniteSQLException("Only SPATIAL modifier is supported for CREATE INDEX", + IgniteQueryErrorCode.UNSUPPORTED_OPERATION); + + GridSqlCreateIndex res = new GridSqlCreateIndex(); + + Schema schema = SCHEMA_COMMAND_SCHEMA.get(createIdx); + + String tblName = CREATE_INDEX_TABLE_NAME.get(createIdx); + + res.schemaName(schema.getName()); + res.tableName(tblName); + res.ifNotExists(CREATE_INDEX_IF_NOT_EXISTS.get(createIdx)); + + QueryIndex idx = new QueryIndex(); + + idx.setName(CREATE_INDEX_NAME.get(createIdx)); + idx.setIndexType(CREATE_INDEX_SPATIAL.get(createIdx) ? QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED); + + IndexColumn[] cols = CREATE_INDEX_COLUMNS.get(createIdx); + + LinkedHashMap flds = new LinkedHashMap<>(cols.length); + + for (IndexColumn col : CREATE_INDEX_COLUMNS.get(createIdx)) { + int sortType = INDEX_COLUMN_SORT_TYPE.get(col); + + if ((sortType & SortOrder.NULLS_FIRST) != 0 || (sortType & SortOrder.NULLS_LAST) != 0) + throw new IgniteSQLException("NULLS FIRST and NULLS LAST modifiers are not supported for index columns", + IgniteQueryErrorCode.UNSUPPORTED_OPERATION); + + flds.put(INDEX_COLUMN_NAME.get(col), (sortType & SortOrder.DESCENDING) == 0); + } + + idx.setFields(flds); + + res.index(idx); + + return res; + } + /** * @param sortOrder Sort order. * @param qry Query. @@ -722,6 +839,12 @@ public class GridSqlQueryParser { if (stmt instanceof Explain) return parse(EXPLAIN_COMMAND.get((Explain)stmt)).explain(true); + if (stmt instanceof CreateIndex) + return parseCreateIndex((CreateIndex)stmt); + + if (stmt instanceof DropIndex) + return parseDropIndex((DropIndex)stmt); + throw new CacheException("Unsupported SQL statement: " + stmt); } http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AbstractSchemaSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AbstractSchemaSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AbstractSchemaSelfTest.java new file mode 100644 index 0000000..a865b18 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AbstractSchemaSelfTest.java @@ -0,0 +1,512 @@ +/* + * 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.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"; + + /** + * 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 types(IgniteEx node, String cacheName) { + Map res = new HashMap<>(); + + Collection 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 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... fields) { + for (Ignite node : Ignition.allGrids()) + assertIndex((IgniteEx)node, cacheName, tblName, idxName, fields); + } + + /** + * Assert index state on particular node. + * + * @param node Node. + * @param cacheName Cache name. + * @param tblName Table name. + * @param idxName Index name. + * @param fields Fields. + */ + protected static void assertIndex(IgniteEx node, String cacheName, String tblName, String idxName, + IgniteBiTuple... fields) { + assertIndexDescriptor(node, cacheName, tblName, idxName, fields); + + if (affinityNode(node, cacheName)) { + QueryTypeDescriptorImpl typeDesc = typeExisting(node, 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... 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 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 [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... fields) { + QueryIndexDescriptorImpl idxDesc = typeDesc.index(idxName); + + assertNotNull(idxDesc); + + assertEquals(idxName, idxDesc.name()); + assertEquals(typeDesc, idxDesc.typeDescriptor()); + assertEquals(QueryIndexType.SORTED, idxDesc.type()); + + List 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(IgniteEx node, String cacheName, String tblName, String idxName) { + assertNoIndexDescriptor(node, cacheName, tblName, idxName); + + if (affinityNode(node, cacheName)) { + QueryTypeDescriptorImpl typeDesc = typeExisting(node, cacheName, tblName); + + assertNoIndex(typeDesc, idxName); + } + } + + /** + * Assert index doesn't exist in particular node's cache descriptor. + * + * @param node Node. + * @param cacheName Cache name. + * @param tblName Table name. + * @param idxName Index name. + */ + protected static void assertNoIndexDescriptor(IgniteEx node, String cacheName, String tblName, 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 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... fields) { + QueryIndex idx = new QueryIndex(); + + idx.setName(name); + + LinkedHashMap fields0 = new LinkedHashMap<>(); + + for (IgniteBiTuple 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 field(String name) { + return field(name, true); + } + + /** + * Field for index state check. + * + * @param name Name. + * @param asc Ascending flag. + * @return Field. + */ + protected static IgniteBiTuple 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; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/2edb935c/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicIndexAbstractBasicSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicIndexAbstractBasicSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicIndexAbstractBasicSelfTest.java new file mode 100644 index 0000000..6bc1576 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicIndexAbstractBasicSelfTest.java @@ -0,0 +1,950 @@ +/* + * 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.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.QueryIndex; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.configuration.NearCacheConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.query.schema.SchemaOperationException; + +import java.util.Arrays; +import java.util.List; + +import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; +import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; +import static org.apache.ignite.cache.CacheMode.LOCAL; +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.cache.CacheMode.REPLICATED; + +/** + * Tests for dynamic index creation. + */ +@SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +public abstract class DynamicIndexAbstractBasicSelfTest extends DynamicIndexAbstractSelfTest { + /** Node index for regular server (coordinator). */ + protected static final int IDX_SRV_CRD = 0; + + /** Node index for regular server (not coordinator). */ + protected static final int IDX_SRV_NON_CRD = 1; + + /** Node index for regular client. */ + protected static final int IDX_CLI = 2; + + /** Node index for server which doesn't pass node filter. */ + protected static final int IDX_SRV_FILTERED = 3; + + /** Node index for client with near-only cache. */ + protected static final int IDX_CLI_NEAR_ONLY = 4; + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + for (IgniteConfiguration cfg : configurations()) + Ignition.start(cfg); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + node().destroyCache(CACHE_NAME); + + super.afterTest(); + } + + /** + * Initialize cache for tests. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + */ + private void initialize(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) { + node().getOrCreateCache(cacheConfiguration(mode, atomicityMode, near)); + + grid(IDX_CLI_NEAR_ONLY).getOrCreateNearCache(CACHE_NAME, new NearCacheConfiguration<>()); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + + loadInitialData(); + } + + /** + * Create cache with the given cache mode and atomicity mode. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Whether near cache should be initialized. + * @return Cache configuration. + */ + private CacheConfiguration cacheConfiguration(CacheMode mode, + CacheAtomicityMode atomicityMode, boolean near) { + CacheConfiguration ccfg = cacheConfiguration(); + + ccfg.setCacheMode(mode); + ccfg.setAtomicityMode(atomicityMode); + + if (near) + ccfg.setNearConfiguration(new NearCacheConfiguration()); + + return ccfg; + } + + /** + * Load initial data. + */ + private void loadInitialData() { + put(node(), 0, KEY_BEFORE); + } + + /** + * Test simple index create for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreatePartitionedAtomic() throws Exception { + checkCreate(PARTITIONED, ATOMIC, false); + } + + /** + * Test simple index create for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreatePartitionedAtomicNear() throws Exception { + checkCreate(PARTITIONED, ATOMIC, true); + } + + /** + * Test simple index create for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreatePartitionedTransactional() throws Exception { + checkCreate(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test simple index create for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreatePartitionedTransactionalNear() throws Exception { + checkCreate(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test simple index create for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateReplicatedAtomic() throws Exception { + checkCreate(REPLICATED, ATOMIC, false); + } + + /** + * Test simple index create for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateReplicatedTransactional() throws Exception { + checkCreate(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check normal create operation. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreate(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + final IgniteEx node = node(); + + final QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1)); + + queryProcessor(node).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + assertIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1, field(FIELD_NAME_1)); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + } + }, SchemaOperationException.CODE_INDEX_EXISTS); + + queryProcessor(node).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, true).get(); + assertIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1, field(FIELD_NAME_1)); + + assertSimpleIndexOperations(SQL_SIMPLE_FIELD_1); + + assertIndexUsed(IDX_NAME_1, SQL_SIMPLE_FIELD_1, SQL_ARG_1); + } + + /** + * Test composite index creation for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositePartitionedAtomic() throws Exception { + checkCreateComposite(PARTITIONED, ATOMIC, false); + } + + /** + * Test composite index creation for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositePartitionedAtomicNear() throws Exception { + checkCreateComposite(PARTITIONED, ATOMIC, true); + } + + /** + * Test composite index creation for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositePartitionedTransactional() throws Exception { + checkCreateComposite(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test composite index creation for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositePartitionedTransactionalNear() throws Exception { + checkCreateComposite(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test composite index creation for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositeReplicatedAtomic() throws Exception { + checkCreateComposite(REPLICATED, ATOMIC, false); + } + + /** + * Test composite index creation for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateCompositeReplicatedTransactional() throws Exception { + checkCreateComposite(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check composite index creation. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreateComposite(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + final QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1), field(alias(FIELD_NAME_2))); + + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + assertIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1, field(FIELD_NAME_1), field(alias(FIELD_NAME_2))); + + assertCompositeIndexOperations(SQL_COMPOSITE); + + assertIndexUsed(IDX_NAME_1, SQL_COMPOSITE, SQL_ARG_1, SQL_ARG_2); + } + + /** + * Test create when cache doesn't exist for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCachePartitionedAtomic() throws Exception { + checkCreateNotCache(PARTITIONED, ATOMIC, false); + } + + /** + * Test create when cache doesn't exist for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCachePartitionedAtomicNear() throws Exception { + checkCreateNotCache(PARTITIONED, ATOMIC, true); + } + + /** + * Test create when cache doesn't exist for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCachePartitionedTransactional() throws Exception { + checkCreateNotCache(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test create when cache doesn't exist for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCachePartitionedTransactionalNear() throws Exception { + checkCreateNotCache(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test create when cache doesn't exist for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCacheReplicatedAtomic() throws Exception { + checkCreateNotCache(REPLICATED, ATOMIC, false); + } + + /** + * Test create when cache doesn't exist for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoCacheReplicatedTransactional() throws Exception { + checkCreateNotCache(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check create when cache doesn't exist. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreateNotCache(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + final QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1)); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexCreate(randomString(), TBL_NAME, idx, false).get(); + } + }, SchemaOperationException.CODE_CACHE_NOT_FOUND); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + } + + /** + * Test create when table doesn't exist for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTablePartitionedAtomic() throws Exception { + checkCreateNoTable(PARTITIONED, ATOMIC, false); + } + + /** + * Test create when table doesn't exist for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTablePartitionedAtomicNear() throws Exception { + checkCreateNoTable(PARTITIONED, ATOMIC, true); + } + + /** + * Test create when table doesn't exist for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTablePartitionedTransactional() throws Exception { + checkCreateNoTable(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test create when table doesn't exist for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTablePartitionedTransactionalNear() throws Exception { + checkCreateNoTable(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test create when table doesn't exist for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTableReplicatedAtomic() throws Exception { + checkCreateNoTable(REPLICATED, ATOMIC, false); + } + + /** + * Test create when table doesn't exist for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoTableReplicatedTransactional() throws Exception { + checkCreateNoTable(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check create when table doesn't exist. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreateNoTable(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + final QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1)); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, randomString(), idx, false).get(); + } + }, SchemaOperationException.CODE_TABLE_NOT_FOUND); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + } + + /** + * Test create when table doesn't exist for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnPartitionedAtomic() throws Exception { + checkCreateNoColumn(PARTITIONED, ATOMIC, false); + } + + /** + * Test create when table doesn't exist for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnPartitionedAtomicNear() throws Exception { + checkCreateNoColumn(PARTITIONED, ATOMIC, true); + } + + /** + * Test create when table doesn't exist for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnPartitionedTransactional() throws Exception { + checkCreateNoColumn(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test create when table doesn't exist for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnPartitionedTransactionalNear() throws Exception { + checkCreateNoColumn(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test create when table doesn't exist for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnReplicatedAtomic() throws Exception { + checkCreateNoColumn(REPLICATED, ATOMIC, false); + } + + /** + * Test create when table doesn't exist for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateNoColumnReplicatedTransactional() throws Exception { + checkCreateNoColumn(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check create when table doesn't exist. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreateNoColumn(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + final QueryIndex idx = index(IDX_NAME_1, field(randomString())); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + } + }, SchemaOperationException.CODE_COLUMN_NOT_FOUND); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + } + + /** + * Test index creation on aliased column for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasPartitionedAtomic() throws Exception { + checkCreateColumnWithAlias(PARTITIONED, ATOMIC, false); + } + + /** + * Test index creation on aliased column for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasPartitionedAtomicNear() throws Exception { + checkCreateColumnWithAlias(PARTITIONED, ATOMIC, true); + } + + /** + * Test index creation on aliased column for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasPartitionedTransactional() throws Exception { + checkCreateColumnWithAlias(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test index creation on aliased column for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasPartitionedTransactionalNear() throws Exception { + checkCreateColumnWithAlias(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test index creation on aliased column for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasReplicatedAtomic() throws Exception { + checkCreateColumnWithAlias(REPLICATED, ATOMIC, false); + } + + /** + * Test index creation on aliased column for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testCreateColumnWithAliasReplicatedTransactional() throws Exception { + checkCreateColumnWithAlias(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check index creation on aliased column. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkCreateColumnWithAlias(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) + throws Exception { + initialize(mode, atomicityMode, near); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_2)); + + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + } + }, SchemaOperationException.CODE_COLUMN_NOT_FOUND); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + + QueryIndex idx = index(IDX_NAME_1, field(alias(FIELD_NAME_2))); + + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + assertIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1, field(alias(FIELD_NAME_2))); + + assertSimpleIndexOperations(SQL_SIMPLE_FIELD_2); + + assertIndexUsed(IDX_NAME_1, SQL_SIMPLE_FIELD_2, SQL_ARG_1); + } + + /** + * Test simple index drop for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropPartitionedAtomic() throws Exception { + checkDrop(PARTITIONED, ATOMIC, false); + } + + /** + * Test simple index drop for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropPartitionedAtomicNear() throws Exception { + checkDrop(PARTITIONED, ATOMIC, true); + } + + /** + * Test simple index drop for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropPartitionedTransactional() throws Exception { + checkDrop(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test simple index drop for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropPartitionedTransactionalNear() throws Exception { + checkDrop(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test simple index drop for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropReplicatedAtomic() throws Exception { + checkDrop(REPLICATED, ATOMIC, false); + } + + /** + * Test simple index drop for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropReplicatedTransactional() throws Exception { + checkDrop(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check simple index drop. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + public void checkDrop(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1)); + + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, false).get(); + assertIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1, field(FIELD_NAME_1)); + + assertIndexUsed(IDX_NAME_1, SQL_SIMPLE_FIELD_1, SQL_ARG_1); + + assertSimpleIndexOperations(SQL_SIMPLE_FIELD_1); + + loadInitialData(); + + queryProcessor(node()).dynamicIndexDrop(CACHE_NAME, IDX_NAME_1, false).get(); + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + + assertSimpleIndexOperations(SQL_SIMPLE_FIELD_1); + + assertIndexNotUsed(IDX_NAME_1, SQL_SIMPLE_FIELD_1, SQL_ARG_1); + } + + /** + * Test drop when there is no index for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexPartitionedAtomic() throws Exception { + checkDropNoIndex(PARTITIONED, ATOMIC, false); + } + + /** + * Test drop when there is no index for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexPartitionedAtomicNear() throws Exception { + checkDropNoIndex(PARTITIONED, ATOMIC, true); + } + + /** + * Test drop when there is no index for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexPartitionedTransactional() throws Exception { + checkDropNoIndex(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test drop when there is no index for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexPartitionedTransactionalNear() throws Exception { + checkDropNoIndex(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test drop when there is no index for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexReplicatedAtomic() throws Exception { + checkDropNoIndex(REPLICATED, ATOMIC, false); + } + + /** + * Test drop when there is no index for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropNoIndexReplicatedTransactional() throws Exception { + checkDropNoIndex(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check drop when there is no index. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkDropNoIndex(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexDrop(CACHE_NAME, IDX_NAME_1, false).get(); + } + }, SchemaOperationException.CODE_INDEX_NOT_FOUND); + + queryProcessor(node()).dynamicIndexDrop(CACHE_NAME, IDX_NAME_1, true).get(); + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + } + + /** + * Test drop when cache doesn't exist for PARTITIONED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropNoCachePartitionedAtomic() throws Exception { + checkDropNoCache(PARTITIONED, ATOMIC, false); + } + + /** + * Test drop when cache doesn't exist for PARTITIONED ATOMIC cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropNoCachePartitionedAtomicNear() throws Exception { + checkDropNoCache(PARTITIONED, ATOMIC, true); + } + + /** + * Test drop when cache doesn't exist for PARTITIONED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropNoCachePartitionedTransactional() throws Exception { + checkDropNoCache(PARTITIONED, TRANSACTIONAL, false); + } + + /** + * Test drop when cache doesn't exist for PARTITIONED TRANSACTIONAL cache with near cache. + * + * @throws Exception If failed. + */ + public void testDropNoCachePartitionedTransactionalNear() throws Exception { + checkDropNoCache(PARTITIONED, TRANSACTIONAL, true); + } + + /** + * Test drop when cache doesn't exist for REPLICATED ATOMIC cache. + * + * @throws Exception If failed. + */ + public void testDropNoCacheReplicatedAtomic() throws Exception { + checkDropNoCache(REPLICATED, ATOMIC, false); + } + + /** + * Test drop when cache doesn't exist for REPLICATED TRANSACTIONAL cache. + * + * @throws Exception If failed. + */ + public void testDropNoCacheReplicatedTransactional() throws Exception { + checkDropNoCache(REPLICATED, TRANSACTIONAL, false); + } + + /** + * Check drop when cache doesn't exist. + * + * Check drop when there is no index. + * + * @param mode Mode. + * @param atomicityMode Atomicity mode. + * @param near Near flag. + * @throws Exception If failed. + */ + private void checkDropNoCache(CacheMode mode, CacheAtomicityMode atomicityMode, boolean near) throws Exception { + initialize(mode, atomicityMode, near); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexDrop(randomString(), "my_idx", false).get(); + } + }, SchemaOperationException.CODE_CACHE_NOT_FOUND); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + } + + /** + * Test that operations fail on LOCAL cache. + * + * @throws Exception If failed. + */ + public void testFailOnLocalCache() throws Exception { + for (Ignite node : Ignition.allGrids()) { + if (!node.configuration().isClientMode()) + node.getOrCreateCache(cacheConfiguration().setCacheMode(LOCAL)); + } + + final QueryIndex idx = index(IDX_NAME_1, field(FIELD_NAME_1)); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexCreate(CACHE_NAME, TBL_NAME, idx, true).get(); + } + }, SchemaOperationException.CODE_GENERIC); + + assertNoIndex(CACHE_NAME, TBL_NAME, IDX_NAME_1); + + assertSchemaException(new RunnableX() { + @Override public void run() throws Exception { + queryProcessor(node()).dynamicIndexDrop(CACHE_NAME, IDX_NAME_1, true).get(); + } + }, SchemaOperationException.CODE_GENERIC); + } + + /** + * Get node which should be used to start operations. + * + * @return If failed. + */ + protected IgniteEx node() { + return grid(nodeIndex()); + } + + /** + * Get index of the node which should be used to start operations. + * + * @return If failed. + */ + protected abstract int nodeIndex(); + + /** + * Get configurations to be used in test. + * + * @return Configurations. + * @throws Exception If failed. + */ + protected List configurations() throws Exception { + return Arrays.asList( + serverCoordinatorConfiguration(IDX_SRV_CRD), + serverConfiguration(IDX_SRV_NON_CRD), + clientConfiguration(IDX_CLI), + serverConfiguration(IDX_SRV_FILTERED, true), + clientConfiguration(IDX_CLI_NEAR_ONLY) + ); + } + + /** + * Get server coordinator configuration. + * + * @param idx Index. + * @return Configuration. + * @throws Exception If failed. + */ + protected IgniteConfiguration serverCoordinatorConfiguration(int idx) throws Exception { + return serverConfiguration(idx); + } + + /** + * Assert FIELD_1 index usage. + * + * @param sql Simple SQL. + */ + private void assertSimpleIndexOperations(String sql) { + for (Ignite node : Ignition.allGrids()) + assertSqlSimpleData(node, sql, KEY_BEFORE - SQL_ARG_1); + + put(node(), KEY_BEFORE, KEY_AFTER); + + for (Ignite node : Ignition.allGrids()) + assertSqlSimpleData(node, sql, KEY_AFTER - SQL_ARG_1); + + remove(node(), 0, KEY_BEFORE); + + for (Ignite node : Ignition.allGrids()) + assertSqlSimpleData(node, sql, KEY_AFTER - KEY_BEFORE); + + remove(node(), KEY_BEFORE, KEY_AFTER); + + for (Ignite node : Ignition.allGrids()) + assertSqlSimpleData(node, sql, 0); + } + + /** + * Assert composite index usage. + * + * @param sql Simple SQL. + */ + private void assertCompositeIndexOperations(String sql) { + for (Ignite node : Ignition.allGrids()) + assertSqlCompositeData(node, sql, KEY_BEFORE - SQL_ARG_2); + + put(node(), KEY_BEFORE, KEY_AFTER); + + for (Ignite node : Ignition.allGrids()) + assertSqlCompositeData(node, sql, KEY_AFTER - SQL_ARG_2); + + remove(node(), 0, KEY_BEFORE); + + for (Ignite node : Ignition.allGrids()) + assertSqlCompositeData(node, sql, KEY_AFTER - KEY_BEFORE); + + remove(node(), KEY_BEFORE, KEY_AFTER); + + for (Ignite node : Ignition.allGrids()) + assertSqlCompositeData(node, sql, 0); + } +}