You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2017/09/06 15:18:40 UTC
[1/2] ignite git commit: IGNITE-5572: SQL: ALTER TABLE ADD COLUMN
support. This closes #2344.
Repository: ignite
Updated Branches:
refs/heads/master ffaf10837 -> adec3e7ec
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractConcurrentSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractConcurrentSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractConcurrentSelfTest.java
new file mode 100644
index 0000000..969c985
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractConcurrentSelfTest.java
@@ -0,0 +1,1056 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.query.SqlQuery;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteClientReconnectAbstractTest;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.managers.discovery.CustomEventListener;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
+import org.apache.ignite.internal.processors.query.schema.SchemaOperationException;
+import org.apache.ignite.internal.processors.query.schema.message.SchemaFinishDiscoveryMessage;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgnitePredicate;
+
+import static org.apache.ignite.internal.IgniteClientReconnectAbstractTest.TestTcpDiscoverySpi;
+
+/**
+ * Concurrency tests for dynamic index create/drop.
+ */
+@SuppressWarnings("unchecked")
+public abstract class DynamicColumnsAbstractConcurrentSelfTest extends DynamicColumnsAbstractTest {
+ /** Test duration. */
+ private static final long TEST_DUR = 10_000L;
+
+ /** Large cache size. */
+ private static final int LARGE_CACHE_SIZE = 100_000;
+
+ /** Table name. */
+ private static final String TBL_NAME = "PERSON";
+
+ /** Cache name. */
+ private static final String CACHE_NAME = QueryUtils.createTableCacheName(QueryUtils.DFLT_SCHEMA, TBL_NAME);
+
+ /** Attribute to filter node out of cache data nodes. */
+ private static final String ATTR_FILTERED = "FILTERED";
+
+ /** SQL statement to create test table accompanied by template specification. */
+ private static final String CREATE_SQL_WITH_TEMPLATE = CREATE_SQL + " WITH \"template=TPL\"";
+
+ /** Latches to block certain index operations. */
+ private static final ConcurrentHashMap<UUID, T3<CountDownLatch, AtomicBoolean, CountDownLatch>> BLOCKS =
+ new ConcurrentHashMap<>();
+
+ /** Cache mode. */
+ private final CacheMode cacheMode;
+
+ /** Atomicity mode. */
+ private final CacheAtomicityMode atomicityMode;
+
+ /**
+ * Constructor.
+ *
+ * @param cacheMode Cache mode.
+ * @param atomicityMode Atomicity mode.
+ */
+ DynamicColumnsAbstractConcurrentSelfTest(CacheMode cacheMode, CacheAtomicityMode atomicityMode) {
+ this.cacheMode = cacheMode;
+ this.atomicityMode = atomicityMode;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+
+ GridQueryProcessor.idxCls = BlockingIndexing.class;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("ConstantConditions")
+ @Override protected void afterTest() throws Exception {
+ GridQueryProcessor.idxCls = null;
+
+ for (T3<CountDownLatch, AtomicBoolean, CountDownLatch> block : BLOCKS.values())
+ block.get1().countDown();
+
+ BLOCKS.clear();
+
+ stopAllGrids();
+
+ super.afterTest();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected long getTestTimeout() {
+ return 5 * 60 * 1000L;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration commonConfiguration(int idx) throws Exception {
+ return super.commonConfiguration(idx).setDiscoverySpi(new TestTcpDiscoverySpi());
+ }
+
+ /**
+ * Make sure that coordinator migrates correctly between nodes.
+ *
+ * @throws Exception If failed.
+ */
+ public void testCoordinatorChange() throws Exception {
+ CountDownLatch finishLatch = new CountDownLatch(2);
+
+ // Start servers.
+ Ignite srv1 = ignitionStart(serverConfiguration(1), null);
+ Ignite srv2 = ignitionStart(serverConfiguration(2), null);
+ ignitionStart(serverConfiguration(3, true), finishLatch);
+
+ UUID srv1Id = srv1.cluster().localNode().id();
+ UUID srv2Id = srv2.cluster().localNode().id();
+
+ // Start client which will execute operations.
+ IgniteEx cli = (IgniteEx)ignitionStart(clientConfiguration(4), finishLatch);
+
+ createSqlCache(cli);
+
+ run(cli, CREATE_SQL_WITH_TEMPLATE);
+
+ // Test migration between normal servers.
+ CountDownLatch idxLatch = blockIndexing(srv1Id);
+
+ IgniteInternalFuture<?> colFut1 = addCols(cli, QueryUtils.DFLT_SCHEMA, c("age", Integer.class.getName()));
+
+ U.await(idxLatch);
+
+ //srv1.close();
+ Ignition.stop(srv1.name(), true);
+
+ unblockIndexing(srv1Id);
+
+ colFut1.get();
+
+ checkNodesState(TBL_NAME, c("age", Integer.class.getName()));
+
+ // Test migration from normal server to non-affinity server.
+ idxLatch = blockIndexing(srv2Id);
+
+ IgniteInternalFuture<?> colFut2 = addCols(cli, QueryUtils.DFLT_SCHEMA, c("city", String.class.getName()));
+
+ idxLatch.countDown();
+
+ //srv2.close();
+ Ignition.stop(srv2.name(), true);
+
+ U.await(idxLatch);
+
+ colFut2.get();
+
+ checkNodesState(TBL_NAME, c("city", String.class.getName()));
+ }
+
+ /**
+ * Test operations join.
+ *
+ * @throws Exception If failed.
+ */
+ public void testOperationChaining() throws Exception {
+ // 7 nodes * 2 columns = 14 latch countdowns.
+ CountDownLatch finishLatch = new CountDownLatch(14);
+
+ Ignite srv1 = ignitionStart(serverConfiguration(1), finishLatch);
+
+ ignitionStart(serverConfiguration(2), finishLatch);
+ ignitionStart(serverConfiguration(3, true), finishLatch);
+ ignitionStart(clientConfiguration(4), finishLatch);
+
+ createSqlCache(srv1);
+
+ run(srv1, CREATE_SQL_WITH_TEMPLATE);
+
+ CountDownLatch idxLatch = blockIndexing(srv1);
+
+ QueryField c1 = c("age", Integer.class.getName());
+ QueryField c2 = c("city", String.class.getName());
+
+ IgniteInternalFuture<?> colFut1 = addCols(srv1, QueryUtils.DFLT_SCHEMA, c1);
+
+ IgniteInternalFuture<?> colFut2 = addCols(srv1, QueryUtils.DFLT_SCHEMA, c2);
+
+ U.await(idxLatch);
+
+ // Start even more nodes of different flavors
+ ignitionStart(serverConfiguration(5), finishLatch);
+ ignitionStart(serverConfiguration(6, true), finishLatch);
+ ignitionStart(clientConfiguration(7), finishLatch);
+
+ assert !colFut1.isDone();
+ assert !colFut2.isDone();
+
+ unblockIndexing(srv1);
+
+ colFut1.get();
+ colFut2.get();
+
+ U.await(finishLatch);
+
+ checkNodesState(TBL_NAME, c1, c2);
+ }
+
+ /**
+ * Test node join on pending operation.
+ *
+ * @throws Exception If failed.
+ */
+ public void testNodeJoinOnPendingOperation() throws Exception {
+ CountDownLatch finishLatch = new CountDownLatch(4);
+
+ Ignite srv1 = ignitionStart(serverConfiguration(1), finishLatch);
+
+ createSqlCache(srv1);
+
+ run(srv1, CREATE_SQL_WITH_TEMPLATE);
+
+ CountDownLatch idxLatch = blockIndexing(srv1);
+
+ QueryField c = c("age", Integer.class.getName());
+
+ IgniteInternalFuture<?> idxFut = addCols(srv1, QueryUtils.DFLT_SCHEMA, c);
+
+ U.await(idxLatch);
+
+ ignitionStart(serverConfiguration(2), finishLatch);
+ ignitionStart(serverConfiguration(3, true), finishLatch);
+ ignitionStart(clientConfiguration(4), finishLatch);
+
+ assertFalse(idxFut.isDone());
+
+ unblockIndexing(srv1);
+
+ idxFut.get();
+
+ U.await(finishLatch);
+
+ checkNodesState(TBL_NAME, c);
+ }
+
+ /**
+ * PUT/REMOVE data from cache and add column concurrently.
+ *
+ * @throws Exception If failed,
+ */
+ public void testConcurrentPutRemove() throws Exception {
+ CountDownLatch finishLatch = new CountDownLatch(4);
+
+ // Start several nodes.
+ Ignite srv1 = ignitionStart(serverConfiguration(1), finishLatch);
+ ignitionStart(serverConfiguration(2), finishLatch);
+ ignitionStart(serverConfiguration(3), finishLatch);
+ ignitionStart(serverConfiguration(4), finishLatch);
+
+ awaitPartitionMapExchange();
+
+ createSqlCache(srv1);
+
+ run(srv1, CREATE_SQL_WITH_TEMPLATE);
+
+ // Start data change operations from several threads.
+ final AtomicBoolean stopped = new AtomicBoolean();
+
+ IgniteInternalFuture updateFut = multithreadedAsync(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ while (!stopped.get()) {
+ Ignite node = grid(ThreadLocalRandom.current().nextInt(1, 5));
+
+ int key = ThreadLocalRandom.current().nextInt(0, LARGE_CACHE_SIZE);
+ int val = ThreadLocalRandom.current().nextInt();
+
+ IgniteCache<BinaryObject, BinaryObject> cache = node.cache(CACHE_NAME);
+
+ if (ThreadLocalRandom.current().nextBoolean())
+ cache.put(key(node, key), val(node, val));
+ else
+ cache.remove(key(node, key));
+ }
+
+ return null;
+ }
+ }, 4);
+
+ // Let some to arrive.
+ Thread.sleep(500L);
+
+ addCols(srv1, QueryUtils.DFLT_SCHEMA, c("v", Integer.class.getName())).get();
+
+ // Stop updates once index is ready.
+ stopped.set(true);
+
+ updateFut.get();
+
+ finishLatch.await();
+
+ // Make sure new column is there.
+ checkNodesState(TBL_NAME, c("v", Integer.class.getName()));
+
+ run(srv1, "update person set \"v\" = case when mod(id, 2) <> 0 then substring(name, 7, length(name) - 6) " +
+ "else null end");
+
+ // Get expected values.
+ Set<Integer> expKeys = new HashSet<>();
+
+ IgniteCache<BinaryObject, BinaryObject> cache = srv1.cache(CACHE_NAME).withKeepBinary();
+
+ for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
+ BinaryObject key = key(srv1, i);
+
+ BinaryObject val = cache.get(key);
+
+ if (val != null) {
+ int id = key.field("ID");
+
+ assertEquals(i, id);
+
+ if (id % 2 != 0)
+ expKeys.add(i);
+ }
+ }
+
+ String valTypeName = ((IgniteEx)srv1).context().query().types(CACHE_NAME).iterator().next().valueTypeName();
+
+ // Validate query result.
+ for (Ignite node : Ignition.allGrids()) {
+ IgniteCache<BinaryObject, BinaryObject> nodeCache = node.cache(CACHE_NAME).withKeepBinary();
+
+ SqlQuery qry = new SqlQuery(valTypeName, "from " + TBL_NAME + " where mod(id, 2) <> 0");
+
+ List<Cache.Entry<BinaryObject, BinaryObject>> res = nodeCache.query(qry).getAll();
+
+ assertEquals("Cache size mismatch [exp=" + expKeys.size() + ", actual=" + res.size() + ']',
+ expKeys.size(), res.size());
+
+ for (Cache.Entry<BinaryObject, BinaryObject> entry : res) {
+ int key = entry.getKey().field("ID");
+ int v = entry.getValue().field("v");
+
+ String name = entry.getValue().field("NAME");
+
+ assertTrue("Expected key is not in result set: " + key, expKeys.contains(key));
+
+ assertEquals(Integer.parseInt(name.substring(6)), v);
+ }
+
+ }
+ }
+
+ /**
+ * @param node Node.
+ * @param val Number to form string field value from.
+ * @return PERSON cache value.
+ */
+ private BinaryObject val(Ignite node, int val) {
+ String valTypeName = ((IgniteEx)node).context().query().types(CACHE_NAME).iterator().next().valueTypeName();
+
+ return node.binary().builder(valTypeName).setField("name", "person" + val).build();
+ }
+
+ /**
+ * @param node Node.
+ * @param key Value for ID field.
+ * @return PERSON cache key.
+ */
+ private BinaryObject key(Ignite node, int key) {
+ String keyTypeName = ((IgniteEx)node).context().query().types(CACHE_NAME).iterator().next().keyTypeName();
+
+ return node.binary().builder(keyTypeName).setField("ID", key).build();
+ }
+
+ /**
+ * Test index consistency on re-balance.
+ *
+ * @throws Exception If failed.
+ */
+ public void testConcurrentRebalance() throws Exception {
+ // Start cache and populate it with data.
+ Ignite srv1 = ignitionStart(serverConfiguration(1));
+ Ignite srv2 = ignitionStart(serverConfiguration(2));
+
+ createSqlCache(srv1);
+
+ run(srv1, CREATE_SQL_WITH_TEMPLATE);
+
+ awaitPartitionMapExchange();
+
+ put(srv1, 0, LARGE_CACHE_SIZE);
+
+ // Start index operation in blocked state.
+ CountDownLatch idxLatch1 = blockIndexing(srv1);
+ CountDownLatch idxLatch2 = blockIndexing(srv2);
+
+ QueryField c = c("salary", Float.class.getName());
+
+ final IgniteInternalFuture<?> idxFut = addCols(srv1, QueryUtils.DFLT_SCHEMA, c);
+
+ U.await(idxLatch1);
+ U.await(idxLatch2);
+
+ // Start two more nodes and unblock index operation in the middle.
+ ignitionStart(serverConfiguration(3));
+
+ unblockIndexing(srv1);
+ unblockIndexing(srv2);
+
+ ignitionStart(serverConfiguration(4));
+
+ awaitPartitionMapExchange();
+
+ // Validate index state.
+ idxFut.get();
+
+ checkNodesState(TBL_NAME, c);
+ }
+
+ /**
+ * Put to cache keys and values for range from startIdx to endIdx.
+ * @param node Node.
+ * @param startIdx Starting index.
+ * @param endIdx Ending index.
+ */
+ private void put(Ignite node, int startIdx, int endIdx) {
+ for (int i = startIdx; i < endIdx; i++)
+ node.cache(CACHE_NAME).put(key(node, i), val(node, i));
+ }
+
+ /**
+ * Check what happens in case cache is destroyed before operation is started.
+ *
+ * @throws Exception If failed.
+ */
+ public void testConcurrentCacheDestroy() throws Exception {
+ // Start complex topology.
+ Ignite srv1 = ignitionStart(serverConfiguration(1));
+
+ ignitionStart(serverConfiguration(2));
+ ignitionStart(serverConfiguration(3, true));
+
+ Ignite cli = ignitionStart(clientConfiguration(4));
+
+ // Start cache and populate it with data.
+ createSqlCache(cli);
+
+ run(cli, CREATE_SQL_WITH_TEMPLATE);
+
+ put(cli, 0, LARGE_CACHE_SIZE);
+
+ // Start index operation and block it on coordinator.
+ CountDownLatch idxLatch = blockIndexing(srv1);
+
+ QueryField c = c("city", String.class.getName());
+
+ final IgniteInternalFuture<?> idxFut = addCols(srv1, QueryUtils.DFLT_SCHEMA, c);
+
+ idxLatch.await();
+
+ // Destroy cache (drop table).
+ run(cli, DROP_SQL);
+
+ // Unblock indexing and see what happens.
+ unblockIndexing(srv1);
+
+ try {
+ idxFut.get();
+
+ fail("Exception has not been thrown.");
+ }
+ catch (SchemaOperationException e) {
+ // No-op.
+ }
+ }
+
+ /**
+ * Make sure that contended operations on the same index from different nodes do not hang when we issue both
+ * CREATE/DROP and SELECT statements.
+ *
+ * @throws Exception If failed.
+ */
+ public void testQueryConsistencyMultithreaded() throws Exception {
+ // Start complex topology.
+ ignitionStart(serverConfiguration(1));
+ ignitionStart(serverConfiguration(2));
+ ignitionStart(serverConfiguration(3, true));
+
+ Ignite cli = ignitionStart(clientConfiguration(4));
+
+ createSqlCache(cli);
+
+ run(cli, CREATE_SQL_WITH_TEMPLATE);
+
+ put(cli, 0, 5000);
+
+ final AtomicBoolean stopped = new AtomicBoolean();
+
+ final AtomicInteger dynColCnt = new AtomicInteger();
+
+ IgniteInternalFuture fut = multithreadedAsync(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ while (!stopped.get()) {
+ Ignite node = grid(ThreadLocalRandom.current().nextInt(1, 5));
+
+ IgniteInternalFuture fut = addCols(node, QueryUtils.DFLT_SCHEMA, c("newCol" + dynColCnt.getAndIncrement(),
+ Integer.class.getName()));
+
+ try {
+ fut.get();
+ }
+ catch (SchemaOperationException e) {
+ // No-op.
+ }
+ catch (Exception e) {
+ fail("Unexpected exception: " + e);
+ }
+ }
+
+ return null;
+ }
+ }, 1);
+
+ IgniteInternalFuture qryFut = multithreadedAsync(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ while (!stopped.get()) {
+ Ignite node = grid(ThreadLocalRandom.current().nextInt(1, 5));
+
+ IgniteCache<BinaryObject, BinaryObject> cache = node.cache(CACHE_NAME).withKeepBinary();
+
+ String valTypeName = ((IgniteEx)node).context().query().types(CACHE_NAME)
+ .iterator().next().valueTypeName();
+
+ List<Cache.Entry<BinaryObject, BinaryObject>> res = cache.query(
+ new SqlQuery<BinaryObject, BinaryObject>(valTypeName, "from " + TBL_NAME)).getAll();
+
+ assertEquals(5000, res.size());
+ }
+
+ return null;
+ }
+ }, 8);
+
+ Thread.sleep(TEST_DUR);
+
+ stopped.set(true);
+
+ // Make sure nothing hanged.
+ fut.get();
+ qryFut.get();
+ }
+
+ /**
+ * Make sure that client receives schema changes made while it was disconnected.
+ *
+ * @throws Exception If failed.
+ */
+ public void testClientReconnect() throws Exception {
+ checkClientReconnect(false, true);
+ }
+
+ /**
+ * Make sure that client receives schema changes made while it was disconnected, even with cache recreation.
+ *
+ * @throws Exception If failed.
+ */
+ public void testClientReconnectWithCacheRestart() throws Exception {
+ checkClientReconnect(true, true);
+ }
+
+ /**
+ * Make sure that client receives schema changes made while it was disconnected.
+ *
+ * @throws Exception If failed.
+ */
+ public void testClientReconnectWithNonDynamicCache() throws Exception {
+ checkClientReconnect(false, false);
+ }
+
+ /**
+ * Make sure that client receives schema changes made while it was disconnected, even with cache recreation.
+ *
+ * @throws Exception If failed.
+ */
+ public void testClientReconnectWithNonDynamicCacheRestart() throws Exception {
+ checkClientReconnect(true, false);
+ }
+
+ /**
+ * Make sure that client receives schema changes made while it was disconnected, optionally with cache restart
+ * in the interim.
+ *
+ * @param restartCache Whether cache needs to be recreated during client's absence.
+ * @param dynamicCache Whether recreate, if needed, should be done on dynamic or static cache.
+ * @throws Exception If failed.
+ */
+ private void checkClientReconnect(final boolean restartCache, boolean dynamicCache) throws Exception {
+ // Start complex topology.
+ final Ignite srv = ignitionStart(serverConfiguration(1));
+ ignitionStart(serverConfiguration(2));
+ ignitionStart(serverConfiguration(3, true));
+
+ final Ignite cli = ignitionStart(clientConfiguration(4));
+
+ if (dynamicCache) {
+ createSqlCache(cli);
+
+ run(cli, CREATE_SQL_WITH_TEMPLATE);
+ }
+
+ final String schemaName = dynamicCache ? QueryUtils.DFLT_SCHEMA : "idx";
+
+ final QueryField[] cols =
+ new QueryField[] { c("age", Integer.class.getName()), c("city", String.class.getName()) };
+
+ // Check index create.
+ reconnectClientNode(srv, cli, restartCache, dynamicCache, new RunnableX() {
+ @Override public void run() throws Exception {
+ addCols(srv, schemaName, cols).get();
+ }
+ });
+
+ checkNodeState((IgniteEx)cli, schemaName, TBL_NAME, cols);
+ }
+
+ /**
+ * Reconnect the client and run specified actions while it's out.
+ *
+ * @param srvNode Server node.
+ * @param cliNode Client node.
+ * @param restart Whether cache has to be recreated prior to executing required actions.
+ * @param dynamicCache Whether recreate, if needed, should be done on dynamic or static cache.
+ * @param clo Closure to run
+ * @throws Exception If failed.
+ */
+ private void reconnectClientNode(final Ignite srvNode, final Ignite cliNode, final boolean restart,
+ final boolean dynamicCache, final RunnableX clo) throws Exception {
+ IgniteClientReconnectAbstractTest.reconnectClientNode(log, cliNode, srvNode, new Runnable() {
+ @Override public void run() {
+ if (restart) {
+ if (dynamicCache) {
+ DynamicColumnsAbstractConcurrentSelfTest.this.run(srvNode, DROP_SQL);
+
+ DynamicColumnsAbstractConcurrentSelfTest.this.run(srvNode, CREATE_SQL_WITH_TEMPLATE);
+ }
+ else {
+ srvNode.destroyCache("idx");
+
+ CacheConfiguration ccfg;
+
+ try {
+ ccfg = clientConfiguration(0).getCacheConfiguration()[0];
+ }
+ catch (Exception e) {
+ throw new AssertionError(e);
+ }
+
+ srvNode.createCache(ccfg);
+ }
+ }
+
+ try {
+ clo.run();
+ }
+ catch (Exception e) {
+ throw new IgniteException("Test reconnect runnable failed.", e);
+ }
+ }
+ });
+
+ if (restart)
+ cliNode.cache(CACHE_NAME);
+ }
+
+ /**
+ * Test concurrent node start/stop along with index operations. Nothing should hang.
+ *
+ * @throws Exception If failed.
+ */
+ @SuppressWarnings("StringConcatenationInLoop")
+ public void testConcurrentOperationsAndNodeStartStopMultithreaded() throws Exception {
+ // Start several stable nodes.
+ ignitionStart(serverConfiguration(1));
+ ignitionStart(serverConfiguration(2));
+ ignitionStart(serverConfiguration(3, true));
+
+ final Ignite cli = ignitionStart(clientConfiguration(4));
+
+ createSqlCache(cli);
+
+ run(cli, CREATE_SQL_WITH_TEMPLATE);
+
+ final AtomicBoolean stopped = new AtomicBoolean();
+
+ // Start node start/stop worker.
+ final AtomicInteger nodeIdx = new AtomicInteger(4);
+
+ final AtomicInteger dynColCnt = new AtomicInteger();
+
+ IgniteInternalFuture startStopFut = multithreadedAsync(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ boolean exists = false;
+
+ int lastIdx = 0;
+
+ while (!stopped.get()) {
+ if (exists) {
+ stopGrid(lastIdx);
+
+ exists = false;
+ }
+ else {
+ lastIdx = nodeIdx.incrementAndGet();
+
+ IgniteConfiguration cfg;
+
+ switch (ThreadLocalRandom.current().nextInt(0, 3)) {
+ case 1:
+ cfg = serverConfiguration(lastIdx, false);
+
+ break;
+
+ case 2:
+
+ cfg = serverConfiguration(lastIdx, true);
+
+ break;
+
+ default:
+ cfg = clientConfiguration(lastIdx);
+ }
+
+ ignitionStart(cfg);
+
+ exists = true;
+ }
+
+ Thread.sleep(ThreadLocalRandom.current().nextLong(500L, 1500L));
+ }
+
+ return null;
+ }
+ }, 1);
+
+ IgniteInternalFuture idxFut = multithreadedAsync(new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ while (!stopped.get()) {
+ Ignite node = grid(ThreadLocalRandom.current().nextInt(1, 5));
+
+ IgniteInternalFuture fut = addCols(node, QueryUtils.DFLT_SCHEMA, c("newCol" + dynColCnt.getAndIncrement(),
+ Integer.class.getName()));
+
+ try {
+ fut.get();
+ }
+ catch (SchemaOperationException e) {
+ // No-op.
+ }
+ catch (Exception e) {
+ fail("Unexpected exception: " + e);
+ }
+ }
+
+ return null;
+ }
+ }, 1);
+
+ Thread.sleep(TEST_DUR);
+
+ stopped.set(true);
+
+ // Make sure nothing hanged.
+ startStopFut.get();
+ idxFut.get();
+
+ // Make sure cache is operational at this point.
+ createSqlCache(cli);
+
+ QueryField[] expCols = new QueryField[dynColCnt.get()];
+
+ // Too many index columns kills indexing internals, have to limit number of the columns
+ // to build the index on.
+ int idxColsCnt = Math.min(300, expCols.length);
+
+ Integer[] args = new Integer[idxColsCnt];
+
+ String updQry = "UPDATE " + TBL_NAME + " SET ";
+
+ String idxQry = "CREATE INDEX idx ON " + TBL_NAME + '(';
+
+ for (int i = 0; i < expCols.length; i++) {
+ expCols[i] = c("newCol" + i, Integer.class.getName());
+
+ if (i >= idxColsCnt)
+ continue;
+
+ if (i > 0) {
+ updQry += ", ";
+
+ idxQry += ", ";
+ }
+
+ updQry += "\"newCol" + i + "\" = id + ?";
+
+ idxQry += "\"newCol" + i + '"';
+
+ args[i] = i;
+ }
+
+ idxQry += ')';
+
+ checkNodesState(TBL_NAME, expCols);
+
+ put(cli, 0, 500);
+
+ run(cli.cache(CACHE_NAME), updQry, (Object[])args);
+
+ run(cli, idxQry);
+
+ run(cli, "DROP INDEX idx");
+ }
+
+ /**
+ * Block indexing.
+ *
+ * @param node Node.
+ */
+ @SuppressWarnings("SuspiciousMethodCalls")
+ private static CountDownLatch blockIndexing(Ignite node) {
+ UUID nodeId = ((IgniteEx)node).localNode().id();
+
+ return blockIndexing(nodeId);
+ }
+
+ /**
+ * Block indexing.
+ *
+ * @param nodeId Node.
+ */
+ @SuppressWarnings("SuspiciousMethodCalls")
+ private static CountDownLatch blockIndexing(UUID nodeId) {
+ assertFalse(BLOCKS.contains(nodeId));
+
+ CountDownLatch idxLatch = new CountDownLatch(1);
+
+ BLOCKS.put(nodeId, new T3<>(new CountDownLatch(1), new AtomicBoolean(), idxLatch));
+
+ return idxLatch;
+ }
+
+ /**
+ * Unblock indexing.
+ *
+ * @param node Node.
+ */
+ private static void unblockIndexing(Ignite node) {
+ UUID nodeId = ((IgniteEx)node).localNode().id();
+
+ unblockIndexing(nodeId);
+ }
+
+ /**
+ * Unblock indexing.
+ *
+ * @param nodeId Node ID.
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static void unblockIndexing(UUID nodeId) {
+ T3<CountDownLatch, AtomicBoolean, CountDownLatch> blocker = BLOCKS.remove(nodeId);
+
+ assertNotNull(blocker);
+
+ blocker.get1().countDown();
+ }
+
+ /**
+ * Await indexing.
+ *
+ * @param nodeId Node ID.
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static void awaitIndexing(UUID nodeId) {
+ T3<CountDownLatch, AtomicBoolean, CountDownLatch> blocker = BLOCKS.get(nodeId);
+
+ if (blocker != null) {
+ assertTrue(blocker.get2().compareAndSet(false, true));
+
+ blocker.get3().countDown();
+
+ while (true) {
+ try {
+ blocker.get1().await();
+
+ break;
+ }
+ catch (InterruptedException e) {
+ // No-op.
+ }
+ }
+ }
+ }
+
+ /**
+ * Blocking indexing processor.
+ */
+ private static class BlockingIndexing extends IgniteH2Indexing {
+ /** {@inheritDoc} */
+ @Override public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols,
+ boolean ifTblExists, boolean ifColNotExists)
+ throws IgniteCheckedException {
+ awaitIndexing(ctx.localNodeId());
+
+ super.dynamicAddColumn(schemaName, tblName, cols, ifTblExists, ifColNotExists);
+ }
+ }
+
+ /**
+ *
+ * @param node Target node.
+ * @param schemaName Schema name.
+ * @param flds Columns to add.
+ * @return DDL operation future.
+ */
+ private static IgniteInternalFuture<?> addCols(Ignite node, String schemaName, QueryField... flds) {
+ final String cacheName = F.eq(schemaName, QueryUtils.DFLT_SCHEMA) ? CACHE_NAME : "idx";
+
+ return ((IgniteEx)node).context().query().dynamicColumnAdd(cacheName, schemaName, TBL_NAME,
+ Arrays.asList(flds), false, false);
+ }
+
+ /**
+ * Start SQL cache on given node.
+ * @param node Node to create cache on.
+ * @return Created cache.
+ */
+ private IgniteCache<?, ?> createSqlCache(Ignite node) throws IgniteCheckedException {
+ node.addCacheConfiguration(new CacheConfiguration<>("TPL")
+ .setCacheMode(cacheMode)
+ .setAtomicityMode(atomicityMode)
+ .setNodeFilter(new NodeFilter()));
+
+ return node.getOrCreateCache(new CacheConfiguration<>("idx").setIndexedTypes(Integer.class, Integer.class));
+ }
+
+ /**
+ * Spoof blocking indexing class and start new node.
+ * @param cfg Node configuration.
+ * @return New node.
+ */
+ private static Ignite ignitionStart(IgniteConfiguration cfg) {
+ return ignitionStart(cfg, null);
+ }
+
+ /**
+ * Spoof blocking indexing class and start new node.
+ * @param cfg Node configuration.
+ * @param latch Latch to await for ultimate completion of DDL operations.
+ * @return New node.
+ */
+ private static Ignite ignitionStart(IgniteConfiguration cfg, final CountDownLatch latch) {
+ // Have to do this for each starting node - see GridQueryProcessor ctor, it nulls
+ // idxCls static field on each call.
+ GridQueryProcessor.idxCls = BlockingIndexing.class;
+
+ IgniteEx node = (IgniteEx)Ignition.start(cfg);
+
+ if (latch != null)
+ node.context().discovery().setCustomEventListener(SchemaFinishDiscoveryMessage.class,
+ new CustomEventListener<SchemaFinishDiscoveryMessage>() {
+ @Override public void onCustomEvent(AffinityTopologyVersion topVer, ClusterNode snd,
+ SchemaFinishDiscoveryMessage msg) {
+ latch.countDown();
+ }
+ });
+
+ return node;
+ }
+
+ /**
+ * Create server configuration.
+ * @param nodeIdx Node index.
+ * @param filtered Whether this node should not be treated as affinity node.
+ * @return Configuration.
+ * @throws Exception if failed.
+ */
+ private IgniteConfiguration serverConfiguration(int nodeIdx, boolean filtered) throws Exception {
+ IgniteConfiguration cfg = serverConfiguration(nodeIdx);
+
+ if (filtered)
+ cfg.setUserAttributes(Collections.singletonMap(ATTR_FILTERED, true));
+
+ return cfg;
+ }
+
+ /**
+ * Runnable which can throw checked exceptions.
+ */
+ interface RunnableX {
+ /**
+ * Do run.
+ *
+ * @throws Exception If failed.
+ */
+ @SuppressWarnings("UnnecessaryInterfaceModifier")
+ public void run() throws Exception;
+ }
+
+ /**
+ * Node filter.
+ */
+ protected static class NodeFilter implements IgnitePredicate<ClusterNode>, Serializable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override public boolean apply(ClusterNode node) {
+ return node.attribute(ATTR_FILTERED) == null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractTest.java
new file mode 100644
index 0000000..75b9a30
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsAbstractTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.MemoryConfiguration;
+import org.apache.ignite.configuration.MemoryPolicyConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.QuerySchema;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.util.typedef.F;
+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 org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.h2.table.Column;
+import org.h2.value.DataType;
+
+/**
+ * Common stuff for dynamic columns tests.
+ */
+public abstract class DynamicColumnsAbstractTest extends GridCommonAbstractTest {
+ /** SQL to create test table. */
+ final static String CREATE_SQL = "CREATE TABLE IF NOT EXISTS Person (id int primary key, name varchar)";
+
+ /** SQL to drop test table. */
+ final static String DROP_SQL = "DROP TABLE Person";
+
+ /** IP finder. */
+ private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+ /**
+ * Check that given columns have been added to all related structures on target node exactly where needed
+ * (namely, schema in cache descriptor, type descriptor on started cache, and H2 state on started cache).
+ * @param node Target node.
+ * @param schemaName Schema name to look for the table in.
+ * @param tblName Table name to check.
+ * @param cols Columns whose presence must be checked.
+ */
+ static void checkNodeState(IgniteEx node, String schemaName, String tblName, QueryField... cols) {
+ String cacheName = F.eq(schemaName, QueryUtils.DFLT_SCHEMA) ?
+ QueryUtils.createTableCacheName(schemaName, tblName) : schemaName;
+
+ // Schema state check - should pass regardless of cache state.
+ {
+ DynamicCacheDescriptor desc = node.context().cache().cacheDescriptor(cacheName);
+
+ assertNotNull("Cache descriptor not found", desc);
+
+ assertTrue(desc.sql() == F.eq(schemaName, QueryUtils.DFLT_SCHEMA));
+
+ QuerySchema schema = desc.schema();
+
+ assertNotNull(schema);
+
+ QueryEntity entity = null;
+
+ for (QueryEntity e : schema.entities()) {
+ if (F.eq(tblName, e.getTableName())) {
+ entity = e;
+
+ break;
+ }
+ }
+
+ assertNotNull("Query entity not found", entity);
+
+ Iterator<Map.Entry<String, String>> it = entity.getFields().entrySet().iterator();
+
+ for (int i = entity.getFields().size() - cols.length; i > 0 && it.hasNext(); i--)
+ it.next();
+
+ for (QueryField col : cols) {
+ assertTrue("New column not found in query entity: " + col.name(), it.hasNext());
+
+ Map.Entry<String, String> e = it.next();
+
+ assertEquals(col.name(), e.getKey());
+
+ assertEquals(col.typeName(), e.getValue());
+ }
+ }
+
+ // Start cache on this node if we haven't yet.
+ node.cache(cacheName);
+
+ // Type descriptor state check.
+ {
+ Collection<GridQueryTypeDescriptor> descs = node.context().query().types(cacheName);
+
+ GridQueryTypeDescriptor desc = null;
+
+ for (GridQueryTypeDescriptor d : descs) {
+ if (F.eq(tblName, d.tableName())) {
+ desc = d;
+
+ break;
+ }
+ }
+
+ assertNotNull("Type descriptor not found", desc);
+
+ Iterator<Map.Entry<String, Class<?>>> it = desc.fields().entrySet().iterator();
+
+ for (int i = desc.fields().size() - cols.length; i > 0 && it.hasNext(); i--)
+ it.next();
+
+ for (QueryField col : cols) {
+ assertTrue("New column not found in type descriptor: " + col.name(), it.hasNext());
+
+ Map.Entry<String, Class<?>> e = it.next();
+
+ assertEquals(col.name(), e.getKey());
+
+ assertEquals(col.typeName(), e.getValue().getName());
+ }
+ }
+
+ // H2 table state check.
+ {
+ GridH2Table tbl = ((IgniteH2Indexing)node.context().query().getIndexing()).dataTable(schemaName,
+ tblName);
+
+ assertNotNull("Table not found", tbl);
+
+ Iterator<Column> colIt = Arrays.asList(tbl.getColumns()).iterator();
+
+ GridH2RowDescriptor rowDesc = tbl.rowDescriptor();
+
+ int i = 0;
+
+ for (; i < tbl.getColumns().length - cols.length && colIt.hasNext(); i++)
+ colIt.next();
+
+ for (QueryField col : cols) {
+ assertTrue("New column not found in H2 table: " + col.name(), colIt.hasNext());
+
+ assertTrue(colIt.hasNext());
+
+ Column c = colIt.next();
+
+ assertEquals(col.name(), c.getName());
+
+ assertEquals(col.typeName(), DataType.getTypeClassName(c.getType()));
+
+ assertFalse(rowDesc.isKeyValueOrVersionColumn(i));
+
+ try {
+ assertEquals(DataType.getTypeFromClass(Class.forName(col.typeName())),
+ rowDesc.fieldType(i - GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT));
+ }
+ catch (ClassNotFoundException e) {
+ throw new AssertionError(e);
+ }
+
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Check that given columns have been added to all related structures on all started nodes (namely, schema
+ * in cache descriptor, type descriptor on started cache, and H2 state on started cache).
+ * @param tblName Table name to check.
+ * @param cols Columns whose presence must be checked.
+ */
+ static void checkNodesState(String tblName, QueryField... cols) {
+ for (Ignite node : Ignition.allGrids())
+ checkNodeState((IgniteEx)node, QueryUtils.DFLT_SCHEMA, tblName, cols);
+ }
+
+ /**
+ * @param name New column name.
+ * @param typeName Class name for this new column's data type.
+ * @return New column with given name and type.
+ */
+ protected static QueryField c(String name, String typeName) {
+ return new QueryField(name, typeName);
+ }
+
+ /**
+ * @param idx Node index.
+ * @return Client configuration.
+ * @throws Exception if failed.
+ */
+ protected IgniteConfiguration clientConfiguration(int idx) throws Exception {
+ QueryEntity e = new QueryEntity(Integer.class.getName(), "Person");
+
+ LinkedHashMap<String, String> flds = new LinkedHashMap<>();
+
+ flds.put("name", String.class.getName());
+
+ e.setFields(flds);
+
+ return commonConfiguration(idx).setClientMode(true).setCacheConfiguration(
+ new CacheConfiguration<>("idx").setQueryEntities(Collections.singletonList(e))
+ );
+ }
+
+ /**
+ * Create common node configuration.
+ *
+ * @param idx Index.
+ * @return Configuration.
+ * @throws Exception If failed.
+ */
+ protected IgniteConfiguration commonConfiguration(int idx) throws Exception {
+ IgniteConfiguration cfg = getConfiguration(getTestIgniteInstanceName(idx));
+
+ cfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(IP_FINDER));
+
+ MemoryConfiguration memCfg = new MemoryConfiguration()
+ .setDefaultMemoryPolicyName("default")
+ .setMemoryPolicies(
+ new MemoryPolicyConfiguration()
+ .setName("default")
+ .setMaxSize(32 * 1024 * 1024L)
+ .setInitialSize(32 * 1024 * 1024L)
+ );
+
+ cfg.setMemoryConfiguration(memCfg);
+
+ return optimize(cfg);
+ }
+
+ /**
+ * Create server node configuration.
+ *
+ * @param idx Index.
+ * @return Configuration.
+ * @throws Exception If failed.
+ */
+ IgniteConfiguration serverConfiguration(int idx) throws Exception {
+ return commonConfiguration(idx);
+ }
+
+ /**
+ * Execute SQL command and return resulting dataset.
+ * @param node Node to run query from.
+ * @param sql Statement.
+ * @return result.
+ */
+ protected List<List<?>> run(Ignite node, String sql) {
+ return ((IgniteEx)node).context().query()
+ .querySqlFieldsNoCache(new SqlFieldsQuery(sql).setSchema(QueryUtils.DFLT_SCHEMA), true).getAll();
+ }
+
+ /**
+ * Execute SQL command and return resulting dataset.
+ * @param cache Cache to initiate query from.
+ * @param sql Statement.
+ * @return result.
+ */
+ protected List<List<?>> run(IgniteCache<?, ?> cache, String sql, Object... args) {
+ return cache.query(new SqlFieldsQuery(sql).setSchema(QueryUtils.DFLT_SCHEMA).setArgs(args)).getAll();
+ }
+
+ /**
+ * Run specified statement expected to throw {@code IgniteSqlException} with expected specified message.
+ * @param sql Statement.
+ * @param msg Expected message.
+ */
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ protected void assertThrows(final Ignite node, final String sql, String msg) {
+ GridTestUtils.assertThrows(log, new Callable<Object>() {
+ @Override public Object call() throws Exception {
+ run(node, sql);
+
+ return null;
+ }
+ }, IgniteSQLException.class, msg);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicPartitionedSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicPartitionedSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicPartitionedSelfTest.java
new file mode 100644
index 0000000..ca68903
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicPartitionedSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/**
+ * Test to check concurrent operations on dynamic columns on ATOMIC PARTITIONED cache.
+ */
+public class DynamicColumnsConcurrentAtomicPartitionedSelfTest extends DynamicColumnsAbstractConcurrentSelfTest {
+ /**
+ * Constructor.
+ */
+ public DynamicColumnsConcurrentAtomicPartitionedSelfTest() {
+ super(CacheMode.PARTITIONED, CacheAtomicityMode.ATOMIC);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicReplicatedSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicReplicatedSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicReplicatedSelfTest.java
new file mode 100644
index 0000000..9a3a32c
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentAtomicReplicatedSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/**
+ * Test to check concurrent operations on dynamic columns on ATOMIC REPLICATED cache.
+ */
+public class DynamicColumnsConcurrentAtomicReplicatedSelfTest extends DynamicColumnsAbstractConcurrentSelfTest {
+ /**
+ * Constructor.
+ */
+ public DynamicColumnsConcurrentAtomicReplicatedSelfTest() {
+ super(CacheMode.REPLICATED, CacheAtomicityMode.ATOMIC);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalPartitionedSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalPartitionedSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalPartitionedSelfTest.java
new file mode 100644
index 0000000..f42a447
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalPartitionedSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/**
+ * Test to check concurrent operations on dynamic columns on TRANSACTIONAL PARTITIONED cache.
+ */
+public class DynamicColumnsConcurrentTransactionalPartitionedSelfTest extends DynamicColumnsAbstractConcurrentSelfTest {
+ /**
+ * Constructor.
+ */
+ public DynamicColumnsConcurrentTransactionalPartitionedSelfTest() {
+ super(CacheMode.PARTITIONED, CacheAtomicityMode.TRANSACTIONAL);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalReplicatedSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalReplicatedSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalReplicatedSelfTest.java
new file mode 100644
index 0000000..2b53e42
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/DynamicColumnsConcurrentTransactionalReplicatedSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+
+/**
+ * Test to check concurrent operations on dynamic columns on TRANSACTIONAL REPLICATED cache.
+ */
+public class DynamicColumnsConcurrentTransactionalReplicatedSelfTest extends DynamicColumnsAbstractConcurrentSelfTest {
+ /**
+ * Constructor.
+ */
+ public DynamicColumnsConcurrentTransactionalReplicatedSelfTest() {
+ super(CacheMode.REPLICATED, CacheAtomicityMode.TRANSACTIONAL);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsAbstractBasicSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsAbstractBasicSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsAbstractBasicSelfTest.java
new file mode 100644
index 0000000..651a3f7
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsAbstractBasicSelfTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+
+/**
+ * Test to check dynamic columns related features.
+ */
+public abstract class H2DynamicColumnsAbstractBasicSelfTest extends DynamicColumnsAbstractTest {
+ /**
+ * Index of coordinator node.
+ */
+ final static int SRV_CRD_IDX = 0;
+
+ /**
+ * Index of non coordinator server node.
+ */
+ final static int SRV_IDX = 1;
+
+ /**
+ * Index of client.
+ */
+ final static int CLI_IDX = 2;
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ for (IgniteConfiguration cfg : configurations())
+ Ignition.start(cfg);
+ }
+
+ /**
+ * @return Grid configurations to start.
+ * @throws Exception if failed.
+ */
+ private IgniteConfiguration[] configurations() throws Exception {
+ return new IgniteConfiguration[] {
+ commonConfiguration(0),
+ commonConfiguration(1),
+ clientConfiguration(2)
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTestsStopped() throws Exception {
+ stopAllGrids();
+
+ super.afterTestsStopped();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+
+ run(CREATE_SQL);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ run(DROP_SQL);
+
+ super.afterTest();
+ }
+
+ /**
+ * Test column addition to the end of the columns list.
+ */
+ public void testAddColumnSimple() {
+ run("ALTER TABLE Person ADD COLUMN age int");
+
+ doSleep(500);
+
+ QueryField c = c("AGE", Integer.class.getName());
+
+ for (Ignite node : Ignition.allGrids())
+ checkNodeState((IgniteEx)node, QueryUtils.DFLT_SCHEMA, "PERSON", c);
+ }
+
+ /**
+ * Test column addition to the end of the columns list.
+ */
+ public void testAddFewColumnsSimple() {
+ run("ALTER TABLE Person ADD COLUMN (age int, \"city\" varchar)");
+
+ doSleep(500);
+
+ for (Ignite node : Ignition.allGrids())
+ checkNodeState((IgniteEx)node, QueryUtils.DFLT_SCHEMA, "PERSON",
+ c("AGE", Integer.class.getName()),
+ c("city", String.class.getName()));
+ }
+
+ /**
+ * Test {@code IF EXISTS} handling.
+ */
+ public void testIfTableExists() {
+ run("ALTER TABLE if exists City ADD COLUMN population int");
+ }
+
+ /**
+ * Test {@code IF NOT EXISTS} handling.
+ */
+ public void testIfColumnNotExists() {
+ run("ALTER TABLE Person ADD COLUMN if not exists name varchar");
+ }
+
+ /**
+ * Test {@code IF NOT EXISTS} handling.
+ */
+ public void testDuplicateColumnName() {
+ assertThrows("ALTER TABLE Person ADD COLUMN name varchar", "Column already exists: NAME");
+ }
+
+ /**
+ * Test behavior in case of missing table.
+ */
+ public void testMissingTable() {
+ assertThrows("ALTER TABLE City ADD COLUMN name varchar", "Table doesn't exist: CITY");
+ }
+
+ /** */
+ @SuppressWarnings("unchecked")
+ public void testComplexOperations() {
+ IgniteCache<BinaryObject, BinaryObject> cache = ignite(nodeIndex())
+ .cache(QueryUtils.createTableCacheName(QueryUtils.DFLT_SCHEMA, "PERSON"));
+
+ run(cache, "ALTER TABLE Person ADD COLUMN city varchar");
+
+ run(cache, "INSERT INTO Person (id, name, city) values (1, 'John Doe', 'New York')");
+
+ run(cache, "INSERT INTO Person (id, name, city) values (2, 'Mike Watts', 'Denver')");
+
+ run(cache, "INSERT INTO Person (id, name, city) values (3, 'Ann Pierce', 'New York')");
+
+ run(cache, "CREATE INDEX pidx1 ON Person(name, city desc)");
+
+ CacheConfiguration<Integer, City> ccfg = defaultCacheConfiguration().setName("City")
+ .setIndexedTypes(Integer.class, City.class).setSqlSchema(QueryUtils.DFLT_SCHEMA);
+
+ ccfg.getQueryEntities().iterator().next().setKeyFieldName("id");
+
+ ignite(nodeIndex()).getOrCreateCache(ccfg);
+
+ run(cache, "ALTER TABLE City ADD COLUMN population int");
+
+ run(cache, "CREATE INDEX cidx1 ON City(population)");
+
+ run(cache, "CREATE INDEX cidx2 ON City(name)");
+
+ run(cache, "INSERT INTO City(id, name, population, state) values (5, 'New York', 15000000, 'New York')," +
+ "(7, 'Denver', 3000000, 'Colorado')");
+
+ List<List<?>> res = run(cache, "SELECT p.name from Person p join City c on p.city = c.name where " +
+ "c.population > 5000000 order by p.name");
+
+ assertEquals(2, res.size());
+
+ assertEquals(Collections.singletonList("Ann Pierce"), res.get(0));
+
+ assertEquals(Collections.singletonList("John Doe"), res.get(1));
+
+ run(cache, "ALTER TABLE Person ADD COLUMN age int");
+
+ run(cache, "UPDATE Person SET age = (5 - id) * 10");
+
+ res = run(cache, "SELECT p.name from Person p join City c on p.city = c.name where " +
+ "c.population > 5000000 and age < 40");
+
+ assertEquals(1, res.size());
+
+ assertEquals(Collections.singletonList("Ann Pierce"), res.get(0));
+
+ run(cache, "CREATE INDEX pidx2 on Person(age desc)");
+
+ run(cache, "DROP INDEX pidx2");
+
+ run(cache, "DROP INDEX pidx1");
+
+ run(cache, "DROP INDEX cidx2");
+
+ run(cache, "DROP INDEX cidx1");
+
+ run(cache, "DELETE FROM Person where age > 10");
+
+ assertEquals(0, cache.size());
+
+ ignite(nodeIndex()).destroyCache("City");
+ }
+
+ /**
+ * Test that we can add columns dynamically to tables associated with non dynamic caches as well.
+ */
+ public void testAddColumnToNonDynamicCache() {
+ run("ALTER TABLE \"idx\".PERSON ADD COLUMN CITY varchar");
+
+ doSleep(500);
+
+ QueryField c = c("CITY", String.class.getName());
+
+ for (Ignite node : Ignition.allGrids())
+ checkNodeState((IgniteEx)node, "idx", "PERSON", c);
+ }
+
+ /**
+ * Test that we can add columns dynamically to tables associated with non dynamic caches storing user types as well.
+ */
+ @SuppressWarnings("unchecked")
+ public void testAddColumnToNonDynamicCacheWithRealValueType() {
+ CacheConfiguration<Integer, City> ccfg = defaultCacheConfiguration().setName("City")
+ .setIndexedTypes(Integer.class, City.class);
+
+ IgniteCache<Integer, City> cache = ignite(nodeIndex()).getOrCreateCache(ccfg);
+
+ run(cache, "ALTER TABLE \"City\".City ADD COLUMN population int");
+
+ doSleep(500);
+
+ QueryField c = c("POPULATION", Integer.class.getName());
+
+ for (Ignite node : Ignition.allGrids())
+ checkNodeState((IgniteEx)node, "City", "CITY", c);
+
+ run(cache, "INSERT INTO \"City\".City (_key, id, name, state, population) values " +
+ "(1, 1, 'Washington', 'DC', 2500000)");
+
+ List<List<?>> res = run(cache, "select _key, id, name, state, population from \"City\".City");
+
+ assertEquals(Collections.singletonList(Arrays.asList(1, 1, "Washington", "DC", 2500000)), res);
+
+ City city = cache.get(1);
+
+ assertEquals(1, city.id());
+ assertEquals("Washington", city.name());
+ assertEquals("DC", city.state());
+
+ cache.destroy();
+ }
+
+ /**
+ * @return Node index to run queries on.
+ */
+ protected abstract int nodeIndex();
+
+ /**
+ * Run specified statement expected to throw {@code IgniteSqlException} with expected specified message.
+ * @param sql Statement.
+ * @param msg Expected message.
+ */
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ protected void assertThrows(final String sql, String msg) {
+ assertThrows(grid(nodeIndex()), sql, msg);
+ }
+
+ /**
+ * Execute SQL command and return resulting dataset.
+ * @param sql Statement.
+ * @return result.
+ */
+ protected List<List<?>> run(String sql) {
+ return run(grid(nodeIndex()), sql);
+ }
+
+ /** City class. */
+ private final static class City {
+ /** City id. */
+ @QuerySqlField
+ private int id;
+
+ /** City name. */
+ @QuerySqlField
+ private String name;
+
+ /** City state. */
+ @QuerySqlField
+ private String state;
+
+ /**
+ * @return City id.
+ */
+ public int id() {
+ return id;
+ }
+
+ /**
+ * @param id City id.
+ */
+ public void id(int id) {
+ this.id = id;
+ }
+
+ /**
+ * @return City name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * @param name City name.
+ */
+ public void name(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return City state.
+ */
+ public String state() {
+ return state;
+ }
+
+ /**
+ * @param state City state.
+ */
+ public void state(String state) {
+ this.state = state;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsClientBasicSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsClientBasicSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsClientBasicSelfTest.java
new file mode 100644
index 0000000..1972f2c
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsClientBasicSelfTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Test to check {@code ALTER TABLE} operations started from client node.
+ */
+public class H2DynamicColumnsClientBasicSelfTest extends H2DynamicColumnsAbstractBasicSelfTest {
+ /** {@inheritDoc} */
+ @Override protected int nodeIndex() {
+ return CLI_IDX;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerBasicSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerBasicSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerBasicSelfTest.java
new file mode 100644
index 0000000..fa15752
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerBasicSelfTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Test to check {@code ALTER TABLE} operations started from server node.
+ */
+public class H2DynamicColumnsServerBasicSelfTest extends H2DynamicColumnsAbstractBasicSelfTest {
+ /** {@inheritDoc} */
+ @Override protected int nodeIndex() {
+ return SRV_IDX;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerCoordinatorBasicSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerCoordinatorBasicSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerCoordinatorBasicSelfTest.java
new file mode 100644
index 0000000..db7f75c
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicColumnsServerCoordinatorBasicSelfTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Test to check {@code ALTER TABLE} operations started from coordinator node.
+ */
+public class H2DynamicColumnsServerCoordinatorBasicSelfTest extends H2DynamicColumnsAbstractBasicSelfTest {
+ /** {@inheritDoc} */
+ @Override protected int nodeIndex() {
+ return SRV_CRD_IDX;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/sql/GridQueryParsingTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/sql/GridQueryParsingTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/sql/GridQueryParsingTest.java
index 329e580..770a375 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/sql/GridQueryParsingTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/sql/GridQueryParsingTest.java
@@ -599,7 +599,7 @@ public class GridQueryParsingTest extends GridCommonAbstractTest {
IgniteSQLException.class, "No cache value related columns found");
assertParseThrows("create table Person (id int primary key, age int not null) WITH \"cacheTemplate=cache\"",
- IgniteSQLException.class, "Non nullable columns are forbidden");
+ IgniteSQLException.class, "Non nullable columns are not supported [colName=AGE]");
assertParseThrows("create table Person (id int primary key, age int unique) WITH \"template=cache\"",
IgniteSQLException.class, "Too many constraints - only PRIMARY KEY is supported for CREATE TABLE");
@@ -620,6 +620,44 @@ public class GridQueryParsingTest extends GridCommonAbstractTest {
IgniteSQLException.class, "Direct specification of _KEY and _VAL columns is forbidden");
}
+ /** */
+ public void testParseAlterTableAddColumn() throws Exception {
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "Person", false, false,
+ c("COMPANY", Value.STRING)), "ALTER TABLE SCH2.Person ADD company varchar");
+
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "Person", true, true,
+ c("COMPANY", Value.STRING)), "ALTER TABLE IF EXISTS SCH2.Person ADD if not exists company varchar");
+
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "Person", false, true,
+ c("COMPANY", Value.STRING), c("city", Value.STRING)),
+ "ALTER TABLE IF EXISTS SCH2.Person ADD (company varchar, \"city\" varchar)");
+
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "City", false, true,
+ c("POPULATION", Value.INT)), "ALTER TABLE IF EXISTS SCH2.\"City\" ADD (population int)");
+
+ // There's no table with such name, but H2 parsing does not fail just yet.
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "City", false, false,
+ c("POPULATION", Value.INT)), "ALTER TABLE SCH2.\"City\" ADD (population int)");
+
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "Person", true, false,
+ c("NAME", Value.STRING)), "ALTER TABLE SCH2.Person ADD if not exists name varchar");
+
+ // There's a column with such name, but H2 parsing does not fail just yet.
+ assertAlterTableAddColumnEquals(buildAlterTableAddColumn("SCH2", "Person", false, false,
+ c("NAME", Value.STRING)), "ALTER TABLE SCH2.Person ADD name varchar");
+
+ // IF NOT EXISTS with multiple columns.
+ assertParseThrows("ALTER TABLE IF EXISTS SCH2.Person ADD if not exists (company varchar, city varchar)",
+ DbException.class, null);
+
+ // Both BEFORE and AFTER keywords.
+ assertParseThrows("ALTER TABLE IF EXISTS SCH2.Person ADD if not exists company varchar before addrid",
+ IgniteSQLException.class, "ALTER TABLE ADD COLUMN BEFORE/AFTER is not supported");
+
+ // No such schema.
+ assertParseThrows("ALTER TABLE SCH3.\"Person\" ADD (city varchar)", DbException.class, null);
+ }
+
/**
* @param sql Statement.
* @param exCls Exception class.
@@ -747,6 +785,58 @@ public class GridQueryParsingTest extends GridCommonAbstractTest {
}
/**
+ * Parse SQL and compare it to expected instance of ALTER TABLE.
+ */
+ private void assertAlterTableAddColumnEquals(GridSqlAlterTableAddColumn exp, String sql) throws Exception {
+ Prepared prepared = parse(sql);
+
+ GridSqlStatement stmt = new GridSqlQueryParser(false).parse(prepared);
+
+ assertTrue(stmt instanceof GridSqlAlterTableAddColumn);
+
+ assertAlterTableAddColumnEquals(exp, (GridSqlAlterTableAddColumn)stmt);
+ }
+
+ /** */
+ private static GridSqlAlterTableAddColumn buildAlterTableAddColumn(String schema, String tbl,
+ boolean ifNotExists, boolean ifTblExists, GridSqlColumn... cols) {
+ GridSqlAlterTableAddColumn res = new GridSqlAlterTableAddColumn();
+
+ res.schemaName(schema);
+
+ res.tableName(tbl);
+
+ res.ifNotExists(ifNotExists);
+
+ res.ifTableExists(ifTblExists);
+
+ res.columns(cols);
+
+ return res;
+ }
+
+ /**
+ * Test two instances of {@link GridSqlAlterTableAddColumn} for equality.
+ */
+ private static void assertAlterTableAddColumnEquals(GridSqlAlterTableAddColumn exp,
+ GridSqlAlterTableAddColumn actual) {
+ assertEqualsIgnoreCase(exp.schemaName(), actual.schemaName());
+ assertEqualsIgnoreCase(exp.tableName(), actual.tableName());
+ assertEquals(exp.columns().length, actual.columns().length);
+
+ for (int i = 0; i < exp.columns().length; i++) {
+ GridSqlColumn expCol = exp.columns()[i];
+ GridSqlColumn col = actual.columns()[i];
+
+ assertEquals(expCol.columnName(), col.columnName());
+ assertEquals(expCol.column().getType(), col.column().getType());
+ }
+
+ assertEquals(exp.ifNotExists(), actual.ifNotExists());
+ assertEquals(exp.ifTableExists(), actual.ifTableExists());
+ }
+
+ /**
* @param name Column name.
* @param type Column data type.
* @return {@link GridSqlColumn} with given name and type.
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
index 5ac0655f..947bb4d 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java
@@ -89,11 +89,18 @@ import org.apache.ignite.internal.processors.cache.distributed.replicated.Ignite
import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheReplicatedQueryP2PDisabledSelfTest;
import org.apache.ignite.internal.processors.cache.distributed.replicated.IgniteCacheReplicatedQuerySelfTest;
import org.apache.ignite.internal.processors.cache.index.DuplicateKeyValueClassesSelfTest;
+import org.apache.ignite.internal.processors.cache.index.DynamicColumnsConcurrentAtomicPartitionedSelfTest;
+import org.apache.ignite.internal.processors.cache.index.DynamicColumnsConcurrentAtomicReplicatedSelfTest;
+import org.apache.ignite.internal.processors.cache.index.DynamicColumnsConcurrentTransactionalPartitionedSelfTest;
+import org.apache.ignite.internal.processors.cache.index.DynamicColumnsConcurrentTransactionalReplicatedSelfTest;
import org.apache.ignite.internal.processors.cache.index.DynamicIndexClientBasicSelfTest;
import org.apache.ignite.internal.processors.cache.index.DynamicIndexServerBasicSelfTest;
import org.apache.ignite.internal.processors.cache.index.DynamicIndexServerCoordinatorBasicSelfTest;
import org.apache.ignite.internal.processors.cache.index.DynamicIndexServerNodeFIlterBasicSelfTest;
import org.apache.ignite.internal.processors.cache.index.DynamicIndexServerNodeFilterCoordinatorBasicSelfTest;
+import org.apache.ignite.internal.processors.cache.index.H2DynamicColumnsClientBasicSelfTest;
+import org.apache.ignite.internal.processors.cache.index.H2DynamicColumnsServerBasicSelfTest;
+import org.apache.ignite.internal.processors.cache.index.H2DynamicColumnsServerCoordinatorBasicSelfTest;
import org.apache.ignite.internal.processors.cache.index.H2DynamicIndexAtomicPartitionedNearSelfTest;
import org.apache.ignite.internal.processors.cache.index.H2DynamicIndexAtomicPartitionedSelfTest;
import org.apache.ignite.internal.processors.cache.index.H2DynamicIndexAtomicReplicatedSelfTest;
@@ -250,6 +257,13 @@ public class IgniteCacheQuerySelfTestSuite extends TestSuite {
suite.addTestSuite(H2DynamicIndexAtomicPartitionedSelfTest.class);
suite.addTestSuite(H2DynamicIndexAtomicPartitionedNearSelfTest.class);
suite.addTestSuite(H2DynamicTableSelfTest.class);
+ suite.addTestSuite(H2DynamicColumnsClientBasicSelfTest.class);
+ suite.addTestSuite(H2DynamicColumnsServerBasicSelfTest.class);
+ suite.addTestSuite(H2DynamicColumnsServerCoordinatorBasicSelfTest.class);
+ suite.addTestSuite(DynamicColumnsConcurrentAtomicPartitionedSelfTest.class);
+ suite.addTestSuite(DynamicColumnsConcurrentTransactionalPartitionedSelfTest.class);
+ suite.addTestSuite(DynamicColumnsConcurrentAtomicReplicatedSelfTest.class);
+ suite.addTestSuite(DynamicColumnsConcurrentTransactionalReplicatedSelfTest.class);
// DML+DDL.
suite.addTestSuite(H2DynamicIndexingComplexClientAtomicPartitionedTest.class);
[2/2] ignite git commit: IGNITE-5572: SQL: ALTER TABLE ADD COLUMN
support. This closes #2344.
Posted by vo...@apache.org.
IGNITE-5572: SQL: ALTER TABLE ADD COLUMN support. This closes #2344.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/adec3e7e
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/adec3e7e
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/adec3e7e
Branch: refs/heads/master
Commit: adec3e7ec20fe04aa37a34416253602bc0387f4f
Parents: ffaf108
Author: Alexander Paschenko <al...@gmail.com>
Authored: Wed Sep 6 18:18:23 2017 +0300
Committer: devozerov <pp...@gmail.com>
Committed: Wed Sep 6 18:18:23 2017 +0300
----------------------------------------------------------------------
.../processors/query/GridQueryIndexing.java | 15 +
.../processors/query/GridQueryProcessor.java | 132 ++-
.../internal/processors/query/QueryField.java | 64 ++
.../internal/processors/query/QuerySchema.java | 37 +-
.../query/QueryTypeDescriptorImpl.java | 17 +-
.../query/schema/SchemaOperationWorker.java | 2 +-
.../SchemaAbstractAlterTableOperation.java | 39 +
.../SchemaAlterTableAddColumnOperation.java | 96 ++
...IgniteClientCacheInitializationFailTest.java | 8 +
.../processors/query/h2/H2RowDescriptor.java | 105 +-
.../processors/query/h2/H2TableEngine.java | 13 +-
.../processors/query/h2/IgniteH2Indexing.java | 22 +-
.../query/h2/ddl/DdlStatementsProcessor.java | 50 +-
.../processors/query/h2/opt/GridH2Table.java | 55 +-
.../h2/sql/GridSqlAlterTableAddColumn.java | 113 ++
.../query/h2/sql/GridSqlQueryParser.java | 160 ++-
...ynamicColumnsAbstractConcurrentSelfTest.java | 1056 ++++++++++++++++++
.../cache/index/DynamicColumnsAbstractTest.java | 311 ++++++
...umnsConcurrentAtomicPartitionedSelfTest.java | 33 +
...lumnsConcurrentAtomicReplicatedSelfTest.java | 33 +
...currentTransactionalPartitionedSelfTest.java | 33 +
...ncurrentTransactionalReplicatedSelfTest.java | 33 +
.../H2DynamicColumnsAbstractBasicSelfTest.java | 348 ++++++
.../H2DynamicColumnsClientBasicSelfTest.java | 28 +
.../H2DynamicColumnsServerBasicSelfTest.java | 28 +
...icColumnsServerCoordinatorBasicSelfTest.java | 28 +
.../query/h2/sql/GridQueryParsingTest.java | 92 +-
.../IgniteCacheQuerySelfTestSuite.java | 14 +
28 files changed, 2841 insertions(+), 124 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
index 8eecfc2..cecc5dd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
@@ -165,6 +165,21 @@ public interface GridQueryIndexing {
public void dynamicIndexDrop(String schemaName, String idxName, boolean ifExists) throws IgniteCheckedException;
/**
+ * Add columns to dynamic table.
+ *
+ * @param schemaName Schema name.
+ * @param tblName Table name.
+ * @param cols Columns to add.
+ * @param ifTblExists Ignore operation if target table does not exist (instead of throwing an error).
+ * @param ifColNotExists Ignore operation if column already exists (instead of throwing an error) - is honored only
+ * for single column case.
+ * @throws IgniteCheckedException If failed.
+ */
+ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+ public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols, boolean ifTblExists,
+ boolean ifColNotExists) throws IgniteCheckedException;
+
+ /**
* Registers cache.
*
* @param cacheName Cache name.
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index d5ac8fc..ea4c576 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -69,6 +69,7 @@ import org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
import org.apache.ignite.internal.processors.cache.query.CacheQueryType;
import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.property.QueryBinaryProperty;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorImpl;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexOperationCancellationToken;
@@ -81,6 +82,7 @@ import org.apache.ignite.internal.processors.query.schema.message.SchemaFinishDi
import org.apache.ignite.internal.processors.query.schema.message.SchemaOperationStatusMessage;
import org.apache.ignite.internal.processors.query.schema.message.SchemaProposeDiscoveryMessage;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaAbstractOperation;
+import org.apache.ignite.internal.processors.query.schema.operation.SchemaAlterTableAddColumnOperation;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexCreateOperation;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexDropOperation;
import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
@@ -754,6 +756,16 @@ public class GridQueryProcessor extends GridProcessorAdapter {
QueryUtils.processDynamicIndexChange(opDrop.indexName(), null, typeDesc);
}
+ else if (op0 instanceof SchemaAlterTableAddColumnOperation) {
+ SchemaAlterTableAddColumnOperation opAddCol =
+ (SchemaAlterTableAddColumnOperation)op0;
+
+ QueryTypeDescriptorImpl typeDesc = tblTypMap.get(opAddCol.tableName());
+
+ assert typeDesc != null;
+
+ processDynamicAddColumn(typeDesc, opAddCol.columns());
+ }
else
assert false;
}
@@ -966,6 +978,32 @@ public class GridQueryProcessor extends GridProcessorAdapter {
else
type = oldIdx.typeDescriptor();
}
+ else if (op instanceof SchemaAlterTableAddColumnOperation) {
+ SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+ type = type(cacheName, op0.tableName());
+
+ if (type == null) {
+ if (op0.ifTableExists())
+ nop = true;
+ else
+ err = new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+ op0.tableName());
+ }
+ else {
+ for (QueryField col : op0.columns()) {
+ if (type.hasField(col.name())) {
+ if (op0.ifNotExists()) {
+ assert op0.columns().size() == 1;
+
+ nop = true;
+ }
+ else
+ err = new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, col.name());
+ }
+ }
+ }
+ }
else
err = new SchemaOperationException("Unsupported operation: " + op);
@@ -1070,6 +1108,32 @@ public class GridQueryProcessor extends GridProcessorAdapter {
err = new SchemaOperationException(SchemaOperationException.CODE_INDEX_NOT_FOUND, idxName);
}
}
+ else if (op instanceof SchemaAlterTableAddColumnOperation) {
+ SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+ QueryEntity e = tblMap.get(op0.tableName());
+
+ if (e == null) {
+ if (op0.ifTableExists())
+ nop = true;
+ else
+ err = new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+ op0.tableName());
+ }
+ else {
+ for (QueryField fld : op0.columns()) {
+ if (e.getFields().containsKey(fld.name())) {
+ if (op0.ifNotExists()) {
+ assert op0.columns().size() == 1;
+
+ nop = true;
+ }
+ else
+ err = new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS, fld.name());
+ }
+ }
+ }
+ }
else
err = new SchemaOperationException("Unsupported operation: " + op);
@@ -1180,9 +1244,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
idxs.put(idxKey, idxDesc);
}
- else {
- assert op instanceof SchemaIndexDropOperation;
-
+ else if (op instanceof SchemaIndexDropOperation) {
SchemaIndexDropOperation op0 = (SchemaIndexDropOperation) op;
QueryUtils.processDynamicIndexChange(op0.indexName(), null, type);
@@ -1191,6 +1253,12 @@ public class GridQueryProcessor extends GridProcessorAdapter {
idxs.remove(idxKey);
}
+ else {
+ assert op instanceof SchemaAlterTableAddColumnOperation;
+
+ // No-op - all processing is done at "local" stage
+ // as we must update both table and type descriptor atomically.
+ }
}
catch (IgniteCheckedException e) {
U.warn(log, "Failed to finish index operation [opId=" + op.id() + " op=" + op + ']', e);
@@ -1229,7 +1297,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
}
/**
- * Process index operation.
+ * Process schema operation.
*
* @param op Operation.
* @param type Type descriptor.
@@ -1237,7 +1305,8 @@ public class GridQueryProcessor extends GridProcessorAdapter {
* @param cancelTok Cancel token.
* @throws SchemaOperationException If failed.
*/
- public void processIndexOperationLocal(SchemaAbstractOperation op, QueryTypeDescriptorImpl type, IgniteUuid depId,
+ @SuppressWarnings("StatementWithEmptyBody")
+ public void processSchemaOperationLocal(SchemaAbstractOperation op, QueryTypeDescriptorImpl type, IgniteUuid depId,
SchemaIndexOperationCancellationToken cancelTok) throws SchemaOperationException {
if (log.isDebugEnabled())
log.debug("Started local index operation [opId=" + op.id() + ']');
@@ -1251,7 +1320,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
try {
if (op instanceof SchemaIndexCreateOperation) {
- SchemaIndexCreateOperation op0 = (SchemaIndexCreateOperation) op;
+ SchemaIndexCreateOperation op0 = (SchemaIndexCreateOperation)op;
QueryIndexDescriptorImpl idxDesc = QueryUtils.createIndexDescriptor(type, op0.index());
@@ -1261,10 +1330,18 @@ public class GridQueryProcessor extends GridProcessorAdapter {
idx.dynamicIndexCreate(op0.schemaName(), op0.tableName(), idxDesc, op0.ifNotExists(), visitor);
}
else if (op instanceof SchemaIndexDropOperation) {
- SchemaIndexDropOperation op0 = (SchemaIndexDropOperation) op;
+ SchemaIndexDropOperation op0 = (SchemaIndexDropOperation)op;
idx.dynamicIndexDrop(op0.schemaName(), op0.indexName(), op0.ifExists());
}
+ else if (op instanceof SchemaAlterTableAddColumnOperation) {
+ SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+ processDynamicAddColumn(type, op0.columns());
+
+ idx.dynamicAddColumn(op0.schemaName(), op0.tableName(), op0.columns(), op0.ifTableExists(),
+ op0.ifNotExists());
+ }
else
throw new SchemaOperationException("Unsupported operation: " + op);
}
@@ -2063,6 +2140,23 @@ private IgniteInternalFuture<Object> rebuildIndexesFromHash(@Nullable final Stri
}
/**
+ * Entry point for add column procedure.
+ * @param schemaName Schema name.
+ * @param tblName Target table name.
+ * @param cols Columns to add.
+ * @param ifTblExists Ignore operation if target table doesn't exist.
+ * @param ifNotExists Ignore operation if column exists.
+ */
+ public IgniteInternalFuture<?> dynamicColumnAdd(String cacheName, String schemaName, String tblName,
+ List<QueryField> cols, boolean ifTblExists, boolean ifNotExists) {
+
+ SchemaAlterTableAddColumnOperation op = new SchemaAlterTableAddColumnOperation(UUID.randomUUID(), cacheName,
+ schemaName, tblName, cols, ifTblExists, ifNotExists);
+
+ return startIndexOperationDistributed(op);
+ }
+
+ /**
* Start distributed index change operation.
*
* @param op Operation.
@@ -2130,6 +2224,30 @@ private IgniteInternalFuture<Object> rebuildIndexesFromHash(@Nullable final Stri
}
/**
+ * Update type descriptor with new fields metadata.
+ *
+ * @param d Type descriptor to update.
+ * @param cols Columns to add.
+ * @throws IgniteCheckedException If failed to update type descriptor.
+ */
+ private void processDynamicAddColumn(QueryTypeDescriptorImpl d, List<QueryField> cols) throws IgniteCheckedException {
+ List<GridQueryProperty> props = new ArrayList<>(cols.size());
+
+ for (QueryField col : cols) {
+ try {
+ props.add(new QueryBinaryProperty(ctx, col.name(), null, Class.forName(col.typeName()),
+ false, null));
+ }
+ catch (ClassNotFoundException e) {
+ throw new SchemaOperationException("Class not found for new property: " + col.typeName());
+ }
+ }
+
+ for (GridQueryProperty p : props)
+ d.addProperty(p, true);
+ }
+
+ /**
*
* @param cacheName Cache name.
* @param sql Query.
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
new file mode 100644
index 0000000..968c287
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryField.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.io.Serializable;
+
+/**
+ * Query field metadata.
+ */
+public class QueryField implements Serializable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Field name. */
+ private final String name;
+
+ /** Class name for this field's values. */
+ private final String typeName;
+
+ /**
+ * @param name Field name.
+ * @param typeName Class name for this field's values.
+ */
+ public QueryField(String name, String typeName) {
+ this.name = name;
+ this.typeName = typeName;
+ }
+
+ /**
+ * @return Field name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * @return Class name for this field's values.
+ */
+ public String typeName() {
+ return typeName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(QueryField.class, this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
index 34a4502..ccbba93 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QuerySchema.java
@@ -17,21 +17,21 @@
package org.apache.ignite.internal.processors.query;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.internal.processors.query.schema.message.SchemaFinishDiscoveryMessage;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaAbstractOperation;
+import org.apache.ignite.internal.processors.query.schema.operation.SchemaAlterTableAddColumnOperation;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexCreateOperation;
import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexDropOperation;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
/**
* Dynamic cache schema.
*/
@@ -118,9 +118,7 @@ public class QuerySchema implements Serializable {
}
}
}
- else {
- assert op instanceof SchemaIndexDropOperation;
-
+ else if (op instanceof SchemaIndexDropOperation) {
SchemaIndexDropOperation op0 = (SchemaIndexDropOperation)op;
for (QueryEntity entity : entities) {
@@ -147,6 +145,27 @@ public class QuerySchema implements Serializable {
}
}
}
+ else {
+ assert op instanceof SchemaAlterTableAddColumnOperation;
+
+ SchemaAlterTableAddColumnOperation op0 = (SchemaAlterTableAddColumnOperation)op;
+
+ QueryEntity target = null;
+
+ for (QueryEntity entity : entities()) {
+ if (F.eq(entity.getTableName(), op0.tableName())) {
+ target = entity;
+
+ break;
+ }
+ }
+
+ if (target == null)
+ return;
+
+ for (QueryField field : op0.columns())
+ target.getFields().put(field.name(), field.typeName());
+ }
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
index 79b90e5..0b77c4c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
@@ -17,6 +17,12 @@
package org.apache.ignite.internal.processors.query;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.QueryIndexType;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
@@ -25,13 +31,6 @@ import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
/**
* Descriptor of type.
*/
@@ -50,7 +49,7 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
/** Value field names and types with preserved order. */
@GridToStringInclude
- private final Map<String, Class<?>> fields = new LinkedHashMap<>();
+ private final LinkedHashMap<String, Class<?>> fields = new LinkedHashMap<>();
/** */
@GridToStringExclude
@@ -155,7 +154,7 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
}
/** {@inheritDoc} */
- @Override public Map<String, Class<?>> fields() {
+ @Override public LinkedHashMap<String, Class<?>> fields() {
return fields;
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
index 06feecb..0bf4d22 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/SchemaOperationWorker.java
@@ -105,7 +105,7 @@ public class SchemaOperationWorker extends GridWorker {
@Override protected void body() throws InterruptedException, IgniteInterruptedCheckedException {
try {
// Execute.
- qryProc.processIndexOperationLocal(op, type, depId, cancelToken);
+ qryProc.processSchemaOperationLocal(op, type, depId, cancelToken);
fut.onDone();
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java
new file mode 100644
index 0000000..a33bc08
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAbstractAlterTableOperation.java
@@ -0,0 +1,39 @@
+/*
+ * 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.schema.operation;
+
+import java.util.UUID;
+
+/**
+ * Parent class for ALTER TABLE command variants.
+ */
+public class SchemaAbstractAlterTableOperation extends SchemaAbstractOperation {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructor.
+ *
+ * @param opId Operation ID.
+ * @param cacheName Cache name.
+ * @param schemaName Schema name.
+ */
+ public SchemaAbstractAlterTableOperation(UUID opId, String cacheName, String schemaName) {
+ super(opId, cacheName, schemaName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
new file mode 100644
index 0000000..9bd109f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/operation/SchemaAlterTableAddColumnOperation.java
@@ -0,0 +1,96 @@
+/*
+ * 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.schema.operation;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Schema index drop operation.
+ */
+public class SchemaAlterTableAddColumnOperation extends SchemaAbstractAlterTableOperation {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Target table name. */
+ private final String tblName;
+
+ /** Columns to add. */
+ private final List<QueryField> cols;
+
+ /** Ignore operation if target table doesn't exist. */
+ private final boolean ifTblExists;
+
+ /** Ignore operation if column exists. */
+ private final boolean ifNotExists;
+
+ /**
+ * Constructor.
+ *
+ * @param opId Operation id.
+ * @param schemaName Schema name.
+ * @param tblName Target table name.
+ * @param cols Columns to add.
+ * @param ifTblExists Ignore operation if target table doesn't exist.
+ * @param ifNotExists Ignore operation if column exists.
+ */
+ public SchemaAlterTableAddColumnOperation(UUID opId, String cacheName, String schemaName, String tblName,
+ List<QueryField> cols, boolean ifTblExists, boolean ifNotExists) {
+ super(opId, cacheName, schemaName);
+
+ this.tblName = tblName;
+ this.cols = cols;
+ this.ifTblExists = ifTblExists;
+ this.ifNotExists = ifNotExists;
+ }
+
+ /**
+ * @return Ignore operation if table doesn't exist.
+ */
+ public boolean ifTableExists() {
+ return ifTblExists;
+ }
+
+ /**
+ * @return Columns to add.
+ */
+ public List<QueryField> columns() {
+ return cols;
+ }
+
+ /**
+ * @return Quietly abort this command if column exists (honored only in single column case).
+ */
+ public boolean ifNotExists() {
+ return ifNotExists;
+ }
+
+ /**
+ * @return Target table name.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(SchemaAlterTableAddColumnOperation.class, this, "parent", super.toString());
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
index fa6dd70..c745d8a 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClientCacheInitializationFailTest.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexing;
import org.apache.ignite.internal.processors.query.GridQueryProcessor;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.GridRunningQueryInfo;
+import org.apache.ignite.internal.processors.query.QueryField;
import org.apache.ignite.internal.processors.query.QueryIndexDescriptorImpl;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
import org.apache.ignite.internal.util.GridSpinBusyLock;
@@ -284,6 +285,13 @@ public class IgniteClientCacheInitializationFailTest extends GridCommonAbstractT
}
/** {@inheritDoc} */
+ @Override public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols,
+ boolean ifTblExists, boolean ifColNotExists)
+ throws IgniteCheckedException {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
@Override public void registerCache(String cacheName, String schemaName,
GridCacheContext<?, ?> cctx) throws IgniteCheckedException {
if (FAILED_CACHES.contains(cctx.name()) && cctx.kernalContext().clientNode())
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
index 31f0e69..e59404e 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2RowDescriptor.java
@@ -17,6 +17,15 @@
package org.apache.ignite.internal.processors.query.h2;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.processors.cache.CacheObject;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
@@ -60,16 +69,6 @@ import org.h2.value.ValueUuid;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.math.BigDecimal;
-import java.sql.Date;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.DEFAULT_COLUMNS_COUNT;
import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.KEY_COL;
import static org.apache.ignite.internal.processors.query.h2.opt.GridH2AbstractKeyValueRow.VAL_COL;
@@ -89,10 +88,10 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
private final GridQueryTypeDescriptor type;
/** */
- private final String[] fields;
+ private volatile String[] fields;
/** */
- private final int[] fieldTypes;
+ private volatile int[] fieldTypes;
/** */
private final int keyType;
@@ -107,13 +106,13 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
private final GridUnsafeGuard guard;
/** */
- private final GridQueryProperty[] props;
+ private volatile GridQueryProperty[] props;
/** Id of user-defined key column */
- private final int keyAliasColumnId;
+ private volatile int keyAliasColId;
/** Id of user-defined value column */
- private final int valueAliasColumnId;
+ private volatile int valAliasColId;
/**
* Constructor.
@@ -134,6 +133,17 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
guard = schema.offheap() == null ? null : new GridUnsafeGuard();
+ keyType = DataType.getTypeFromClass(type.keyClass());
+ valType = DataType.getTypeFromClass(type.valueClass());
+
+ refreshMetadataFromTypeDescriptor();
+ }
+
+ /**
+ * Update metadata of this row descriptor according to current state of type descriptor.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final void refreshMetadataFromTypeDescriptor() {
Map<String, Class<?>> allFields = new LinkedHashMap<>();
allFields.putAll(type.fields());
@@ -147,9 +157,6 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
for (int i = 0; i < fieldTypes.length; i++)
fieldTypes[i] = DataType.getTypeFromClass(classes[i]);
- keyType = DataType.getTypeFromClass(type.keyClass());
- valType = DataType.getTypeFromClass(type.valueClass());
-
props = new GridQueryProperty[fields.length];
for (int i = 0; i < fields.length; i++) {
@@ -160,12 +167,12 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
props[i] = p;
}
- final List<String> fieldsList = Arrays.asList(fields);
+ List<String> fieldsList = Arrays.asList(fields);
- keyAliasColumnId =
+ keyAliasColId =
(type.keyFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.keyFieldAlias()) : -1;
- valueAliasColumnId =
+ valAliasColId =
(type.valueFieldName() != null) ? DEFAULT_COLUMNS_COUNT + fieldsList.indexOf(type.valueFieldAlias()) : -1;
}
@@ -376,29 +383,29 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
}
/** {@inheritDoc} */
- @Override public boolean isKeyColumn(int columnId) {
- assert columnId >= 0;
- return columnId == KEY_COL || columnId == keyAliasColumnId;
+ @Override public boolean isKeyColumn(int colId) {
+ assert colId >= 0;
+ return colId == KEY_COL || colId == keyAliasColId;
}
/** {@inheritDoc} */
- @Override public boolean isValueColumn(int columnId) {
- assert columnId >= 0;
- return columnId == VAL_COL || columnId == valueAliasColumnId;
+ @Override public boolean isValueColumn(int colId) {
+ assert colId >= 0;
+ return colId == VAL_COL || colId == valAliasColId;
}
/** {@inheritDoc} */
@SuppressWarnings("RedundantIfStatement")
- @Override public boolean isKeyValueOrVersionColumn(int columnId) {
- assert columnId >= 0;
+ @Override public boolean isKeyValueOrVersionColumn(int colId) {
+ assert colId >= 0;
- if (columnId < DEFAULT_COLUMNS_COUNT)
+ if (colId < DEFAULT_COLUMNS_COUNT)
return true;
- if (columnId == keyAliasColumnId)
+ if (colId == keyAliasColId)
return true;
- if (columnId == valueAliasColumnId)
+ if (colId == valAliasColId)
return true;
return false;
@@ -409,26 +416,26 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
assert masks != null;
assert masks.length > 0;
- if (keyAliasColumnId < 0)
+ if (keyAliasColId < 0)
return (masks[KEY_COL] & mask) != 0;
else
- return (masks[KEY_COL] & mask) != 0 || (masks[keyAliasColumnId] & mask) != 0;
+ return (masks[KEY_COL] & mask) != 0 || (masks[keyAliasColId] & mask) != 0;
}
/** {@inheritDoc} */
- @Override public void initValueCache(Value valCache[], Value key, Value value, Value version) {
+ @Override public void initValueCache(Value valCache[], Value key, Value val, Value ver) {
assert valCache != null;
assert valCache.length > 0;
valCache[KEY_COL] = key;
- valCache[VAL_COL] = value;
- valCache[VER_COL] = version;
+ valCache[VAL_COL] = val;
+ valCache[VER_COL] = ver;
- if (keyAliasColumnId > 0)
- valCache[keyAliasColumnId] = key;
+ if (keyAliasColId > 0)
+ valCache[keyAliasColId] = key;
- if (valueAliasColumnId > 0)
- valCache[valueAliasColumnId] = value;
+ if (valAliasColId > 0)
+ valCache[valAliasColId] = val;
}
/** {@inheritDoc} */
@@ -440,8 +447,8 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
for (int idx = 0; idx < data.length; idx++)
data[idx] = row.getValue(idx);
- copyAliasColumnData(data, KEY_COL, keyAliasColumnId);
- copyAliasColumnData(data, VAL_COL, valueAliasColumnId);
+ copyAliasColumnData(data, KEY_COL, keyAliasColId);
+ copyAliasColumnData(data, VAL_COL, valAliasColId);
return new SimpleRow(data);
}
@@ -466,16 +473,16 @@ public class H2RowDescriptor implements GridH2RowDescriptor {
/** {@inheritDoc} */
@Override public int getAlternativeColumnId(int colId) {
- if (keyAliasColumnId > 0) {
+ if (keyAliasColId > 0) {
if (colId == KEY_COL)
- return keyAliasColumnId;
- else if (colId == keyAliasColumnId)
+ return keyAliasColId;
+ else if (colId == keyAliasColId)
return KEY_COL;
}
- if (valueAliasColumnId > 0) {
+ if (valAliasColId > 0) {
if (colId == VAL_COL)
- return valueAliasColumnId;
- else if (colId == valueAliasColumnId)
+ return valAliasColId;
+ else if (colId == valAliasColId)
return VAL_COL;
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
index 6bdcc30..d3e9560 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2TableEngine.java
@@ -17,24 +17,21 @@
package org.apache.ignite.internal.processors.query.h2;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.table.TableBase;
-import org.jetbrains.annotations.Nullable;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Statement;
/**
* H2 Table engine.
*/
public class H2TableEngine implements TableEngine {
/** */
- private static GridH2RowDescriptor rowDesc0;
+ private static H2RowDescriptor rowDesc0;
/** */
private static H2RowFactory rowFactory0;
@@ -56,7 +53,7 @@ public class H2TableEngine implements TableEngine {
* @throws SQLException If failed.
* @return Created table.
*/
- public static synchronized GridH2Table createTable(Connection conn, String sql, GridH2RowDescriptor rowDesc,
+ public static synchronized GridH2Table createTable(Connection conn, String sql, H2RowDescriptor rowDesc,
H2RowFactory rowFactory, H2TableDescriptor tblDesc)
throws SQLException {
rowDesc0 = rowDesc;
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 0f97a4b..dff82d7 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -90,6 +90,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexing;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.GridRunningQueryInfo;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
import org.apache.ignite.internal.processors.query.QueryIndexDescriptorImpl;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory;
@@ -708,6 +709,25 @@ public class IgniteH2Indexing implements GridQueryIndexing {
executeSql(schemaName, sql);
}
+ /** {@inheritDoc} */
+ @Override public void dynamicAddColumn(String schemaName, String tblName, List<QueryField> cols,
+ boolean ifTblExists, boolean ifColNotExists) throws IgniteCheckedException {
+ // Locate table.
+ H2Schema schema = schemas.get(schemaName);
+
+ H2TableDescriptor desc = (schema != null ? schema.tableByName(tblName) : null);
+
+ if (desc == null) {
+ if (!ifTblExists)
+ throw new IgniteCheckedException("Table not found in internal H2 database [schemaName=" + schemaName +
+ ", tblName=" + tblName + ']');
+ else
+ return;
+ }
+
+ desc.table().addColumns(cols, ifColNotExists);
+ }
+
/**
* Execute DDL command.
*
@@ -1628,7 +1648,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
if (log.isDebugEnabled())
log.debug("Creating DB table with SQL: " + sql);
- GridH2RowDescriptor rowDesc = new H2RowDescriptor(this, tbl, tbl.type(), schema);
+ H2RowDescriptor rowDesc = new H2RowDescriptor(this, tbl, tbl.type(), schema);
H2RowFactory rowFactory = tbl.rowFactory(rowDesc);
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
index 65e402d..f322010 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.h2.ddl;
import java.sql.PreparedStatement;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -34,9 +35,11 @@ import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.processors.query.GridQueryProperty;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlAlterTableAddColumn;
import org.apache.ignite.internal.processors.query.h2.sql.GridSqlColumn;
import org.apache.ignite.internal.processors.query.h2.sql.GridSqlCreateIndex;
import org.apache.ignite.internal.processors.query.h2.sql.GridSqlCreateTable;
@@ -48,6 +51,7 @@ import org.apache.ignite.internal.processors.query.schema.SchemaOperationExcepti
import org.apache.ignite.internal.util.future.GridFinishedFuture;
import org.apache.ignite.internal.util.typedef.F;
import org.h2.command.Prepared;
+import org.h2.command.ddl.AlterTableAlterColumn;
import org.h2.command.ddl.CreateIndex;
import org.h2.command.ddl.CreateTable;
import org.h2.command.ddl.DropIndex;
@@ -203,6 +207,50 @@ public class DdlStatementsProcessor {
else
ctx.query().dynamicTableDrop(tbl.cacheName(), cmd.tableName(), cmd.ifExists());
}
+ else if (stmt0 instanceof GridSqlAlterTableAddColumn) {
+ GridSqlAlterTableAddColumn cmd = (GridSqlAlterTableAddColumn)stmt0;
+
+ GridH2Table tbl = idx.dataTable(cmd.schemaName(), cmd.tableName());
+
+ if (tbl == null && cmd.ifTableExists()) {
+ ctx.cache().createMissingQueryCaches();
+
+ tbl = idx.dataTable(cmd.schemaName(), cmd.tableName());
+ }
+
+ if (tbl == null) {
+ if (!cmd.ifTableExists())
+ throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND,
+ cmd.tableName());
+ }
+ else {
+ List<QueryField> cols = new ArrayList<>(cmd.columns().length);
+
+ for (GridSqlColumn col : cmd.columns()) {
+ if (tbl.doesColumnExist(col.columnName())) {
+ if ((!cmd.ifNotExists() || cmd.columns().length != 1)) {
+ throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_EXISTS,
+ col.columnName());
+ }
+ else {
+ cols = null;
+
+ break;
+ }
+ }
+
+ cols.add(new QueryField(col.columnName(),
+ DataType.getTypeClassName(col.column().getType())));
+ }
+
+ if (cols != null) {
+ assert tbl.rowDescriptor() != null;
+
+ fut = ctx.query().dynamicColumnAdd(tbl.cacheName(), cmd.schemaName(),
+ tbl.rowDescriptor().type().tableName(), cols, cmd.ifTableExists(), cmd.ifNotExists());
+ }
+ }
+ }
else
throw new IgniteSQLException("Unsupported DDL operation: " + sql,
IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
@@ -311,6 +359,6 @@ public class DdlStatementsProcessor {
*/
public static boolean isDdlStatement(Prepared cmd) {
return cmd instanceof CreateIndex || cmd instanceof DropIndex || cmd instanceof CreateTable ||
- cmd instanceof DropTable;
+ cmd instanceof DropTable || cmd instanceof AlterTableAlterColumn;
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/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 107e3bb..694346c 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
@@ -32,6 +32,9 @@ import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.KeyCacheObject;
import org.apache.ignite.internal.processors.cache.query.QueryTable;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.QueryField;
+import org.apache.ignite.internal.processors.query.h2.H2RowDescriptor;
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;
@@ -49,9 +52,11 @@ import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.schema.SchemaObject;
+import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableBase;
import org.h2.table.TableType;
+import org.h2.value.DataType;
import org.h2.value.Value;
import org.jetbrains.annotations.Nullable;
import org.jsr166.ConcurrentHashMap8;
@@ -68,7 +73,7 @@ public class GridH2Table extends TableBase {
private final GridCacheContext cctx;
/** */
- private final GridH2RowDescriptor desc;
+ private final H2RowDescriptor desc;
/** */
private volatile ArrayList<Index> idxs;
@@ -121,7 +126,7 @@ public class GridH2Table extends TableBase {
* @param idxsFactory Indexes factory.
* @param cctx Cache context.
*/
- public GridH2Table(CreateTableData createTblData, GridH2RowDescriptor desc, H2RowFactory rowFactory,
+ public GridH2Table(CreateTableData createTblData, H2RowDescriptor desc, H2RowFactory rowFactory,
GridH2SystemIndexFactory idxsFactory, GridCacheContext cctx) {
super(createTblData);
@@ -949,4 +954,50 @@ public class GridH2Table extends TableBase {
return null;
}
+
+ /**
+ * Add new columns to this table.
+ *
+ * @param cols Columns to add.
+ * @param ifNotExists Ignore this command if {@code cols} has size of 1 and column with given name already exists.
+ */
+ public void addColumns(List<QueryField> cols, boolean ifNotExists) {
+ assert !ifNotExists || cols.size() == 1;
+
+ lock(true);
+
+ try {
+ int pos = columns.length;
+
+ Column[] newCols = new Column[columns.length + cols.size()];
+
+ // First, let's copy existing columns to new array
+ System.arraycopy(columns, 0, newCols, 0, columns.length);
+
+ // And now, let's add new columns
+ for (QueryField col : cols) {
+ if (doesColumnExist(col.name())) {
+ if (ifNotExists && cols.size() == 1)
+ return;
+ else
+ throw new IgniteSQLException("Column already exists [tblName=" + getName() +
+ ", colName=" + col.name() + ']');
+ }
+
+ try {
+ newCols[pos++] = new Column(col.name(), DataType.getTypeFromClass(Class.forName(col.typeName())));
+ }
+ catch (ClassNotFoundException e) {
+ throw new IgniteSQLException("H2 data type not found for class: " + col.typeName(), e);
+ }
+ }
+
+ setColumns(newCols);
+
+ desc.refreshMetadataFromTypeDescriptor();
+ }
+ finally {
+ unlock(true);
+ }
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
new file mode 100644
index 0000000..40423c7
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlAlterTableAddColumn.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+/**
+ * ALTER TABLE ADD COLUMN command data holder.
+ */
+public class GridSqlAlterTableAddColumn extends GridSqlStatement {
+ /** Schema name. */
+ private String schemaName;
+
+ /** Target table name. */
+ private String tblName;
+
+ /** Columns to add. */
+ private GridSqlColumn[] cols;
+
+ /** Quietly abort this command if column exists (honored only in single column case). */
+ private boolean ifNotExists;
+
+ /** Quietly abort this command if target table does not exist. */
+ private boolean ifTblExists;
+
+ /**
+ * @return Columns to add.
+ */
+ public GridSqlColumn[] columns() {
+ return cols;
+ }
+
+ /**
+ * @param cols Columns to add.
+ */
+ public void columns(GridSqlColumn[] cols) {
+ this.cols = cols;
+ }
+
+ /**
+ * @return Quietly abort this command if column exists (honored only in single column case).
+ */
+ public boolean ifNotExists() {
+ return ifNotExists;
+ }
+
+ /**
+ * @param ifNotExists Quietly abort this command if column exists (honored only in single column case).
+ */
+ public void ifNotExists(boolean ifNotExists) {
+ this.ifNotExists = ifNotExists;
+ }
+
+ /**
+ * @return Quietly abort this command if target table does not exist.
+ */
+ public boolean ifTableExists() {
+ return ifTblExists;
+ }
+
+ /**
+ * @param ifTblExists Quietly abort this command if target table does not exist.
+ */
+ public void ifTableExists(boolean ifTblExists) {
+ this.ifTblExists = ifTblExists;
+ }
+
+ /**
+ * @return Target table name.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * @param tblName Target table name.
+ */
+ public void tableName(String tblName) {
+ this.tblName = tblName;
+ }
+
+ /**
+ * @return Schema name.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * @param schemaName Schema name.
+ */
+ public void schemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getSQL() {
+ throw new UnsupportedOperationException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/adec3e7e/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 0d6a0b2..97e9d5d 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
@@ -38,8 +38,10 @@ import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.h2.command.Command;
import org.h2.command.CommandContainer;
+import org.h2.command.CommandInterface;
import org.h2.command.Prepared;
import org.h2.command.ddl.AlterTableAddConstraint;
+import org.h2.command.ddl.AlterTableAlterColumn;
import org.h2.command.ddl.CreateIndex;
import org.h2.command.ddl.CreateTable;
import org.h2.command.ddl.CreateTableData;
@@ -393,6 +395,30 @@ public class GridSqlQueryParser {
private static final Getter<Column, Expression> COLUMN_CHECK_CONSTRAINT = getter(Column.class, "checkConstraint");
/** */
+ private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_TBL_NAME =
+ getter(AlterTableAlterColumn.class, "tableName");
+
+ /** */
+ private static final Getter<AlterTableAlterColumn, ArrayList<Column>> ALTER_COLUMN_NEW_COLS =
+ getter(AlterTableAlterColumn.class, "columnsToAdd");
+
+ /** */
+ private static final Getter<AlterTableAlterColumn, Boolean> ALTER_COLUMN_IF_NOT_EXISTS =
+ getter(AlterTableAlterColumn.class, "ifNotExists");
+
+ /** */
+ private static final Getter<AlterTableAlterColumn, Boolean> ALTER_COLUMN_IF_TBL_EXISTS =
+ getter(AlterTableAlterColumn.class, "ifTableExists");
+
+ /** */
+ private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_BEFORE_COL =
+ getter(AlterTableAlterColumn.class, "addBefore");
+
+ /** */
+ private static final Getter<AlterTableAlterColumn, String> ALTER_COLUMN_AFTER_COL =
+ getter(AlterTableAlterColumn.class, "addAfter");
+
+ /** */
private static final String PARAM_NAME_VALUE_SEPARATOR = "=";
/** */
@@ -885,41 +911,8 @@ public class GridSqlQueryParser {
LinkedHashMap<String, GridSqlColumn> cols = new LinkedHashMap<>(data.columns.size());
- for (Column col : data.columns) {
- if (col.isAutoIncrement())
- throw new IgniteSQLException("AUTO_INCREMENT columns are not supported [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- if (!col.isNullable())
- throw new IgniteSQLException("Non nullable columns are forbidden [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.PARSING);
-
- if (COLUMN_IS_COMPUTED.get(col))
- throw new IgniteSQLException("Computed columns are not supported [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- if (col.getDefaultExpression() != null)
- throw new IgniteSQLException("DEFAULT expressions are not supported [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- if (col.getSequence() != null)
- throw new IgniteSQLException("SEQUENCE columns are not supported [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- if (col.getSelectivity() != Constants.SELECTIVITY_DEFAULT)
- throw new IgniteSQLException("SELECTIVITY column attr is not supported [colName=" + col.getName() + ']',
- IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- if (COLUMN_CHECK_CONSTRAINT.get(col) != null)
- throw new IgniteSQLException("Column CHECK constraints are not supported [colName=" + col.getName() +
- ']', IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
-
- GridSqlColumn gridCol = new GridSqlColumn(col, null, col.getName());
-
- gridCol.resultType(GridSqlType.fromColumn(col));
-
- cols.put(col.getName(), gridCol);
- }
+ for (Column col : data.columns)
+ cols.put(col.getName(), parseColumn(col));
if (cols.containsKey(QueryUtils.KEY_FIELD_NAME.toUpperCase()) ||
cols.containsKey(QueryUtils.VAL_FIELD_NAME.toUpperCase()))
@@ -1009,6 +1002,98 @@ public class GridSqlQueryParser {
}
/**
+ * Parse {@code ALTER TABLE} statement.
+ * @param stmt H2 statement.
+ */
+ private GridSqlStatement parseAlterColumn(AlterTableAlterColumn stmt) {
+ switch (stmt.getType()) {
+ case CommandInterface.ALTER_TABLE_ADD_COLUMN:
+ return parseAddColumn(stmt);
+
+ default:
+ throw new IgniteSQLException("Unsupported operation code: " + stmt.getType());
+ }
+ }
+
+ /**
+ * Turn H2 column to grid column and check requested features.
+ * @param col H2 column.
+ * @return Grid column.
+ */
+ private static GridSqlColumn parseColumn(Column col) {
+ if (col.isAutoIncrement())
+ throw new IgniteSQLException("AUTO_INCREMENT columns are not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (!col.isNullable())
+ throw new IgniteSQLException("Non nullable columns are not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (COLUMN_IS_COMPUTED.get(col))
+ throw new IgniteSQLException("Computed columns are not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (col.getDefaultExpression() != null)
+ throw new IgniteSQLException("DEFAULT expressions are not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (col.getSequence() != null)
+ throw new IgniteSQLException("SEQUENCE columns are not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (col.getSelectivity() != Constants.SELECTIVITY_DEFAULT)
+ throw new IgniteSQLException("SELECTIVITY column attribute is not supported [colName=" + col.getName() + ']',
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ if (COLUMN_CHECK_CONSTRAINT.get(col) != null)
+ throw new IgniteSQLException("Column CHECK constraints are not supported [colName=" + col.getName() +
+ ']', IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ GridSqlColumn gridCol = new GridSqlColumn(col, null, col.getName());
+
+ gridCol.resultType(GridSqlType.fromColumn(col));
+
+ return gridCol;
+ }
+
+ /**
+ * Parse {@code ALTER TABLE ... ADD COLUMN} statement.
+ * @param addCol H2 statement.
+ * @see <a href="http://www.h2database.com/html/grammar.html#alter_table_add"></a>
+ */
+ private GridSqlStatement parseAddColumn(AlterTableAlterColumn addCol) {
+ assert addCol.getType() == CommandInterface.ALTER_TABLE_ADD_COLUMN;
+
+ if (ALTER_COLUMN_BEFORE_COL.get(addCol) != null || ALTER_COLUMN_AFTER_COL.get(addCol) != null)
+ throw new IgniteSQLException("ALTER TABLE ADD COLUMN BEFORE/AFTER is not supported",
+ IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+
+ GridSqlAlterTableAddColumn res = new GridSqlAlterTableAddColumn();
+
+ ArrayList<Column> h2NewCols = ALTER_COLUMN_NEW_COLS.get(addCol);
+
+ GridSqlColumn[] gridNewCols = new GridSqlColumn[h2NewCols.size()];
+
+ for (int i = 0; i < h2NewCols.size(); i++)
+ gridNewCols[i] = parseColumn(h2NewCols.get(i));
+
+ res.columns(gridNewCols);
+
+ if (gridNewCols.length == 1)
+ res.ifNotExists(ALTER_COLUMN_IF_NOT_EXISTS.get(addCol));
+
+ res.ifTableExists(ALTER_COLUMN_IF_TBL_EXISTS.get(addCol));
+
+ Schema schema = SCHEMA_COMMAND_SCHEMA.get(addCol);
+
+ res.schemaName(schema.getName());
+
+ res.tableName(ALTER_COLUMN_TBL_NAME.get(addCol));
+
+ return res;
+ }
+
+ /**
* @param name Param name.
* @param val Param value.
* @param res Table params to update.
@@ -1206,7 +1291,10 @@ public class GridSqlQueryParser {
return parseCreateTable((CreateTable)stmt);
if (stmt instanceof DropTable)
- return parseDropTable((DropTable) stmt);
+ return parseDropTable((DropTable)stmt);
+
+ if (stmt instanceof AlterTableAlterColumn)
+ return parseAlterColumn((AlterTableAlterColumn)stmt);
throw new CacheException("Unsupported SQL statement: " + stmt);
}