Repository: calcite Updated Branches: refs/heads/master 8dd935ee5 -> 2b9663752
[CALCITE-1563] In case-insensitive connection, non-existent tables use alphabetically preceding table Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/40e5b884 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/40e5b884 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/40e5b884 Branch: refs/heads/master Commit: 40e5b8847b6a2966b024191fb5f65db642d17519 Parents: 91102ba Author: Julian Hyde <[email protected]> Authored: Wed Jan 4 18:36:30 2017 -0800 Committer: Julian Hyde <[email protected]> Committed: Fri Jan 6 10:45:52 2017 -0800 ---------------------------------------------------------------------- .../calcite/jdbc/CachingCalciteSchema.java | 115 +++++------- .../org/apache/calcite/jdbc/CalciteSchema.java | 184 ++++++------------- .../calcite/jdbc/SimpleCalciteSchema.java | 5 +- .../java/org/apache/calcite/util/NameMap.java | 81 ++++++++ .../org/apache/calcite/util/NameMultimap.java | 101 ++++++++++ .../java/org/apache/calcite/util/NameSet.java | 104 +++++++++++ .../java/org/apache/calcite/test/JdbcTest.java | 15 ++ .../java/org/apache/calcite/util/UtilTest.java | 123 +++++++++++++ 8 files changed, 528 insertions(+), 200 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java b/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java index 8defef4..7985bf2 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java @@ -20,7 +20,7 @@ import org.apache.calcite.schema.Function; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.TableMacro; -import org.apache.calcite.util.Compatible; +import org.apache.calcite.util.NameSet; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import java.util.Collection; -import java.util.NavigableSet; +import java.util.Set; /** * Concrete implementation of {@link CalciteSchema} that caches tables, @@ -38,8 +38,8 @@ import java.util.NavigableSet; */ class CachingCalciteSchema extends CalciteSchema { private final Cached<SubSchemaCache> implicitSubSchemaCache; - private final Cached<NavigableSet<String>> implicitTableCache; - private final Cached<NavigableSet<String>> implicitFunctionCache; + private final Cached<NameSet> implicitTableCache; + private final Cached<NameSet> implicitFunctionCache; private boolean cache = true; @@ -50,25 +50,21 @@ class CachingCalciteSchema extends CalciteSchema { new AbstractCached<SubSchemaCache>() { public SubSchemaCache build() { return new SubSchemaCache(CachingCalciteSchema.this, - Compatible.INSTANCE.navigableSet( - ImmutableSortedSet.copyOf(COMPARATOR, - CachingCalciteSchema.this.schema.getSubSchemaNames()))); + CachingCalciteSchema.this.schema.getSubSchemaNames()); } }; this.implicitTableCache = - new AbstractCached<NavigableSet<String>>() { - public NavigableSet<String> build() { - return Compatible.INSTANCE.navigableSet( - ImmutableSortedSet.copyOf(COMPARATOR, - CachingCalciteSchema.this.schema.getTableNames())); + new AbstractCached<NameSet>() { + public NameSet build() { + return NameSet.immutableCopyOf( + CachingCalciteSchema.this.schema.getTableNames()); } }; this.implicitFunctionCache = - new AbstractCached<NavigableSet<String>>() { - public NavigableSet<String> build() { - return Compatible.INSTANCE.navigableSet( - ImmutableSortedSet.copyOf(COMPARATOR, - CachingCalciteSchema.this.schema.getFunctionNames())); + new AbstractCached<NameSet>() { + public NameSet build() { + return NameSet.immutableCopyOf( + CachingCalciteSchema.this.schema.getFunctionNames()); } }; } @@ -90,22 +86,13 @@ class CachingCalciteSchema extends CalciteSchema { protected CalciteSchema getImplicitSubSchema(String schemaName, boolean caseSensitive) { - if (caseSensitive) { - // Check implicit schemas, case-sensitive. - final long now = System.currentTimeMillis(); - final SubSchemaCache subSchemaCache = implicitSubSchemaCache.get(now); - if (subSchemaCache.names.contains(schemaName)) { - return subSchemaCache.cache.getUnchecked(schemaName); - } - } else { - // Check implicit schemas, case-insensitive. - final long now = System.currentTimeMillis(); - final SubSchemaCache subSchemaCache = - implicitSubSchemaCache.get(now); - final String schemaName2 = subSchemaCache.names.floor(schemaName); - if (schemaName2 != null) { - return subSchemaCache.cache.getUnchecked(schemaName2); - } + final long now = System.currentTimeMillis(); + final SubSchemaCache subSchemaCache = + implicitSubSchemaCache.get(now); + //noinspection LoopStatementThatDoesntLoop + for (String schemaName2 + : subSchemaCache.names.range(schemaName, caseSensitive)) { + return subSchemaCache.cache.getUnchecked(schemaName2); } return null; } @@ -120,26 +107,13 @@ class CachingCalciteSchema extends CalciteSchema { protected TableEntry getImplicitTable(String tableName, boolean caseSensitive) { - if (caseSensitive) { - // Check implicit tables, case-sensitive. - final long now = System.currentTimeMillis(); - if (implicitTableCache.get(now).contains(tableName)) { - final Table table = schema.getTable(tableName); - if (table != null) { - return tableEntry(tableName, table); - } - } - } else { - // Check implicit tables, case-insensitive. - final long now = System.currentTimeMillis(); - final NavigableSet<String> implicitTableNames = - implicitTableCache.get(now); - final String tableName2 = implicitTableNames.floor(tableName); - if (tableName2 != null) { - final Table table = schema.getTable(tableName2); - if (table != null) { - return tableEntry(tableName2, table); - } + final long now = System.currentTimeMillis(); + final NameSet implicitTableNames = implicitTableCache.get(now); + for (String tableName2 + : implicitTableNames.range(tableName, caseSensitive)) { + final Table table = schema.getTable(tableName2); + if (table != null) { + return tableEntry(tableName2, table); } } return null; @@ -150,7 +124,7 @@ class CachingCalciteSchema extends CalciteSchema { ImmutableSortedMap<String, CalciteSchema> explicitSubSchemas = builder.build(); final long now = System.currentTimeMillis(); final SubSchemaCache subSchemaCache = implicitSubSchemaCache.get(now); - for (String name : subSchemaCache.names) { + for (String name : subSchemaCache.names.iterable()) { if (explicitSubSchemas.containsKey(name)) { // explicit sub-schema wins. continue; @@ -162,14 +136,17 @@ class CachingCalciteSchema extends CalciteSchema { protected void addImplicitTableToBuilder( ImmutableSortedSet.Builder<String> builder) { // Add implicit tables, case-sensitive. - builder.addAll(implicitTableCache.get(System.currentTimeMillis())); + final long now = System.currentTimeMillis(); + final NameSet set = implicitTableCache.get(now); + builder.addAll(set.iterable()); } - protected void addImplicitFunctionToBuilder( - ImmutableList.Builder<Function> builder) { + protected void addImplicitFunctionsToBuilder( + ImmutableList.Builder<Function> builder, boolean caseSensitive) { // Add implicit functions, case-insensitive. - for (String name2 - : find(implicitFunctionCache.get(System.currentTimeMillis()), name)) { + final long now = System.currentTimeMillis(); + final NameSet set = implicitFunctionCache.get(now); + for (String name2 : set.range(name, caseSensitive)) { final Collection<Function> functions = schema.getFunctions(name2); if (functions != null) { builder.addAll(functions); @@ -180,14 +157,18 @@ class CachingCalciteSchema extends CalciteSchema { protected void addImplicitFuncNamesToBuilder( ImmutableSortedSet.Builder<String> builder) { // Add implicit functions, case-sensitive. - builder.addAll(implicitFunctionCache.get(System.currentTimeMillis())); + final long now = System.currentTimeMillis(); + final NameSet set = implicitFunctionCache.get(now); + builder.addAll(set.iterable()); } protected void addImplicitTablesBasedOnNullaryFunctionsToBuilder( ImmutableSortedMap.Builder<String, Table> builder) { ImmutableSortedMap<String, Table> explicitTables = builder.build(); - for (String s : implicitFunctionCache.get(System.currentTimeMillis())) { + final long now = System.currentTimeMillis(); + final NameSet set = implicitFunctionCache.get(now); + for (String s : set.iterable()) { // explicit table wins. if (explicitTables.containsKey(s)) { continue; @@ -204,9 +185,9 @@ class CachingCalciteSchema extends CalciteSchema { protected TableEntry getImplicitTableBasedOnNullaryFunction(String tableName, boolean caseSensitive) { - final NavigableSet<String> set = - implicitFunctionCache.get(System.currentTimeMillis()); - for (String s : find(set, tableName)) { + final long now = System.currentTimeMillis(); + final NameSet set = implicitFunctionCache.get(now); + for (String s : set.range(tableName, caseSensitive)) { for (Function function : schema.getFunctions(s)) { if (function instanceof TableMacro && function.getParameters().isEmpty()) { @@ -264,14 +245,14 @@ class CachingCalciteSchema extends CalciteSchema { /** Information about the implicit sub-schemas of an {@link CalciteSchema}. */ private static class SubSchemaCache { /** The names of sub-schemas returned from the {@link Schema} SPI. */ - final NavigableSet<String> names; + final NameSet names; /** Cached {@link CalciteSchema} wrappers. It is * worth caching them because they contain maps of their own sub-objects. */ final LoadingCache<String, CalciteSchema> cache; private SubSchemaCache(final CalciteSchema calciteSchema, - NavigableSet<String> names) { - this.names = names; + Set<String> names) { + this.names = NameSet.immutableCopyOf(names); this.cache = CacheBuilder.newBuilder().build( new CacheLoader<String, CalciteSchema>() { @SuppressWarnings("NullableProblems") http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java index a1f24b0..f1614fd 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java @@ -26,25 +26,24 @@ import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.impl.MaterializedViewTable; import org.apache.calcite.schema.impl.StarTable; import org.apache.calcite.util.Compatible; +import org.apache.calcite.util.NameMap; +import org.apache.calcite.util.NameMultimap; +import org.apache.calcite.util.NameSet; +import org.apache.calcite.util.Pair; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; /** * Schema. @@ -52,39 +51,19 @@ import java.util.TreeSet; * <p>Wrapper around user-defined schema used internally.</p> */ public abstract class CalciteSchema { - /** Comparator that compares all strings differently, but if two strings are - * equal in case-insensitive match they are right next to each other. In a - * collection sorted on this comparator, we can find case-insensitive matches - * for a given string using a range scan between the upper-case string and - * the lower-case string. */ - protected static final Comparator<String> COMPARATOR = - new Comparator<String>() { - public int compare(String o1, String o2) { - int c = o1.compareToIgnoreCase(o2); - if (c == 0) { - c = o1.compareTo(o2); - } - return c; - } - }; private final CalciteSchema parent; public final Schema schema; public final String name; /** Tables explicitly defined in this schema. Does not include tables in * {@link #schema}. */ - public final NavigableMap<String, TableEntry> tableMap = - new TreeMap<>(COMPARATOR); - protected final Multimap<String, FunctionEntry> functionMap = - LinkedListMultimap.create(); - protected final NavigableMap<String, LatticeEntry> latticeMap = - new TreeMap<>(COMPARATOR); - protected final NavigableSet<String> functionNames = - new TreeSet<>(COMPARATOR); - protected final NavigableMap<String, FunctionEntry> nullaryFunctionMap = - new TreeMap<>(COMPARATOR); - protected final NavigableMap<String, CalciteSchema> subSchemaMap = - new TreeMap<>(COMPARATOR); + protected final NameMap<TableEntry> tableMap = new NameMap<>(); + protected final NameMultimap<FunctionEntry> functionMap = + new NameMultimap<>(); + protected final NameMap<LatticeEntry> latticeMap = new NameMap<>(); + protected final NameSet functionNames = new NameSet(); + protected final NameMap<FunctionEntry> nullaryFunctionMap = new NameMap<>(); + protected final NameMap<CalciteSchema> subSchemaMap = new NameMap<>(); private ImmutableList<ImmutableList<String>> path; CalciteSchema(CalciteSchema parent, Schema schema, String name) { @@ -120,7 +99,8 @@ public abstract class CalciteSchema { ImmutableSortedSet.Builder<String> builder); /** Adds implicit functions to a builder. */ - protected abstract void addImplicitFunctionToBuilder(ImmutableList.Builder<Function> builder); + protected abstract void addImplicitFunctionsToBuilder( + ImmutableList.Builder<Function> builder, boolean caseSensitive); /** Adds implicit function names to a builder. */ protected abstract void addImplicitFuncNamesToBuilder( @@ -165,7 +145,7 @@ public abstract class CalciteSchema { } private LatticeEntry add(String name, Lattice lattice) { - if (latticeMap.containsKey(name)) { + if (latticeMap.containsKey(name, false)) { throw new RuntimeException("Duplicate lattice '" + name + "'"); } final LatticeEntryImpl entry = new LatticeEntryImpl(this, name, lattice); @@ -205,19 +185,11 @@ public abstract class CalciteSchema { public final CalciteSchema getSubSchema(String schemaName, boolean caseSensitive) { - if (caseSensitive) { - // Check explicit schemas, case-sensitive. - final CalciteSchema entry = subSchemaMap.get(schemaName); - if (entry != null) { - return entry; - } - } else { - // Check explicit schemas, case-insensitive. - //noinspection LoopStatementThatDoesntLoop - for (Map.Entry<String, CalciteSchema> entry - : find(subSchemaMap, schemaName).entrySet()) { - return entry.getValue(); - } + // Check explicit schemas. + //noinspection LoopStatementThatDoesntLoop + for (Map.Entry<String, CalciteSchema> entry + : subSchemaMap.range(schemaName, caseSensitive).entrySet()) { + return entry.getValue(); } return getImplicitSubSchema(schemaName, caseSensitive); } @@ -227,7 +199,7 @@ public abstract class CalciteSchema { /** Returns a table that materializes the given SQL statement. */ public final TableEntry getTableBySql(String sql) { - for (TableEntry tableEntry : tableMap.values()) { + for (TableEntry tableEntry : tableMap.map().values()) { if (tableEntry.sqls.contains(sql)) { return tableEntry; } @@ -237,21 +209,12 @@ public abstract class CalciteSchema { /** Returns a table with the given name. Does not look for views. */ public final TableEntry getTable(String tableName, boolean caseSensitive) { - if (caseSensitive) { - // Check explicit tables, case-sensitive. - final TableEntry entry = tableMap.get(tableName); - if (entry != null) { - return entry; - } - } else { - // Check explicit tables, case-insensitive. - //noinspection LoopStatementThatDoesntLoop - for (Map.Entry<String, TableEntry> entry - : find(tableMap, tableName).entrySet()) { - return entry.getValue(); - } + // Check explicit tables. + //noinspection LoopStatementThatDoesntLoop + for (Map.Entry<String, TableEntry> entry + : tableMap.range(tableName, caseSensitive).entrySet()) { + return entry.getValue(); } - return getImplicitTable(tableName, caseSensitive); } @@ -292,8 +255,8 @@ public abstract class CalciteSchema { // Build a map of implicit sub-schemas first, then explicit sub-schemas. // If there are implicit and explicit with the same name, explicit wins. final ImmutableSortedMap.Builder<String, CalciteSchema> builder = - new ImmutableSortedMap.Builder<>(COMPARATOR); - builder.putAll(subSchemaMap); + new ImmutableSortedMap.Builder<>(NameSet.COMPARATOR); + builder.putAll(subSchemaMap.map()); addImplicitSubSchemaToBuilder(builder); return Compatible.INSTANCE.navigableMap(builder.build()); } @@ -302,16 +265,16 @@ public abstract class CalciteSchema { * * <p>All are explicit (defined using {@link #add(String, Lattice)}). */ public NavigableMap<String, LatticeEntry> getLatticeMap() { - return Compatible.INSTANCE.immutableNavigableMap(latticeMap); + return ImmutableSortedMap.copyOf(latticeMap.map()); } /** Returns the set of all table names. Includes implicit and explicit tables * and functions with zero parameters. */ public final NavigableSet<String> getTableNames() { final ImmutableSortedSet.Builder<String> builder = - new ImmutableSortedSet.Builder<>(COMPARATOR); + new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR); // Add explicit tables, case-sensitive. - builder.addAll(tableMap.keySet()); + builder.addAll(tableMap.map().keySet()); // Add implicit tables, case-sensitive. addImplicitTableToBuilder(builder); return Compatible.INSTANCE.navigableSet(builder.build()); @@ -321,34 +284,13 @@ public abstract class CalciteSchema { * name. Never null. */ public final Collection<Function> getFunctions(String name, boolean caseSensitive) { final ImmutableList.Builder<Function> builder = ImmutableList.builder(); - - if (caseSensitive) { - // Add explicit functions, case-sensitive. - final Collection<FunctionEntry> functionEntries = functionMap.get(name); - if (functionEntries != null) { - for (FunctionEntry functionEntry : functionEntries) { - builder.add(functionEntry.getFunction()); - } - } - // Add implicit functions, case-sensitive. - final Collection<Function> functions = schema.getFunctions(name); - if (functions != null) { - builder.addAll(functions); - } - } else { - // Add explicit functions, case-insensitive. - for (String name2 : find(functionNames, name)) { - final Collection<FunctionEntry> functionEntries = - functionMap.get(name2); - if (functionEntries != null) { - for (FunctionEntry functionEntry : functionEntries) { - builder.add(functionEntry.getFunction()); - } - } - } - // Add implicit functions, case-insensitive. - addImplicitFunctionToBuilder(builder); + // Add explicit functions. + for (FunctionEntry functionEntry + : Pair.right(functionMap.range(name, caseSensitive))) { + builder.add(functionEntry.getFunction()); } + // Add implicit functions. + addImplicitFunctionsToBuilder(builder, caseSensitive); return builder.build(); } @@ -356,9 +298,9 @@ public abstract class CalciteSchema { * explicit, never null. */ public final NavigableSet<String> getFunctionNames() { final ImmutableSortedSet.Builder<String> builder = - new ImmutableSortedSet.Builder<>(COMPARATOR); + new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR); // Add explicit functions, case-sensitive. - builder.addAll(functionMap.keySet()); + builder.addAll(functionMap.map().keySet()); // Add implicit functions, case-sensitive. addImplicitFuncNamesToBuilder(builder); return Compatible.INSTANCE.navigableSet(builder.build()); @@ -368,13 +310,14 @@ public abstract class CalciteSchema { * that take zero parameters. */ public final NavigableMap<String, Table> getTablesBasedOnNullaryFunctions() { ImmutableSortedMap.Builder<String, Table> builder = - new ImmutableSortedMap.Builder<>(COMPARATOR); - for (Map.Entry<String, FunctionEntry> s : nullaryFunctionMap.entrySet()) { - final Function function = s.getValue().getFunction(); + new ImmutableSortedMap.Builder<>(NameSet.COMPARATOR); + for (Map.Entry<String, FunctionEntry> entry + : nullaryFunctionMap.map().entrySet()) { + final Function function = entry.getValue().getFunction(); if (function instanceof TableMacro) { assert function.getParameters().isEmpty(); final Table table = ((TableMacro) function).apply(ImmutableList.of()); - builder.put(s.getKey(), table); + builder.put(entry.getKey(), table); } } // add tables derived from implicit functions @@ -386,51 +329,30 @@ public abstract class CalciteSchema { * that take zero parameters. */ public final TableEntry getTableBasedOnNullaryFunction(String tableName, boolean caseSensitive) { - if (caseSensitive) { - final FunctionEntry functionEntry = nullaryFunctionMap.get(tableName); - if (functionEntry != null) { - final Function function = functionEntry.getFunction(); - if (function instanceof TableMacro) { - assert function.getParameters().isEmpty(); - final Table table = ((TableMacro) function).apply(ImmutableList.of()); - return tableEntry(tableName, table); - } - } - for (Function function : schema.getFunctions(tableName)) { - if (function instanceof TableMacro - && function.getParameters().isEmpty()) { - final Table table = ((TableMacro) function).apply(ImmutableList.of()); - return tableEntry(tableName, table); - } - } - } else { - for (Map.Entry<String, FunctionEntry> entry - : find(nullaryFunctionMap, tableName).entrySet()) { - final Function function = entry.getValue().getFunction(); - if (function instanceof TableMacro) { - assert function.getParameters().isEmpty(); - final Table table = ((TableMacro) function).apply(ImmutableList.of()); - return tableEntry(tableName, table); - } + for (Map.Entry<String, FunctionEntry> entry + : nullaryFunctionMap.range(tableName, caseSensitive).entrySet()) { + final Function function = entry.getValue().getFunction(); + if (function instanceof TableMacro) { + assert function.getParameters().isEmpty(); + final Table table = ((TableMacro) function).apply(ImmutableList.of()); + return tableEntry(tableName, table); } - TableEntry tableEntry = - getImplicitTableBasedOnNullaryFunction(tableName, false); } - return null; + return getImplicitTableBasedOnNullaryFunction(tableName, caseSensitive); } /** Returns a subset of a map whose keys match the given string * case-insensitively. */ protected static <V> NavigableMap<String, V> find(NavigableMap<String, V> map, String s) { - assert map.comparator() == COMPARATOR; + assert map.comparator() == NameSet.COMPARATOR; return map.subMap(s.toUpperCase(), true, s.toLowerCase(), true); } /** Returns a subset of a set whose values match the given string * case-insensitively. */ protected static Iterable<String> find(NavigableSet<String> set, String name) { - assert set.comparator() == COMPARATOR; + assert set.comparator() == NameSet.COMPARATOR; return set.subSet(name.toUpperCase(), true, name.toLowerCase(), true); } http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java b/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java index 40a8870..db97b14 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java @@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableSortedSet; * that maintains minimal state. */ class SimpleCalciteSchema extends CalciteSchema { - /** Creates a CachingCalciteSchema. + /** Creates a SimpleCalciteSchema. * * <p>Use {@link CalciteSchema#createRootSchema(boolean)} * or {@link #add(String, Schema)}. */ @@ -89,7 +89,8 @@ class SimpleCalciteSchema extends CalciteSchema { builder.addAll(schema.getTableNames()); } - protected void addImplicitFunctionToBuilder(ImmutableList.Builder<Function> builder) { + protected void addImplicitFunctionsToBuilder( + ImmutableList.Builder<Function> builder, boolean caseSensitive) { for (String functionName : schema.getFunctionNames()) { builder.addAll(schema.getFunctions(functionName)); } http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/util/NameMap.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/NameMap.java b/core/src/main/java/org/apache/calcite/util/NameMap.java new file mode 100644 index 0000000..a0c69bc --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/NameMap.java @@ -0,0 +1,81 @@ +/* + * 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.calcite.util; + +import com.google.common.collect.ImmutableSortedMap; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +import static org.apache.calcite.util.NameSet.COMPARATOR; + +/** Map whose keys are names and can be accessed with and without case + * sensitivity. + * + * @param <V> Value type */ +public class NameMap<V> { + private final NavigableMap<String, V> map; + + /** Creates a NameSet based on an existing set. */ + private NameMap(NavigableMap<String, V> map) { + this.map = map; + assert this.map.comparator() == COMPARATOR; + } + + /** Creates a NameMap, initially empty. */ + public NameMap() { + this(new TreeMap<String, V>(COMPARATOR)); + } + + /** Creates a NameMap that is an immutable copy of a given map. */ + public static <V> NameMap immutableCopyOf(Map<String, V> names) { + return new NameMap<>(ImmutableSortedMap.copyOf(names, COMPARATOR)); + } + + public void put(String name, V v) { + map.put(name, v); + } + + /** Returns a map containing all the entries in the map that match the given + * name. If case-sensitive, that map will have 0 or 1 elements; if + * case-insensitive, it may have 0 or more. */ + public NavigableMap<String, V> range(String name, boolean caseSensitive) { + if (caseSensitive) { + if (map.containsKey(name)) { + return ImmutableSortedMap.of(name, map.get(name)); + } else { + return ImmutableSortedMap.of(); + } + } else { + return map.subMap(name.toUpperCase(), true, name.toLowerCase(), true); + } + } + + /** Returns whether this map contains a given key, with a given + * case-sensitivity. */ + public boolean containsKey(String name, boolean caseSensitive) { + return !range(name, caseSensitive).isEmpty(); + } + + /** Returns the underlying map. */ + public NavigableMap<String, V> map() { + return map; + } +} + +// End NameMap.java http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/util/NameMultimap.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/NameMultimap.java b/core/src/main/java/org/apache/calcite/util/NameMultimap.java new file mode 100644 index 0000000..1b3734a --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/NameMultimap.java @@ -0,0 +1,101 @@ +/* + * 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.calcite.util; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +import static org.apache.calcite.util.NameSet.COMPARATOR; + +/** Multimap whose keys are names and can be accessed with and without case + * sensitivity. + * + * @param <V> Value type */ +public class NameMultimap<V> { + private final NavigableMap<String, List<V>> map; + + /** Creates a NameMultimap based on an existing map. */ + private NameMultimap(NavigableMap<String, List<V>> map) { + this.map = map; + assert this.map.comparator() == COMPARATOR; + } + + /** Creates a NameMultimap, initially empty. */ + public NameMultimap() { + this(new TreeMap<String, List<V>>(COMPARATOR)); + } + + /** Adds an entry to this multimap. */ + public void put(String name, V v) { + List<V> list = map.get(name); + if (list == null) { + list = new ArrayList<>(); + map.put(name, list); + } + list.add(v); + } + + /** Returns a map containing all the entries in this multimap that match the + * given name. */ + public Collection<Map.Entry<String, V>> range(String name, + boolean caseSensitive) { + if (caseSensitive) { + final List<V> list = map.get(name); + if (list != null && !list.isEmpty()) { + final ImmutableList.Builder<Map.Entry<String, V>> builder = + ImmutableList.builder(); + for (V v : list) { + builder.add(Pair.of(name, v)); + } + return builder.build(); + } else { + return ImmutableList.of(); + } + } else { + final ImmutableList.Builder<Map.Entry<String, V>> builder = + ImmutableList.builder(); + NavigableMap<String, List<V>> m = + map.subMap(name.toUpperCase(), true, name.toLowerCase(), true); + for (Map.Entry<String, List<V>> entry : m.entrySet()) { + for (V v : entry.getValue()) { + builder.add(Pair.of(entry.getKey(), v)); + } + } + return builder.build(); + } + } + + /** Returns whether this map contains a given key, with a given + * case-sensitivity. */ + public boolean containsKey(String name, boolean caseSensitive) { + return !range(name, caseSensitive).isEmpty(); + } + + /** Returns the underlying map. + * Its size is the number of keys, not the number of values. */ + public NavigableMap<String, List<V>> map() { + return map; + } +} + +// End NameMultimap.java http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/main/java/org/apache/calcite/util/NameSet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/NameSet.java b/core/src/main/java/org/apache/calcite/util/NameSet.java new file mode 100644 index 0000000..ccdd5d5 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/NameSet.java @@ -0,0 +1,104 @@ +/* + * 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.calcite.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.NavigableSet; +import java.util.Set; +import java.util.TreeSet; + +/** Set of names that can be accessed with and without case sensitivity. */ +public class NameSet { + /** Comparator that compares all strings differently, but if two strings are + * equal in case-insensitive match they are right next to each other. In a + * collection sorted on this comparator, we can find case-insensitive matches + * for a given string using a range scan between the upper-case string and + * the lower-case string. */ + public static final Comparator<String> COMPARATOR = + new Comparator<String>() { + public int compare(String o1, String o2) { + int c = o1.compareToIgnoreCase(o2); + if (c == 0) { + c = o1.compareTo(o2); + } + return c; + } + }; + + private final NavigableSet<String> names; + + /** Creates a NameSet based on an existing set. */ + private NameSet(NavigableSet<String> names) { + this.names = names; + assert names.comparator() == COMPARATOR; + } + + /** Creates a NameSet, initially empty. */ + public NameSet() { + this(new TreeSet<>(COMPARATOR)); + } + + /** Creates a NameSet that is an immutable copy of a given collection. */ + public static NameSet immutableCopyOf(Set<String> names) { + return new NameSet(ImmutableSortedSet.copyOf(NameSet.COMPARATOR, names)); + } + + public void add(String name) { + names.add(name); + } + + /** Returns an iterable over all the entries in the set that match the given + * name. If case-sensitive, that iterable will have 0 or 1 elements; if + * case-insensitive, it may have 0 or more. */ + public Collection<String> range(String name, boolean caseSensitive) { + if (caseSensitive) { + if (names.contains(name)) { + return ImmutableList.of(name); + } else { + return ImmutableList.of(); + } + } else { + return names.subSet(name.toUpperCase(), true, name.toLowerCase(), true); + } + } + + /** Returns whether this set contains the given name, with a given + * case-sensitivity. */ + public boolean contains(String name, boolean caseSensitive) { + if (names.contains(name)) { + return true; + } + if (!caseSensitive) { + final String s = names.ceiling(name.toLowerCase()); + return s != null + && s.equalsIgnoreCase(name); + } + return false; + } + + /** Returns the contents as an iterable. */ + public Iterable<String> iterable() { + return Collections.unmodifiableSet(names); + } +} + +// End NameSet.java http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/test/java/org/apache/calcite/test/JdbcTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java index b8bbe12..a3659c0 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -6113,6 +6113,21 @@ public class JdbcTest { .throws_("Table 'metaData.tAbles' not found"); } + /** Test case for + * <a href="https://issues.apache.org/jira/browse/CALCITE-1563">[CALCITE-1563] + * In case-insensitive connection, non-existent tables use alphabetically + * preceding table</a>. */ + @Test public void testLexCaseInsensitiveFindsNonexistentTable() { + final CalciteAssert.AssertThat with = + CalciteAssert.that().with(Lex.MYSQL); + // With [CALCITE-1563], the following query succeeded; it queried + // metadata.tables. + with.query("select COUNT(*) as c from `metaData`.`zoo`") + .throws_("Table 'metaData.zoo' not found"); + with.query("select COUNT(*) as c from `metaData`.`tAbLes`") + .returns("c=2\n"); + } + /** Tests case-insensitive resolution of sub-query columns. * * <p>Test case for http://git-wip-us.apache.org/repos/asf/calcite/blob/40e5b884/core/src/test/java/org/apache/calcite/util/UtilTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java index 96102be..d9c65d8 100644 --- a/core/src/test/java/org/apache/calcite/util/UtilTest.java +++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java @@ -1818,6 +1818,129 @@ public class UtilTest { return litmus.succeed(); } } + + /** Unit test for {@link org.apache.calcite.util.NameSet}. */ + @Test public void testNameSet() { + final NameSet names = new NameSet(); + assertThat(names.contains("foo", true), is(false)); + assertThat(names.contains("foo", false), is(false)); + names.add("baz"); + assertThat(names.contains("foo", true), is(false)); + assertThat(names.contains("foo", false), is(false)); + assertThat(names.contains("baz", true), is(true)); + assertThat(names.contains("baz", false), is(true)); + assertThat(names.contains("BAZ", true), is(false)); + assertThat(names.contains("BAZ", false), is(true)); + assertThat(names.contains("bAz", false), is(true)); + assertThat(names.range("baz", true).size(), is(1)); + assertThat(names.range("baz", false).size(), is(1)); + assertThat(names.range("BAZ", true).size(), is(0)); + assertThat(names.range("BaZ", true).size(), is(0)); + assertThat(names.range("BaZ", false).size(), is(1)); + assertThat(names.range("BAZ", false).size(), is(1)); + + assertThat(names.contains("bAzinga", false), is(false)); + assertThat(names.range("bAzinga", true).size(), is(0)); + assertThat(names.range("bAzinga", false).size(), is(0)); + + assertThat(names.contains("zoo", true), is(false)); + assertThat(names.contains("zoo", false), is(false)); + assertThat(names.range("zoo", true).size(), is(0)); + + assertThat(Iterables.size(names.iterable()), is(1)); + names.add("Baz"); + names.add("Abcde"); + names.add("Zymurgy"); + assertThat(Iterables.size(names.iterable()), is(4)); + assertThat(names.range("baz", false).size(), is(2)); + assertThat(names.range("baz", true).size(), is(1)); + assertThat(names.range("BAZ", true).size(), is(0)); + assertThat(names.range("Baz", true).size(), is(1)); + } + + /** Unit test for {@link org.apache.calcite.util.NameMap}. */ + @Test public void testNameMap() { + final NameMap<Integer> map = new NameMap<>(); + assertThat(map.containsKey("foo", true), is(false)); + assertThat(map.containsKey("foo", false), is(false)); + map.put("baz", 0); + assertThat(map.containsKey("foo", true), is(false)); + assertThat(map.containsKey("foo", false), is(false)); + assertThat(map.containsKey("baz", true), is(true)); + assertThat(map.containsKey("baz", false), is(true)); + assertThat(map.containsKey("BAZ", true), is(false)); + assertThat(map.containsKey("BAZ", false), is(true)); + assertThat(map.containsKey("bAz", false), is(true)); + assertThat(map.range("baz", true).size(), is(1)); + assertThat(map.range("baz", false).size(), is(1)); + assertThat(map.range("BAZ", true).size(), is(0)); + assertThat(map.range("BaZ", true).size(), is(0)); + assertThat(map.range("BaZ", false).size(), is(1)); + assertThat(map.range("BAZ", false).size(), is(1)); + + assertThat(map.containsKey("bAzinga", false), is(false)); + assertThat(map.range("bAzinga", true).size(), is(0)); + assertThat(map.range("bAzinga", false).size(), is(0)); + + assertThat(map.containsKey("zoo", true), is(false)); + assertThat(map.containsKey("zoo", false), is(false)); + assertThat(map.range("zoo", true).size(), is(0)); + + assertThat(map.map().size(), is(1)); + map.put("Baz", 1); + map.put("Abcde", 2); + map.put("Zymurgy", 3); + assertThat(map.map().size(), is(4)); + assertThat(map.map().entrySet().size(), is(4)); + assertThat(map.map().keySet().size(), is(4)); + assertThat(map.range("baz", false).size(), is(2)); + assertThat(map.range("baz", true).size(), is(1)); + assertThat(map.range("BAZ", true).size(), is(0)); + assertThat(map.range("Baz", true).size(), is(1)); + } + + /** Unit test for {@link org.apache.calcite.util.NameMultimap}. */ + @Test public void testNameMultimap() { + final NameMultimap<Integer> map = new NameMultimap<>(); + assertThat(map.containsKey("foo", true), is(false)); + assertThat(map.containsKey("foo", false), is(false)); + map.put("baz", 0); + map.put("baz", 0); + map.put("BAz", 0); + assertThat(map.containsKey("foo", true), is(false)); + assertThat(map.containsKey("foo", false), is(false)); + assertThat(map.containsKey("baz", true), is(true)); + assertThat(map.containsKey("baz", false), is(true)); + assertThat(map.containsKey("BAZ", true), is(false)); + assertThat(map.containsKey("BAZ", false), is(true)); + assertThat(map.containsKey("bAz", false), is(true)); + assertThat(map.range("baz", true).size(), is(2)); + assertThat(map.range("baz", false).size(), is(3)); + assertThat(map.range("BAZ", true).size(), is(0)); + assertThat(map.range("BaZ", true).size(), is(0)); + assertThat(map.range("BaZ", false).size(), is(3)); + assertThat(map.range("BAZ", false).size(), is(3)); + + assertThat(map.containsKey("bAzinga", false), is(false)); + assertThat(map.range("bAzinga", true).size(), is(0)); + assertThat(map.range("bAzinga", false).size(), is(0)); + + assertThat(map.containsKey("zoo", true), is(false)); + assertThat(map.containsKey("zoo", false), is(false)); + assertThat(map.range("zoo", true).size(), is(0)); + + assertThat(map.map().size(), is(2)); + map.put("Baz", 1); + map.put("Abcde", 2); + map.put("Zymurgy", 3); + assertThat(map.map().size(), is(5)); + assertThat(map.map().entrySet().size(), is(5)); + assertThat(map.map().keySet().size(), is(5)); + assertThat(map.range("baz", false).size(), is(4)); + assertThat(map.range("baz", true).size(), is(2)); + assertThat(map.range("BAZ", true).size(), is(0)); + assertThat(map.range("Baz", true).size(), is(1)); + } } // End UtilTest.java
