ignite-1979 Support case insensitive nonquoted cache names in SQL - Fixes #324.
Signed-off-by: S.Vladykin <svlady...@gridgain.com> Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/da24df99 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/da24df99 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/da24df99 Branch: refs/heads/ignite-1537 Commit: da24df99525b796a79fdb55997efe1c9bd515a5d Parents: a0cdb59 Author: vershov <vers...@gridgain.com> Authored: Tue Dec 22 00:08:37 2015 +0300 Committer: S.Vladykin <svlady...@gridgain.com> Committed: Tue Dec 22 00:08:37 2015 +0300 ---------------------------------------------------------------------- .../configuration/CacheConfiguration.java | 44 +++- .../ignite/internal/util/IgniteUtils.java | 6 +- .../cache/VisorCacheQueryConfiguration.java | 11 + .../processors/query/h2/IgniteH2Indexing.java | 118 ++++++--- .../query/h2/sql/GridSqlQuerySplitter.java | 48 ++-- .../query/IgniteSqlSchemaIndexingTest.java | 240 +++++++++++++++++++ .../IgniteCacheQuerySelfTestSuite.java | 2 + .../commands/cache/VisorCacheCommand.scala | 13 +- 8 files changed, 416 insertions(+), 66 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java index be1240c..d52662e 100644 --- a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java @@ -365,15 +365,18 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { private IgnitePredicate<ClusterNode> nodeFilter; /** */ - private boolean sqlEscapeAll; + private String sqlSchema; /** */ - private transient Class<?>[] indexedTypes; + private boolean sqlEscapeAll; /** */ private int sqlOnheapRowCacheSize = DFLT_SQL_ONHEAP_ROW_CACHE_SIZE; /** */ + private transient Class<?>[] indexedTypes; + + /** */ private boolean snapshotableIdx; /** Copy on read flag. */ @@ -466,6 +469,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { rebalanceTimeout = cc.getRebalanceTimeout(); rebalanceThrottle = cc.getRebalanceThrottle(); snapshotableIdx = cc.isSnapshotableIndex(); + sqlSchema = cc.getSqlSchema(); sqlEscapeAll = cc.isSqlEscapeAll(); sqlFuncCls = cc.getSqlFunctionClasses(); sqlOnheapRowCacheSize = cc.getSqlOnheapRowCacheSize(); @@ -1770,7 +1774,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { } /** - * Gets timeout in milliseconds after which long query warning will be printed. + * Sets timeout in milliseconds after which long query warning will be printed. * * @param longQryWarnTimeout Timeout in milliseconds. * @return {@code this} for chaining. @@ -1782,6 +1786,40 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> { } /** + * Gets custom name of the sql schema. If custom sql schema is not set then {@code null} will be returned and + * quoted case sensitive name will be used as sql schema. + * + * @return Schema name for current cache according to SQL ANSI-99. Could be {@code null}. + */ + @Nullable public String getSqlSchema() { + return sqlSchema; + } + + /** + * Sets sql schema to be used for current cache. This name will correspond to SQL ANSI-99 standard. + * Nonquoted identifiers are not case sensitive. Quoted identifiers are case sensitive. + * <p/> + * Be aware of using the same string in case sensitive and case insensitive manner simultaneously, since + * behaviour for such case is not specified. + * <p/> + * When sqlSchema is not specified, quoted {@code cacheName} is used instead. + * <p/> + * {@code sqlSchema} could not be an empty string. Has to be {@code "\"\""} instead. + * + * @param sqlSchema Schema name for current cache according to SQL ANSI-99. Should not be {@code null}. + * + * @return {@code this} for chaining. + */ + public CacheConfiguration<K, V> setSqlSchema(String sqlSchema) { + A.ensure((sqlSchema != null), "Schema could not be null."); + A.ensure(!sqlSchema.isEmpty(), "Schema could not be empty."); + + this.sqlSchema = sqlSchema; + + return this; + } + + /** * If {@code true} all the SQL table and field names will be escaped with double quotes like * ({@code "tableName"."fieldsName"}). This enforces case sensitivity for field names and * also allows having special characters in table and field names. http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index e74b3f0..be4851d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -4243,11 +4243,11 @@ public abstract class IgniteUtils { /** * Mask component name to make sure that it is not {@code null}. * - * @param cacheName Component name to mask, possibly {@code null}. + * @param name Component name to mask, possibly {@code null}. * @return Component name. */ - public static String maskName(@Nullable String cacheName) { - return cacheName == null ? "default" : cacheName; + public static String maskName(@Nullable String name) { + return name == null ? "default" : name; } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryConfiguration.java index cbb97a5..a779db4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryConfiguration.java @@ -35,6 +35,9 @@ public class VisorCacheQueryConfiguration implements Serializable { private long longQryWarnTimeout; /** */ + private String sqlSchema; + + /** */ private boolean sqlEscapeAll; /** */ @@ -69,6 +72,7 @@ public class VisorCacheQueryConfiguration implements Serializable { cfg.sqlFuncClss = compactClasses(ccfg.getSqlFunctionClasses()); cfg.longQryWarnTimeout = ccfg.getLongQueryWarningTimeout(); + cfg.sqlSchema = ccfg.getSqlSchema(); cfg.sqlEscapeAll = ccfg.isSqlEscapeAll(); cfg.indexedTypes = compactClasses(ccfg.getIndexedTypes()); cfg.sqlOnheapRowCacheSize = ccfg.getSqlOnheapRowCacheSize(); @@ -91,6 +95,13 @@ public class VisorCacheQueryConfiguration implements Serializable { } /** + * @return Schema name, which is used by SQL engine for SQL statements generation. + */ + public String sqlSchema() { + return sqlSchema; + } + + /** * @return {@code true} if SQL engine generate SQL statements with escaped names. */ public boolean sqlEscapeAll() { http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/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 8625be9..dead526 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 @@ -195,6 +195,12 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** */ private static final Field COMMAND_FIELD; + /** */ + private static final char ESC_CH = '\"'; + + /** */ + private static final String ESC_STR = ESC_CH + "" + ESC_CH; + /** * Command in H2 prepared statement. */ @@ -236,6 +242,9 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** */ private GridReduceQueryExecutor rdcQryExec; + /** space name -> schema name */ + private final Map<String, String> space2schema = new ConcurrentHashMap8<>(); + /** */ private final ThreadLocal<ConnectionWrapper> connCache = new ThreadLocal<ConnectionWrapper>() { @Nullable @Override public ConnectionWrapper get() { @@ -360,7 +369,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { try { stmt = c.connection().createStatement(); - stmt.executeUpdate("SET SCHEMA \"" + schema + '"'); + stmt.executeUpdate("SET SCHEMA " + schema); if (log.isDebugEnabled()) log.debug("Set schema: " + schema); @@ -386,7 +395,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { * @throws IgniteCheckedException If failed to create db schema. */ private void createSchema(String schema) throws IgniteCheckedException { - executeStatement("INFORMATION_SCHEMA", "CREATE SCHEMA IF NOT EXISTS \"" + schema + '"'); + executeStatement("INFORMATION_SCHEMA", "CREATE SCHEMA IF NOT EXISTS " + schema); if (log.isDebugEnabled()) log.debug("Created H2 schema for index database: " + schema); @@ -399,7 +408,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { * @throws IgniteCheckedException If failed to create db schema. */ private void dropSchema(String schema) throws IgniteCheckedException { - executeStatement("INFORMATION_SCHEMA", "DROP SCHEMA IF EXISTS \"" + schema + '"'); + executeStatement("INFORMATION_SCHEMA", "DROP SCHEMA IF EXISTS " + schema); if (log.isDebugEnabled()) log.debug("Dropped H2 schema for index database: " + schema); @@ -642,7 +651,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { if (log.isDebugEnabled()) log.debug("Removing query index table: " + tbl.fullTableName()); - Connection c = connectionForThread(tbl.schema()); + Connection c = connectionForThread(tbl.schemaName()); Statement stmt = null; @@ -767,6 +776,22 @@ public class IgniteH2Indexing implements GridQueryIndexing { } /** + * Stores rule for constructing schemaName according to cache configuration. + * + * @param ccfg Cache configuration. + * @return Proper schema name according to ANSI-99 standard. + */ + private static String schemaNameFromCacheConf(CacheConfiguration<?,?> ccfg) { + if (ccfg.getSqlSchema() == null) + return escapeName(ccfg.getName(), true); + + if (ccfg.getSqlSchema().charAt(0) == ESC_CH) + return ccfg.getSqlSchema(); + + return ccfg.isSqlEscapeAll() ? escapeName(ccfg.getSqlSchema(), true) : ccfg.getSqlSchema().toUpperCase(); + } + + /** * Executes sql query. * * @param conn Connection,. @@ -865,9 +890,9 @@ public class IgniteH2Indexing implements GridQueryIndexing { * @return Result set. * @throws IgniteCheckedException If failed. */ - private ResultSet executeQuery(String space, String qry, @Nullable Collection<Object> params, - TableDescriptor tbl) throws IgniteCheckedException { - Connection conn = connectionForThread(tbl.schema()); + private ResultSet executeQuery(String space, String qry, @Nullable Collection<Object> params, TableDescriptor tbl) + throws IgniteCheckedException { + Connection conn = connectionForThread(tbl.schemaName()); String sql = generateQuery(qry, tbl); @@ -1027,7 +1052,8 @@ public class IgniteH2Indexing implements GridQueryIndexing { } try { - twoStepQry = GridSqlQuerySplitter.split((JdbcPreparedStatement)stmt, qry.getArgs(), qry.isCollocated()); + twoStepQry = GridSqlQuerySplitter.split( + (JdbcPreparedStatement)stmt, qry.getArgs(), qry.isCollocated(), this); meta = meta(stmt.getMetaData()); } @@ -1174,6 +1200,16 @@ public class IgniteH2Indexing implements GridQueryIndexing { } /** + * Returns empty string, if {@code nullableString} is empty. + * + * @param nullableString String for convertion. Could be null. + * @return Non null string. Could be empty. + */ + private static String emptyIfNull(String nullableString) { + return nullableString == null ? "" : nullableString; + } + + /** * Escapes name to be valid SQL identifier. Currently just replaces '.' and '$' sign with '_'. * * @param name Name. @@ -1181,8 +1217,11 @@ public class IgniteH2Indexing implements GridQueryIndexing { * @return Escaped name. */ private static String escapeName(String name, boolean escapeAll) { + if (name == null) // It is possible only for a cache name. + return ESC_STR; + if (escapeAll) - return "\"" + name + "\""; + return ESC_CH + name + ESC_CH; SB sb = null; @@ -1304,24 +1343,32 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** * Gets database schema from space. * - * @param space Space name. - * @return Schema name. + * @param space Space name. {@code null} would be converted to an empty string. + * @return Schema name. Should not be null since we should not fail for an invalid space name. */ - public static String schema(@Nullable String space) { - if (space == null) - return ""; - - return space; + private String schema(@Nullable String space) { + return emptyIfNull(space2schema.get(emptyIfNull(space))); } /** - * @param schema Schema. - * @return Space name. + * Gets space name from database schema. + * + * @param schemaName Schema name. Could not be null. Could be empty. + * @return Space name. Could be null. */ - public static String space(String schema) { - assert schema != null; + public String space(String schemaName) { + assert schemaName != null; + + Schema schema = schemas.get(schemaName); + + // For the compatibility with conversion from """" to "" inside h2 lib + if (schema == null) { + assert schemaName.isEmpty() || schemaName.charAt(0) != ESC_CH; + + schema = schemas.get(escapeName(schemaName, true)); + } - return "".equals(schema) ? null : schema; + return schema.spaceName; } /** {@inheritDoc} */ @@ -1427,7 +1474,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { rdcQryExec.start(ctx, this); } - // TODO https://issues.apache.org/jira/browse/IGNITE-751 + // TODO https://issues.apache.org/jira/browse/IGNITE-2139 // registerMBean(gridName, this, GridH2IndexingSpiMBean.class); } @@ -1485,7 +1532,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { if (log.isDebugEnabled()) log.debug("Stopping cache query index..."); -// unregisterMBean(); TODO +// unregisterMBean(); TODO https://issues.apache.org/jira/browse/IGNITE-2139 for (Schema schema : schemas.values()) { for (TableDescriptor desc : schema.tbls.values()) { @@ -1501,6 +1548,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { conns.clear(); schemas.clear(); + space2schema.clear(); try (Connection c = DriverManager.getConnection(dbUrl); Statement s = c.createStatement()) { @@ -1516,12 +1564,14 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** {@inheritDoc} */ @Override public void registerCache(CacheConfiguration<?,?> ccfg) throws IgniteCheckedException { - String schema = schema(ccfg.getName()); + String schema = schemaNameFromCacheConf(ccfg); - if (schemas.putIfAbsent(schema, new Schema(ccfg.getName(), + if (schemas.putIfAbsent(schema, new Schema(ccfg.getName(), schema, ccfg.getOffHeapMaxMemory() >= 0 || ccfg.getMemoryMode() == CacheMemoryMode.OFFHEAP_TIERED ? new GridUnsafeMemory(0) : null, ccfg)) != null) - throw new IgniteCheckedException("Cache already registered: " + U.maskName(ccfg.getName())); + throw new IgniteCheckedException("Schema for cache already registered: " + U.maskName(ccfg.getName())); + + space2schema.put(emptyIfNull(ccfg.getName()), schema); createSchema(schema); @@ -1531,10 +1581,10 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** {@inheritDoc} */ @Override public void unregisterCache(CacheConfiguration<?, ?> ccfg) { String schema = schema(ccfg.getName()); - Schema rmv = schemas.remove(schema); if (rmv != null) { + space2schema.remove(rmv.spaceName); mapQryExec.onCacheStop(ccfg.getName()); try { @@ -1855,15 +1905,14 @@ public class IgniteH2Indexing implements GridQueryIndexing { this.type = type; this.schema = schema; - fullTblName = '\"' + IgniteH2Indexing.schema(schema.spaceName) + "\"." + - escapeName(type.name(), schema.escapeAll()); + fullTblName = schema.schemaName + "." + escapeName(type.name(), schema.escapeAll()); } /** * @return Schema name. */ - public String schema() { - return IgniteH2Indexing.schema(schema.spaceName); + public String schemaName() { + return schema.schemaName; } /** @@ -2129,6 +2178,9 @@ public class IgniteH2Indexing implements GridQueryIndexing { private final String spaceName; /** */ + private final String schemaName; + + /** */ private final GridUnsafeMemory offheap; /** */ @@ -2142,11 +2194,13 @@ public class IgniteH2Indexing implements GridQueryIndexing { /** * @param spaceName Space name. + * @param schemaName Schema name. * @param offheap Offheap memory. * @param ccfg Cache configuration. */ - private Schema(@Nullable String spaceName, GridUnsafeMemory offheap, CacheConfiguration<?,?> ccfg) { + private Schema(@Nullable String spaceName, String schemaName, GridUnsafeMemory offheap, CacheConfiguration<?,?> ccfg) { this.spaceName = spaceName; + this.schemaName = schemaName; this.offheap = offheap; this.ccfg = ccfg; http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java index 727c2c7..3d9c10a 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java @@ -138,18 +138,19 @@ public class GridSqlQuerySplitter { * @param stmt Prepared statement. * @param params Parameters. * @param collocated Collocated query. + * @param igniteH2Indexing Indexing implementation. * @return Two step query. */ - public static GridCacheTwoStepQuery split(JdbcPreparedStatement stmt, Object[] params, boolean collocated) { + public static GridCacheTwoStepQuery split(JdbcPreparedStatement stmt, Object[] params, boolean collocated, IgniteH2Indexing igniteH2Indexing) { if (params == null) params = GridCacheSqlQuery.EMPTY_PARAMS; - Set<String> spaces = new HashSet<>(); + Set<String> schemas = new HashSet<>(); // Map query will be direct reference to the original query AST. // Thus all the modifications will be performed on the original AST, so we should be careful when // nullifying or updating things, have to make sure that we will not need them in the original form later. - final GridSqlSelect mapQry = wrapUnion(collectAllSpaces(GridSqlQueryParser.parse(stmt), spaces)); + final GridSqlSelect mapQry = wrapUnion(collectAllSpaces(GridSqlQueryParser.parse(stmt), schemas)); final boolean explain = mapQry.explain(); @@ -258,6 +259,11 @@ public class GridSqlQuerySplitter { map.parameterIndexes(toIntArray(paramIdxs)); + Set<String> spaces = new HashSet<>(schemas.size()); + + for (String schema : schemas) + spaces.add(igniteH2Indexing.space(schema)); + // Build resulting two step query. GridCacheTwoStepQuery res = new GridCacheTwoStepQuery(spaces, rdc, rdcQry.simpleQuery()).addMapQuery(map); @@ -309,25 +315,25 @@ public class GridSqlQuerySplitter { /** * @param qry Query. - * @param spaces Space names. + * @param schemas Shemas' names. * @return Query. */ - private static GridSqlQuery collectAllSpaces(GridSqlQuery qry, Set<String> spaces) { + private static GridSqlQuery collectAllSpaces(GridSqlQuery qry, Set<String> schemas) { if (qry instanceof GridSqlUnion) { GridSqlUnion union = (GridSqlUnion)qry; - collectAllSpaces(union.left(), spaces); - collectAllSpaces(union.right(), spaces); + collectAllSpaces(union.left(), schemas); + collectAllSpaces(union.right(), schemas); } else { GridSqlSelect select = (GridSqlSelect)qry; - collectAllSpacesInFrom(select.from(), spaces); + collectAllSpacesInFrom(select.from(), schemas); for (GridSqlElement el : select.columns(false)) - collectAllSpacesInSubqueries(el, spaces); + collectAllSpacesInSubqueries(el, schemas); - collectAllSpacesInSubqueries(select.where(), spaces); + collectAllSpacesInSubqueries(select.where(), schemas); } return qry; @@ -335,26 +341,26 @@ public class GridSqlQuerySplitter { /** * @param from From element. - * @param spaces Space names. + * @param schemas Shemas' names. */ - private static void collectAllSpacesInFrom(GridSqlElement from, Set<String> spaces) { + private static void collectAllSpacesInFrom(GridSqlElement from, Set<String> schemas) { assert from != null; if (from instanceof GridSqlJoin) { // Left and right. - collectAllSpacesInFrom(from.child(0), spaces); - collectAllSpacesInFrom(from.child(1), spaces); + collectAllSpacesInFrom(from.child(0), schemas); + collectAllSpacesInFrom(from.child(1), schemas); } else if (from instanceof GridSqlTable) { String schema = ((GridSqlTable)from).schema(); if (schema != null) - spaces.add(IgniteH2Indexing.space(schema)); + schemas.add(schema); } else if (from instanceof GridSqlSubquery) - collectAllSpaces(((GridSqlSubquery)from).select(), spaces); + collectAllSpaces(((GridSqlSubquery)from).select(), schemas); else if (from instanceof GridSqlAlias) - collectAllSpacesInFrom(from.child(), spaces); + collectAllSpacesInFrom(from.child(), schemas); else if (!(from instanceof GridSqlFunction)) throw new IllegalStateException(from.getClass().getName() + " : " + from.getSQL()); } @@ -362,18 +368,18 @@ public class GridSqlQuerySplitter { /** * Searches spaces in subqueries in SELECT and WHERE clauses. * @param el Element. - * @param spaces Space names. + * @param schemas Schemas' names. */ - private static void collectAllSpacesInSubqueries(GridSqlElement el, Set<String> spaces) { + private static void collectAllSpacesInSubqueries(GridSqlElement el, Set<String> schemas) { if (el instanceof GridSqlAlias) el = el.child(); if (el instanceof GridSqlOperation || el instanceof GridSqlFunction) { for (GridSqlElement child : el) - collectAllSpacesInSubqueries(child, spaces); + collectAllSpacesInSubqueries(child, schemas); } else if (el instanceof GridSqlSubquery) - collectAllSpaces(((GridSqlSubquery)el).select(), spaces); + collectAllSpaces(((GridSqlSubquery)el).select(), schemas); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteSqlSchemaIndexingTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteSqlSchemaIndexingTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteSqlSchemaIndexingTest.java new file mode 100644 index 0000000..5c9acb5 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteSqlSchemaIndexingTest.java @@ -0,0 +1,240 @@ +/* + * 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 java.util.List; +import java.util.concurrent.Callable; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.Ignition; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.query.SqlFieldsQuery; +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.processors.query.h2.IgniteH2Indexing; +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; + +/** + * Tests {@link IgniteH2Indexing} support {@link CacheConfiguration#setSqlSchema(String)} configuration. + */ +@SuppressWarnings("unchecked") +public class IgniteSqlSchemaIndexingTest extends GridCommonAbstractTest { + /** */ + private static final TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true); + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration() throws Exception { + IgniteConfiguration cfg = super.getConfiguration(); + + cfg.setPeerClassLoadingEnabled(false); + + TcpDiscoverySpi disco = new TcpDiscoverySpi(); + + disco.setIpFinder(ipFinder); + + cfg.setDiscoverySpi(disco); + + return cfg; + } + + /** + * @param name Cache name. + * @param partitioned Partition or replicated cache. + * @param idxTypes Indexed types. + * @return Cache configuration. + */ + private static CacheConfiguration cacheConfig(String name, boolean partitioned, Class<?>... idxTypes) { + return new CacheConfiguration() + .setName(name) + .setCacheMode(partitioned ? CacheMode.PARTITIONED : CacheMode.REPLICATED) + .setAtomicityMode(CacheAtomicityMode.ATOMIC) + .setBackups(1) + .setIndexedTypes(Integer.class, Fact.class); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(); + } + + /** + * Tests collision for case insensitive sqlScheme. + * + * @throws Exception If failed. + */ + public void testCaseSensitive() throws Exception { + //TODO rewrite with dynamic cache creation, and GRID start in #beforeTest after resolve of + //https://issues.apache.org/jira/browse/IGNITE-1094 + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + final CacheConfiguration cfg = cacheConfig("InSensitiveCache", true, Integer.class, Integer.class) + .setSqlSchema("InsensitiveCache"); + final CacheConfiguration collisionCfg = cacheConfig("InsensitiveCache", true, Integer.class, Integer.class) + .setSqlSchema("Insensitivecache"); + IgniteConfiguration icfg = new IgniteConfiguration() + .setLocalHost("127.0.0.1") + .setCacheConfiguration(cfg, collisionCfg); + + Ignition.start(icfg); + + return null; + } + }, IgniteException.class, "Schema for cache already registered"); + } + + /** + * Tests unregistration of previous scheme. + * + * @throws Exception If failed. + */ + public void testCacheUnregistration() throws Exception { + startGridsMultiThreaded(3, true); + + final CacheConfiguration<Integer, Fact> cfg = cacheConfig("Insensitive_Cache", true, Integer.class, Fact.class) + .setSqlSchema("Insensitive_Cache"); + final CacheConfiguration<Integer, Fact> collisionCfg = cacheConfig("InsensitiveCache", true, Integer.class, Fact.class) + .setSqlSchema("Insensitive_Cache"); + + IgniteCache<Integer, Fact> cache = ignite(0).createCache(cfg); + + SqlFieldsQuery qry = new SqlFieldsQuery("select f.id, f.name " + + "from InSENSitive_Cache.Fact f"); + + cache.put(1, new Fact(1, "cacheInsensitive")); + + for (List<?> row : cache.query(qry)) { + assertEquals(2, row.size()); + assertEquals(1, row.get(0)); + assertEquals("cacheInsensitive", row.get(1)); + } + + ignite(0).destroyCache(cache.getName()); + + cache = ignite(0).createCache(collisionCfg); // Previous collision should be removed by now. + + cache.put(1, new Fact(1, "cacheInsensitive")); + cache.put(2, new Fact(2, "ThisIsANewCache")); + cache.put(3, new Fact(3, "With3RecordsAndAnotherName")); + + assertEquals(3, cache.query(qry).getAll().size()); + + ignite(0).destroyCache(cache.getName()); + } + + /** + * Tests escapeAll and sqlSchema apposition. + * + * @throws Exception If failed. + */ + public void testSchemaEscapeAll() throws Exception { + startGridsMultiThreaded(3, true); + + final CacheConfiguration<Integer, Fact> cfg = cacheConfig("simpleSchema", true, Integer.class, Fact.class) + .setSqlSchema("SchemaName1") + .setSqlEscapeAll(true); + + final CacheConfiguration<Integer, Fact> cfgEsc = cacheConfig("escapedSchema", true, Integer.class, Fact.class) + .setSqlSchema("\"SchemaName2\"") + .setSqlEscapeAll(true); + + escapeCheckSchemaName(ignite(0).createCache(cfg), log, cfg.getSqlSchema()); + + escapeCheckSchemaName(ignite(0).createCache(cfgEsc), log, "SchemaName2"); + + ignite(0).destroyCache(cfg.getName()); + ignite(0).destroyCache(cfgEsc.getName()); + } + + /** + * Executes query with and without escaped schema name. + * @param cache cache for querying + * @param log logger for assertThrows + * @param schemaName - schema name without quotes for testing + */ + private static void escapeCheckSchemaName(final IgniteCache<Integer, Fact> cache, IgniteLogger log, String schemaName) { + final SqlFieldsQuery qryWrong = new SqlFieldsQuery("select f.id, f.name " + + "from " + schemaName.toUpperCase() + ".Fact f"); + + cache.put(1, new Fact(1, "cacheInsensitive")); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + cache.query(qryWrong); + return null; + } + }, IgniteException.class, "Failed to parse query"); + + SqlFieldsQuery qryCorrect = new SqlFieldsQuery("select f.\"id\", f.\"name\" " + + "from \""+schemaName+"\".\"Fact\" f"); + + for ( List<?> row : cache.query(qryCorrect)) { + assertEquals(2, row.size()); + assertEquals(1, row.get(0)); + assertEquals("cacheInsensitive", row.get(1)); + } + } + + // TODO add tests with dynamic cache unregistration, after resolve of https://issues.apache.org/jira/browse/IGNITE-1094 + + /** Test class as query entity */ + private static class Fact { + /** Primary key. */ + @QuerySqlField + private int id; + + @QuerySqlField + private String name; + + /** + * Constructs a fact. + * + * @param id fact ID. + * @param name fact name. + */ + Fact(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Gets fact ID. + * + * @return fact ID. + */ + public int getId() { + return id; + } + + /** + * Gets fact name. + * + * @return Fact name. + */ + public String getName() { + return name; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/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 fc88c75..7c8d1d7 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 @@ -92,6 +92,7 @@ import org.apache.ignite.internal.processors.cache.reducefields.GridCacheReduceF import org.apache.ignite.internal.processors.cache.reducefields.GridCacheReduceFieldsQueryLocalSelfTest; import org.apache.ignite.internal.processors.cache.reducefields.GridCacheReduceFieldsQueryPartitionedSelfTest; import org.apache.ignite.internal.processors.cache.reducefields.GridCacheReduceFieldsQueryReplicatedSelfTest; +import org.apache.ignite.internal.processors.query.IgniteSqlSchemaIndexingTest; import org.apache.ignite.internal.processors.query.IgniteSqlSplitterSelfTest; import org.apache.ignite.internal.processors.query.h2.sql.BaseH2CompareQueryTest; import org.apache.ignite.internal.processors.query.h2.sql.GridQueryParsingTest; @@ -117,6 +118,7 @@ public class IgniteCacheQuerySelfTestSuite extends TestSuite { // Queries tests. suite.addTestSuite(IgniteSqlSplitterSelfTest.class); + suite.addTestSuite(IgniteSqlSchemaIndexingTest.class); suite.addTestSuite(GridCacheQueryIndexDisabledSelfTest.class); suite.addTestSuite(IgniteCacheQueryLoadSelfTest.class); suite.addTestSuite(IgniteCacheLocalQuerySelfTest.class); http://git-wip-us.apache.org/repos/asf/ignite/blob/da24df99/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/cache/VisorCacheCommand.scala ---------------------------------------------------------------------- diff --git a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/cache/VisorCacheCommand.scala b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/cache/VisorCacheCommand.scala index 90c2de0..0d8d036 100644 --- a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/cache/VisorCacheCommand.scala +++ b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/cache/VisorCacheCommand.scala @@ -17,23 +17,21 @@ package org.apache.ignite.visor.commands.cache +import java.lang.{Boolean => JavaBoolean} +import java.util.{Collection => JavaCollection, Collections, UUID} + import org.apache.ignite._ import org.apache.ignite.cluster.ClusterNode import org.apache.ignite.internal.util.typedef._ +import org.apache.ignite.internal.visor.cache._ +import org.apache.ignite.internal.visor.util.VisorTaskUtils._ import org.apache.ignite.lang.IgniteBiTuple import org.apache.ignite.visor.VisorTag import org.apache.ignite.visor.commands.cache.VisorCacheCommand._ import org.apache.ignite.visor.commands.common.VisorTextTable import org.apache.ignite.visor.visor._ - import org.jetbrains.annotations._ -import java.lang.{Boolean => JavaBoolean} -import java.util.{Collection => JavaCollection, Collections, UUID} - -import org.apache.ignite.internal.visor.cache._ -import org.apache.ignite.internal.visor.util.VisorTaskUtils._ - import scala.collection.JavaConversions._ import scala.language.{implicitConversions, reflectiveCalls} @@ -890,6 +888,7 @@ object VisorCacheCommand { cacheT += ("Expiry Policy Factory Class Name", safe(cfg.expiryPolicyFactory())) cacheT +=("Query Execution Time Threshold", queryCfg.longQueryWarningTimeout()) + cacheT +=("Query Schema Name", queryCfg.sqlSchema()) cacheT +=("Query Escaped Names", bool2Str(queryCfg.sqlEscapeAll())) cacheT +=("Query Onheap Cache Size", queryCfg.sqlOnheapRowCacheSize())