From 1dca6507f715d7b0f3b4193f46bf64f2445391ac Mon Sep 17 00:00:00 2001 From: Ulrich Kramer Date: Fri, 28 Jun 2024 07:57:47 +0200 Subject: [PATCH] EXPB-2299 Speed up table lookup for huge schemas --- build.sh | 6 + .../adapter/cassandra/CassandraSchema.java | 3 +- .../calcite/adapter/clone/CloneSchema.java | 7 +- .../calcite/adapter/jdbc/JdbcSchema.java | 212 ++++++++++-------- .../calcite/jdbc/CachingCalciteSchema.java | 39 +--- .../apache/calcite/jdbc/CalciteMetaImpl.java | 72 +----- .../apache/calcite/jdbc/CalciteSchema.java | 58 +++-- .../calcite/jdbc/SimpleCalciteSchema.java | 17 +- .../apache/calcite/model/ModelHandler.java | 13 +- .../calcite/prepare/CalciteCatalogReader.java | 3 +- .../calcite/prepare/RelOptTableImpl.java | 9 +- .../apache/calcite/schema/LikePattern.java | 90 ++++++++ .../org/apache/calcite/schema/Lookup.java | 65 ++++++ .../java/org/apache/calcite/schema/Named.java | 41 ++++ .../org/apache/calcite/schema/Schema.java | 20 +- .../org/apache/calcite/schema/Schemas.java | 4 +- .../calcite/schema/impl/AbstractSchema.java | 21 +- .../calcite/schema/impl/CachingLookup.java | 120 ++++++++++ .../calcite/schema/impl/DelegatingSchema.java | 14 +- .../schema/impl/EntryNotFoundException.java | 24 ++ .../calcite/schema/impl/IgnoreCaseLookup.java | 86 +++++++ .../schema/impl/SimpleTableLookup.java | 55 +++++ .../org/apache/calcite/test/JdbcTest.java | 7 +- .../calcite/server/ServerDdlExecutor.java | 10 +- .../apache/calcite/test/CalciteAssert.java | 6 +- 25 files changed, 730 insertions(+), 272 deletions(-) create mode 100644 core/src/main/java/org/apache/calcite/schema/LikePattern.java create mode 100644 core/src/main/java/org/apache/calcite/schema/Lookup.java create mode 100644 core/src/main/java/org/apache/calcite/schema/Named.java create mode 100644 core/src/main/java/org/apache/calcite/schema/impl/CachingLookup.java create mode 100644 core/src/main/java/org/apache/calcite/schema/impl/EntryNotFoundException.java create mode 100644 core/src/main/java/org/apache/calcite/schema/impl/IgnoreCaseLookup.java create mode 100644 core/src/main/java/org/apache/calcite/schema/impl/SimpleTableLookup.java diff --git a/build.sh b/build.sh index 6e107abf2a5..674b89f3628 100755 --- a/build.sh +++ b/build.sh @@ -18,6 +18,12 @@ set -e VERSION=1.37.0 +# Java 21 doesn't suppport Java 8 +if [ -d /Library/Java/JavaVirtualMachines/sapmachine-jdk-17.0.11.jdk/Contents/Home ]; then + export JAVA_HOME=/Library/Java/JavaVirtualMachines/sapmachine-jdk-17.0.11.jdk/Contents/Home + export PATH=$JAVA_HOME/bin:$PATH +fi + ./gradlew clean publishToMavenLocal for module in core linq4j; diff --git a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java index 31e22b0e3ed..01085bbc41c 100644 --- a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java +++ b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java @@ -25,6 +25,7 @@ import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.runtime.Hook; +import org.apache.calcite.schema.LikePattern; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.impl.AbstractSchema; @@ -324,7 +325,7 @@ private void addMaterializedViews() { query = buf.toString(); // Add the view for this query - String viewName = "$" + getTableNames().size(); + String viewName = "$" + tables().getNames(LikePattern.any()).size(); SchemaPlus schema = parentSchema.getSubSchema(name); if (schema == null) { throw new IllegalStateException("Cannot find schema " + name diff --git a/core/src/main/java/org/apache/calcite/adapter/clone/CloneSchema.java b/core/src/main/java/org/apache/calcite/adapter/clone/CloneSchema.java index cdada3cfe04..a372cbe9965 100644 --- a/core/src/main/java/org/apache/calcite/adapter/clone/CloneSchema.java +++ b/core/src/main/java/org/apache/calcite/adapter/clone/CloneSchema.java @@ -26,6 +26,8 @@ import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.type.RelProtoDataType; +import org.apache.calcite.schema.LikePattern; +import org.apache.calcite.schema.Lookup; import org.apache.calcite.schema.QueryableTable; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaFactory; @@ -68,8 +70,9 @@ public CloneSchema(SchemaPlus sourceSchema) { @Override protected Map getTableMap() { final Map map = new LinkedHashMap<>(); - for (String name : sourceSchema.getTableNames()) { - final Table table = sourceSchema.getTable(name); + final Lookup tables = sourceSchema.tables(); + for (String name : tables.getNames(LikePattern.any())) { + final Table table = tables.get(name); if (table instanceof QueryableTable) { final QueryableTable sourceTable = (QueryableTable) table; map.put(name, diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java index 19fcfa8e280..231d4c318be 100644 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java +++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java @@ -16,6 +16,12 @@ */ package org.apache.calcite.adapter.jdbc; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import com.google.common.util.concurrent.UncheckedExecutionException; + import org.apache.calcite.avatica.AvaticaUtils; import org.apache.calcite.avatica.MetaImpl; import org.apache.calcite.avatica.SqlType; @@ -26,14 +32,9 @@ import org.apache.calcite.rel.type.RelDataTypeImpl; import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.RelProtoDataType; -import org.apache.calcite.schema.Function; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaFactory; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.SchemaVersion; -import org.apache.calcite.schema.Schemas; -import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.Wrapper; +import org.apache.calcite.schema.*; +import org.apache.calcite.schema.impl.CachingLookup; +import org.apache.calcite.schema.impl.IgnoreCaseLookup; import org.apache.calcite.sql.SqlDialect; import org.apache.calcite.sql.SqlDialectFactory; import org.apache.calcite.sql.SqlDialectFactoryImpl; @@ -63,8 +64,17 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import javax.sql.DataSource; import static java.util.Objects.requireNonNull; @@ -78,14 +88,29 @@ */ public class JdbcSchema implements Schema, Wrapper { private static final Logger LOGGER = LoggerFactory.getLogger(JdbcSchema.class); + public class TableNotFoundException extends RuntimeException { + } final DataSource dataSource; final @Nullable String catalog; final @Nullable String schema; public final SqlDialect dialect; final JdbcConvention convention; - private @Nullable ImmutableMap tableMap; - private final boolean snapshot; + private final Lookup
tables = new CachingLookup
(new IgnoreCaseLookup
() { + @Override + public @Nullable Table get(String name) { + try (Stream s = getMetaTableStream(name)) { + return s.findFirst().map(it -> jdbcTableMapper(it) ).orElse(null); + } + } + + @Override + public Set getNames(LikePattern pattern) { + try( Stream s = getMetaTableStream(pattern.pattern)) { + return s.map(it -> it.tableName).collect(Collectors.toSet()); + } + } + }); @Experimental public static final ThreadLocal<@Nullable Foo> THREAD_METADATA = new ThreadLocal<>(); @@ -104,19 +129,11 @@ public class JdbcSchema implements Schema, Wrapper { */ public JdbcSchema(DataSource dataSource, SqlDialect dialect, JdbcConvention convention, @Nullable String catalog, @Nullable String schema) { - this(dataSource, dialect, convention, catalog, schema, null); - } - - private JdbcSchema(DataSource dataSource, SqlDialect dialect, - JdbcConvention convention, @Nullable String catalog, @Nullable String schema, - @Nullable ImmutableMap tableMap) { this.dataSource = requireNonNull(dataSource, "dataSource"); this.dialect = requireNonNull(dialect, "dialect"); this.convention = convention; this.catalog = catalog; this.schema = schema; - this.tableMap = tableMap; - this.snapshot = tableMap != null; } public static JdbcSchema create( @@ -217,13 +234,16 @@ public static DataSource dataSource(String url, @Nullable String driverClassName password); } + @Override public Lookup
tables() { + return tables; + } + @Override public boolean isMutable() { return false; } @Override public Schema snapshot(SchemaVersion version) { - return new JdbcSchema(dataSource, dialect, convention, catalog, schema, - tableMap); + return this; } // Used by generated code. @@ -249,49 +269,56 @@ protected Multimap getFunctions() { return getFunctions().keySet(); } - private ImmutableMap computeTables() { + private Stream getMetaTableStream(String tableNamePattern) { + final Pair<@Nullable String, @Nullable String> catalogSchema = getCatalogSchema(); + final Stream tableDefs; Connection connection = null; ResultSet resultSet = null; try { connection = dataSource.getConnection(); - final Pair<@Nullable String, @Nullable String> catalogSchema = getCatalogSchema(connection); - final String catalog = catalogSchema.left; - final String schema = catalogSchema.right; - final Iterable tableDefs; - Foo threadMetadata = THREAD_METADATA.get(); - if (threadMetadata != null) { - tableDefs = threadMetadata.apply(catalog, schema); - } else { - final List tableDefList = new ArrayList<>(); - final DatabaseMetaData metaData = connection.getMetaData(); - resultSet = metaData.getTables(catalog, schema, null, null); - while (resultSet.next()) { - final String catalogName = intern(resultSet.getString(1)); - final String schemaName = intern(resultSet.getString(2)); - final String tableName = intern(resultSet.getString(3)); - final String tableTypeName = intern(resultSet.getString(4)); - tableDefList.add( - new MetaImpl.MetaTable(catalogName, schemaName, tableName, - tableTypeName)); - } - tableDefs = tableDefList; - } - - final ImmutableMap.Builder builder = - ImmutableMap.builder(); - for (MetaImpl.MetaTable tableDef : tableDefs) { - final JdbcTable table = - new JdbcTable(this, tableDef.tableCat, tableDef.tableSchem, - tableDef.tableName, getTableType(tableDef.tableType)); - builder.put(tableDef.tableName, table); - } - return builder.build(); + final List tableDefList = new ArrayList<>(); + final DatabaseMetaData metaData = connection.getMetaData(); + resultSet = metaData.getTables(catalogSchema.left, catalogSchema.right, tableNamePattern, null); + tableDefs = asStream(connection, resultSet) + .map(JdbcSchema::metaDataMapper); } catch (SQLException e) { + close(connection, null, resultSet); throw new RuntimeException( "Exception while reading tables", e); - } finally { - close(connection, null, resultSet); } + return tableDefs; + } + + private static Stream asStream(Connection connection, ResultSet resultSet) { + return StreamSupport.stream(new Spliterators.AbstractSpliterator( + Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(Consumer action) { + try { + if(!resultSet.next()) return false; + action.accept(resultSet); + return true; + } catch(SQLException ex) { + throw new RuntimeException(ex); + } + } + }, false).onClose(() -> { + close(connection, null, resultSet); + }) ; + } + + private JdbcTable jdbcTableMapper(MetaImpl.MetaTable tableDef) { + return new JdbcTable(this, tableDef.tableCat, tableDef.tableSchem, + tableDef.tableName, getTableType(tableDef.tableType)); + } + + private static MetaImpl.MetaTable metaDataMapper(ResultSet resultSet){ + try { + return new MetaImpl.MetaTable(intern(resultSet.getString(1)), intern(resultSet.getString(2)), intern(resultSet.getString(3)), + intern(resultSet.getString(4))); + } catch (SQLException e) { + throw new RuntimeException(e); + } } private static String intern(@Nullable String string) { @@ -329,49 +356,44 @@ private static List version(DatabaseMetaData metaData) throws SQLExcept } /** Returns a pair of (catalog, schema) for the current connection. */ - private Pair<@Nullable String, @Nullable String> getCatalogSchema(Connection connection) - throws SQLException { - final DatabaseMetaData metaData = connection.getMetaData(); - final List version41 = ImmutableList.of(4, 1); // JDBC 4.1 - String catalog = this.catalog; - String schema = this.schema; - final boolean jdbc41OrAbove = - VERSION_ORDERING.compare(version(metaData), version41) >= 0; - if (catalog == null && jdbc41OrAbove) { - // From JDBC 4.1, catalog and schema can be retrieved from the connection - // object, hence try to get it from there if it was not specified by user - catalog = connection.getCatalog(); - } - if (schema == null && jdbc41OrAbove) { - schema = connection.getSchema(); - if ("".equals(schema)) { - schema = null; // PostgreSQL returns useless "" sometimes + private Pair<@Nullable String, @Nullable String> getCatalogSchema() { + try(Connection connection = dataSource.getConnection()) { + final DatabaseMetaData metaData = connection.getMetaData(); + final List version41 = ImmutableList.of(4, 1); // JDBC 4.1 + String catalog = this.catalog; + String schema = this.schema; + final boolean jdbc41OrAbove = + VERSION_ORDERING.compare(version(metaData), version41) >= 0; + if (catalog == null && jdbc41OrAbove) { + // From JDBC 4.1, catalog and schema can be retrieved from the connection + // object, hence try to get it from there if it was not specified by user + catalog = connection.getCatalog(); } - } - if ((catalog == null || schema == null) - && metaData.getDatabaseProductName().equals("PostgreSQL")) { - final String sql = "select current_database(), current_schema()"; - try (Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(sql)) { - if (resultSet.next()) { - catalog = resultSet.getString(1); - schema = resultSet.getString(2); + if (schema == null && jdbc41OrAbove) { + schema = connection.getSchema(); + if ("".equals(schema)) { + schema = null; // PostgreSQL returns useless "" sometimes + } + } + if ((catalog == null || schema == null) + && metaData.getDatabaseProductName().equals("PostgreSQL")) { + final String sql = "select current_database(), current_schema()"; + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sql)) { + if (resultSet.next()) { + catalog = resultSet.getString(1); + schema = resultSet.getString(2); + } } } + return Pair.of(catalog, schema); + } catch (SQLException e) { + throw new RuntimeException(e); } - return Pair.of(catalog, schema); - } - - @Override public @Nullable Table getTable(String name) { - return getTableMap(false).get(name); } - private synchronized ImmutableMap getTableMap( - boolean force) { - if (force || tableMap == null) { - tableMap = computeTables(); - } - return tableMap; + @Deprecated @Override public @Nullable Table getTable(String name) { + return tables.get(name); } RelProtoDataType getRelDataType(String catalogName, String schemaName, @@ -494,10 +516,8 @@ private static RelDataType parseTypeString(RelDataTypeFactory typeFactory, } } - @Override public Set getTableNames() { - // This method is called during a cache refresh. We can take it as a signal - // that we need to re-build our own cache. - return getTableMap(!snapshot).keySet(); + @Deprecated @Override public Set getTableNames() { + return tables.getNames(LikePattern.any()); } protected Map getTypes() { 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 19b204aafa0..551eec69baa 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java @@ -18,6 +18,8 @@ import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.schema.Function; +import org.apache.calcite.schema.Lookup; +import org.apache.calcite.schema.Named; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaVersion; import org.apache.calcite.schema.Table; @@ -47,7 +49,6 @@ */ class CachingCalciteSchema extends CalciteSchema { private final Cached implicitSubSchemaCache; - private final Cached implicitTableCache; private final Cached implicitFunctionCache; private final Cached implicitTypeCache; @@ -79,13 +80,6 @@ private CachingCalciteSchema(@Nullable CalciteSchema parent, Schema schema, CachingCalciteSchema.this.schema.getSubSchemaNames()); } }; - this.implicitTableCache = - new AbstractCached() { - @Override public NameSet build() { - return NameSet.immutableCopyOf( - CachingCalciteSchema.this.schema.getTableNames()); - } - }; this.implicitFunctionCache = new AbstractCached() { @Override public NameSet build() { @@ -108,7 +102,6 @@ private CachingCalciteSchema(@Nullable CalciteSchema parent, Schema schema, } final long now = System.currentTimeMillis(); implicitSubSchemaCache.enable(now, cache); - implicitTableCache.enable(now, cache); implicitFunctionCache.enable(now, cache); this.cache = cache; } @@ -138,14 +131,9 @@ private CachingCalciteSchema(@Nullable CalciteSchema parent, Schema schema, @Override protected @Nullable TableEntry getImplicitTable(String tableName, boolean caseSensitive) { - 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); - } + final Named
entry = Lookup.get(schema.tables(),tableName, caseSensitive); + if (entry != null) { + return tableEntry(entry.name(),entry.table()); } return null; } @@ -179,14 +167,6 @@ private CachingCalciteSchema(@Nullable CalciteSchema parent, Schema schema, } } - @Override protected void addImplicitTableToBuilder( - ImmutableSortedSet.Builder builder) { - // Add implicit tables, case-sensitive. - final long now = System.currentTimeMillis(); - final NameSet set = implicitTableCache.get(now); - builder.addAll(set.iterable()); - } - @Override protected void addImplicitFunctionsToBuilder( ImmutableList.Builder builder, String name, boolean caseSensitive) { @@ -268,15 +248,6 @@ private CachingCalciteSchema(@Nullable CalciteSchema parent, Schema schema, return snapshot; } - @Override public boolean removeTable(String name) { - if (cache) { - final long now = System.nanoTime(); - implicitTableCache.enable(now, false); - implicitTableCache.enable(now, true); - } - return super.removeTable(name); - } - @Override public boolean removeFunction(String name) { if (cache) { final long now = System.nanoTime(); diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java index 82be2738778..72e9ea6d37d 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java @@ -42,6 +42,7 @@ import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.runtime.FlatLists; import org.apache.calcite.runtime.Hook; +import org.apache.calcite.schema.LikePattern; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Table; @@ -79,7 +80,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.regex.Pattern; import static java.util.Objects.requireNonNull; @@ -227,52 +227,12 @@ public static CalciteMetaImpl create(CalciteConnection connection, } static Predicate1 namedMatcher(final Pat pattern) { - if (pattern.s == null || pattern.s.equals("%")) { - return Functions.truePredicate1(); - } - final Pattern regex = likeToRegex(pattern); - return v1 -> regex.matcher(v1.getName()).matches(); + final Predicate1 predicate = LikePattern.matcher(pattern.s); + return v1 -> predicate.apply(v1.getName()); } static Predicate1 matcher(final Pat pattern) { - if (pattern.s == null || pattern.s.equals("%")) { - return Functions.truePredicate1(); - } - final Pattern regex = likeToRegex(pattern); - return v1 -> regex.matcher(v1).matches(); - } - - /** Converts a LIKE-style pattern (where '%' represents a wild-card, escaped - * using '\') to a Java regex. */ - public static Pattern likeToRegex(Pat pattern) { - StringBuilder buf = new StringBuilder("^"); - char[] charArray = pattern.s.toCharArray(); - int slash = -2; - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (slash == i - 1) { - buf.append('[').append(c).append(']'); - } else { - switch (c) { - case '\\': - slash = i; - break; - case '%': - buf.append(".*"); - break; - case '[': - buf.append("\\["); - break; - case ']': - buf.append("\\]"); - break; - default: - buf.append('[').append(c).append(']'); - } - } - } - buf.append("$"); - return Pattern.compile(buf.toString()); + return LikePattern.matcher(pattern.s); } @Override public StatementHandle createStatement(ConnectionHandle ch) { @@ -397,7 +357,7 @@ private static ImmutableMap.Builder addProperty( final Predicate1 schemaMatcher = namedMatcher(schemaPattern); Enumerable tables = schemas(catalog) .where(schemaMatcher) - .selectMany(schema -> tables(schema, matcher(tableNamePattern))) + .selectMany(schema -> tables(schema, new LikePattern(tableNamePattern.s))) .where(typeFilter); return createResultSet(tables, metaTableFactory.getMetaTableClass(), @@ -414,13 +374,12 @@ private static ImmutableMap.Builder addProperty( Pat schemaPattern, Pat tableNamePattern, Pat columnNamePattern) { - final Predicate1 tableNameMatcher = matcher(tableNamePattern); final Predicate1 schemaMatcher = namedMatcher(schemaPattern); final Predicate1 columnMatcher = namedMatcher(columnNamePattern); return createResultSet(schemas(catalog) .where(schemaMatcher) - .selectMany(schema -> tables(schema, tableNameMatcher)) + .selectMany(schema -> tables(schema, new LikePattern(tableNamePattern.s))) .selectMany(this::columns) .where(columnMatcher), metaColumnFactory.getMetaColumnClass(), @@ -458,18 +417,14 @@ Enumerable schemas(final String catalog) { Enumerable tables(String catalog) { return schemas(catalog) .selectMany(schema -> - tables(schema, Functions.truePredicate1())); + tables(schema, LikePattern.any())); } - Enumerable tables(final MetaSchema schema_) { + Enumerable tables(final MetaSchema schema_, LikePattern tableNamePattern) { final CalciteMetaSchema schema = (CalciteMetaSchema) schema_; - return Linq4j.asEnumerable(schema.calciteSchema.getTableNames()) + return Linq4j.asEnumerable(schema.calciteSchema.getTableNames(tableNamePattern)) .select((Function1) name -> { - final Table table = - requireNonNull(schema.calciteSchema.getTable(name, true), - () -> "table " + name + " is not found (case sensitive)") - .getTable(); - return metaTableFactory.createTable(table, schema.tableCatalog, + return metaTableFactory.createTable(schema.calciteSchema.getTable(name,true).getTable(), schema.tableCatalog, schema.tableSchem, name); }) .concat( @@ -485,13 +440,6 @@ Enumerable tables(final MetaSchema schema_) { })); } - Enumerable tables( - final MetaSchema schema, - final Predicate1 matcher) { - return tables(schema) - .where(v1 -> matcher.apply(v1.getName())); - } - private ImmutableList getAllDefaultType() { final ImmutableList.Builder allTypeList = ImmutableList.builder(); 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 418b9e1a600..595c9029e67 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java @@ -17,16 +17,11 @@ package org.apache.calcite.jdbc; import org.apache.calcite.linq4j.function.Experimental; +import org.apache.calcite.linq4j.function.Predicate1; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.materialize.Lattice; import org.apache.calcite.rel.type.RelProtoDataType; -import org.apache.calcite.schema.Function; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.SchemaVersion; -import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.TableMacro; -import org.apache.calcite.schema.Wrapper; +import org.apache.calcite.schema.*; import org.apache.calcite.schema.impl.MaterializedViewTable; import org.apache.calcite.schema.impl.StarTable; import org.apache.calcite.util.NameMap; @@ -47,6 +42,8 @@ import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkArgument; @@ -137,10 +134,6 @@ protected CalciteSchema(@Nullable CalciteSchema parent, Schema schema, protected abstract void addImplicitSubSchemaToBuilder( ImmutableSortedMap.Builder builder); - /** Adds implicit tables to a builder. */ - protected abstract void addImplicitTableToBuilder( - ImmutableSortedSet.Builder builder); - /** Adds implicit functions to a builder. */ protected abstract void addImplicitFunctionsToBuilder( ImmutableList.Builder builder, @@ -335,14 +328,14 @@ public NavigableMap getLatticeMap() { /** Returns the set of all table names. Includes implicit and explicit tables * and functions with zero parameters. */ - public final NavigableSet getTableNames() { - final ImmutableSortedSet.Builder builder = - new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR); - // Add explicit tables, case-sensitive. - builder.addAll(tableMap.map().keySet()); - // Add implicit tables, case-sensitive. - addImplicitTableToBuilder(builder); - return builder.build(); + public final Set getTableNames(LikePattern pattern) { + Predicate1 predicate = pattern.matcher(); + return Stream.concat( + tableMap.map().keySet() + .stream() + .filter(entry -> predicate.apply(entry)), + schema.tables().getNames(pattern).stream()) + .collect(Collectors.toSet()); } /** Returns the set of all types names. */ @@ -649,13 +642,34 @@ CalciteSchema calciteSchema() { return schema.getExpression(parentSchema, name); } - @Override public @Nullable Table getTable(String name) { + @Override public Lookup
tables() { + return new Lookup
() { + @Override + public @Nullable Table get(String name) { + final TableEntry entry = CalciteSchema.this.getTable(name,true); + return entry == null ? null : entry.getTable(); + } + + @Override + public @Nullable Named
getIgnoreCase(String name) { + final TableEntry entry = CalciteSchema.this.getTable(name,false); + return entry == null ? null : new Named<>(entry.name, entry.getTable()); + } + + @Override + public Set getNames(LikePattern pattern) { + return CalciteSchema.this.getTableNames(pattern); + } + }; + } + + @Deprecated @Override public @Nullable Table getTable(String name) { final TableEntry entry = CalciteSchema.this.getTable(name, true); return entry == null ? null : entry.getTable(); } - @Override public NavigableSet getTableNames() { - return CalciteSchema.this.getTableNames(); + @Deprecated @Override public Set getTableNames() { + return CalciteSchema.this.getTableNames(LikePattern.any()); } @Override public @Nullable RelProtoDataType getType(String name) { 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 edb1d2afde6..e12f3463339 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java +++ b/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java @@ -18,6 +18,8 @@ import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.schema.Function; +import org.apache.calcite.schema.Lookup; +import org.apache.calcite.schema.Named; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaVersion; import org.apache.calcite.schema.Table; @@ -118,18 +120,11 @@ private SimpleCalciteSchema(@Nullable CalciteSchema parent, @Override protected @Nullable TableEntry getImplicitTable(String tableName, boolean caseSensitive) { - // Check implicit tables. - final String tableName2 = - caseSensitive ? tableName - : caseInsensitiveLookup(schema.getTableNames(), tableName); - if (tableName2 == null) { - return null; - } - final Table table = schema.getTable(tableName2); + final Named
table = Lookup.get(schema.tables(),tableName, caseSensitive); if (table == null) { return null; } - return tableEntry(tableName2, table); + return tableEntry(table.name(), table.table()); } @Override protected @Nullable TypeEntry getImplicitType(String name, boolean caseSensitive) { @@ -163,10 +158,6 @@ private SimpleCalciteSchema(@Nullable CalciteSchema parent, } } - @Override protected void addImplicitTableToBuilder(ImmutableSortedSet.Builder builder) { - builder.addAll(schema.getTableNames()); - } - @Override protected void addImplicitFunctionsToBuilder( ImmutableList.Builder builder, String name, boolean caseSensitive) { diff --git a/core/src/main/java/org/apache/calcite/model/ModelHandler.java b/core/src/main/java/org/apache/calcite/model/ModelHandler.java index 128869afe8e..444e0a0f962 100644 --- a/core/src/main/java/org/apache/calcite/model/ModelHandler.java +++ b/core/src/main/java/org/apache/calcite/model/ModelHandler.java @@ -23,16 +23,7 @@ import org.apache.calcite.materialize.Lattice; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.schema.AggregateFunction; -import org.apache.calcite.schema.Function; -import org.apache.calcite.schema.ScalarFunction; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaFactory; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.TableFactory; -import org.apache.calcite.schema.TableFunction; -import org.apache.calcite.schema.TableMacro; +import org.apache.calcite.schema.*; import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.schema.impl.AggregateFunctionImpl; import org.apache.calcite.schema.impl.MaterializedViewTable; @@ -360,7 +351,7 @@ public void visit(JsonMaterialization jsonMaterialization) { if (jsonMaterialization.view == null) { // If the user did not supply a view name, that means the materialized // view is pre-populated. Generate a synthetic view name. - viewName = "$" + schema.getTableNames().size(); + viewName = "$" + schema.tables().getNames(LikePattern.any()).size(); existing = true; } else { viewName = jsonMaterialization.view; diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java index 1e4896a2dbf..7f2ac7566b9 100644 --- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java +++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java @@ -27,6 +27,7 @@ import org.apache.calcite.rel.type.RelDataTypeFactoryImpl; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.schema.AggregateFunction; +import org.apache.calcite.schema.LikePattern; import org.apache.calcite.schema.ScalarFunction; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.TableFunction; @@ -203,7 +204,7 @@ private Collection getFunctionsFrom( result.add(moniker(schema, subSchema, SqlMonikerType.SCHEMA)); } - for (String table : schema.getTableNames()) { + for (String table : schema.getTableNames(LikePattern.any())) { result.add(moniker(schema, table, SqlMonikerType.TABLE)); } diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java index 29a6f07c014..bdea7a44362 100644 --- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java +++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java @@ -35,6 +35,7 @@ import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.schema.ColumnStrategy; +import org.apache.calcite.schema.Lookup; import org.apache.calcite.schema.ModifiableTable; import org.apache.calcite.schema.Path; import org.apache.calcite.schema.ScannableTable; @@ -505,11 +506,15 @@ public static MySchemaPlus create(Path path) { return false; } - @Override public @Nullable Table getTable(String name) { + @Override public @Nullable Lookup
tables() { + return schema.tables(); + } + + @Deprecated @Override public @Nullable Table getTable(String name) { return schema.getTable(name); } - @Override public Set getTableNames() { + @Deprecated @Override public Set getTableNames() { return schema.getTableNames(); } diff --git a/core/src/main/java/org/apache/calcite/schema/LikePattern.java b/core/src/main/java/org/apache/calcite/schema/LikePattern.java new file mode 100644 index 00000000000..67564401b70 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/LikePattern.java @@ -0,0 +1,90 @@ +/* + * 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.schema; + +import org.apache.calcite.linq4j.function.Predicate1; + +import java.util.regex.Pattern; + +public class LikePattern { + private static final String ANY = "%"; + public final String pattern; + + public LikePattern(String pattern) { + if (pattern == null) { + pattern = ANY; + } + this.pattern = pattern; + } + + public String toString() { + return "LikePattern[" + this.pattern + "]"; + } + + public Predicate1 matcher() { + return matcher(pattern); + } + + public static LikePattern any() { + return new LikePattern(ANY); + } + + public static Predicate1 matcher(String likePattern) { + if (likePattern == null || likePattern.equals(ANY)) { + return v1 -> true; + } + final Pattern regex = likeToRegex(likePattern); + return v1 -> regex.matcher(v1).matches(); + } + + /** + * Converts a LIKE-style pattern (where '%' represents a wild-card, escaped + * using '\') to a Java regex. + */ + public static Pattern likeToRegex(String pattern) { + StringBuilder buf = new StringBuilder("^"); + char[] charArray = pattern.toCharArray(); + int slash = -2; + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (slash == i - 1) { + buf.append('[').append(c).append(']'); + } else { + switch (c) { + case '\\': + slash = i; + break; + case '%': + buf.append(".*"); + break; + case '[': + buf.append("\\["); + break; + case ']': + buf.append("\\]"); + break; + default: + buf.append('[').append(c).append(']'); + } + } + } + buf.append("$"); + return Pattern.compile(buf.toString()); + } + +} diff --git a/core/src/main/java/org/apache/calcite/schema/Lookup.java b/core/src/main/java/org/apache/calcite/schema/Lookup.java new file mode 100644 index 00000000000..c907904efd7 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/Lookup.java @@ -0,0 +1,65 @@ +/* + * 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.schema; + +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.rel.type.RelProtoDataType; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; +import java.util.Set; + +/** + * A case sensitive/insensitive lookup for tables or functions + * + */ +public interface Lookup { + /** + * Returns a named entity with a given name, or null if not found. + * + * @param name Name + * @return Entity, or null + */ + @Nullable T get(String name) ; + + /** + * Returns a named entity with a given name ignoring the case, or null if not found. + * + * @param name Name + * @return Entity, or null + */ + @Nullable Named getIgnoreCase(String name) ; + + /** + * Returns the names of the entities in matching pattern. + * + * @return Names of the entities + */ + Set getNames(LikePattern pattern); + + static Named get(Lookup lookup, String name, boolean caseSensitive) { + if ( caseSensitive) { + T entry = lookup.get(name); + if ( entry == null ) { + return null; + } + return new Named(name,entry); + } + return lookup.getIgnoreCase(name); + } +} diff --git a/core/src/main/java/org/apache/calcite/schema/Named.java b/core/src/main/java/org/apache/calcite/schema/Named.java new file mode 100644 index 00000000000..31e1fc585d9 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/Named.java @@ -0,0 +1,41 @@ +/* + * 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.schema; + +import java.security.InvalidParameterException; + +public class Named { + private final String name; + private final T table; + + public Named(String name, T table) { + this.name = name; + if ( table == null) { + throw new InvalidParameterException("table"); + } + this.table = table; + } + + public String name() { + return name; + } + + public T table() { + return table; + } +} diff --git a/core/src/main/java/org/apache/calcite/schema/Schema.java b/core/src/main/java/org/apache/calcite/schema/Schema.java index 5e034b1da9d..bd1c673fcfb 100644 --- a/core/src/main/java/org/apache/calcite/schema/Schema.java +++ b/core/src/main/java/org/apache/calcite/schema/Schema.java @@ -19,6 +19,8 @@ import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.rel.type.RelProtoDataType; +import org.apache.calcite.schema.impl.SimpleTableLookup; + import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; @@ -56,19 +58,35 @@ * {@link Schema#getSubSchema(String)}. */ public interface Schema { + + /** + * Returns a lookup object to find tables + * + * @return Lookup + */ + default Lookup
tables() { + return new SimpleTableLookup(this); + } /** * Returns a table with a given name, or null if not found. * + * @deprecated + * Please use {@link Schema#tables()} and {@link Lookup#get(String)} instead. + * * @param name Table name * @return Table, or null */ - @Nullable Table getTable(String name); + @Deprecated + @Nullable Table getTable(String name) ; /** * Returns the names of the tables in this schema. * + * @deprecated + * Please use {@link Schema#tables()} and {@link Lookup#getNames(LikePattern)} instead. * @return Names of the tables in this schema */ + @Deprecated Set getTableNames(); /** diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java index 89c9f8e3078..3da44d79f90 100644 --- a/core/src/main/java/org/apache/calcite/schema/Schemas.java +++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java @@ -259,7 +259,7 @@ public static Queryable queryable(DataContext root, Class clazz, public static Queryable queryable(DataContext root, SchemaPlus schema, Class clazz, String tableName) { QueryableTable table = - (QueryableTable) requireNonNull(schema.getTable(tableName), + (QueryableTable) requireNonNull(schema.tables().get(tableName), () -> "table " + tableName + " is not found in " + schema); QueryProvider queryProvider = root.getQueryProvider(); return table.asQueryable(queryProvider, schema, tableName); @@ -314,7 +314,7 @@ private static int[] identity(int count) { } schema = next; } else { - return schema.getTable(name); + return schema.tables().get(name); } } } diff --git a/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java b/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java index a7b050a5045..35a73cafca0 100644 --- a/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java +++ b/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java @@ -16,15 +16,10 @@ */ package org.apache.calcite.schema.impl; +import org.apache.calcite.linq4j.function.Predicate1; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.rel.type.RelProtoDataType; -import org.apache.calcite.schema.Function; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaFactory; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.SchemaVersion; -import org.apache.calcite.schema.Schemas; -import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.*; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; @@ -35,6 +30,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; @@ -56,9 +52,16 @@ * */ public class AbstractSchema implements Schema { + + private Lookup
tables = new SimpleTableLookup(this); + public AbstractSchema() { } + @Override public Lookup
tables() { + return tables; + } + @Override public boolean isMutable() { return true; } @@ -86,12 +89,12 @@ protected Map getTableMap() { return ImmutableMap.of(); } - @Override public final Set getTableNames() { + @Deprecated @Override public final Set getTableNames() { //noinspection RedundantCast return (Set) getTableMap().keySet(); } - @Override public final @Nullable Table getTable(String name) { + @Deprecated @Override public final @Nullable Table getTable(String name) { return getTableMap().get(name); } diff --git a/core/src/main/java/org/apache/calcite/schema/impl/CachingLookup.java b/core/src/main/java/org/apache/calcite/schema/impl/CachingLookup.java new file mode 100644 index 00000000000..43c66830dcb --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/CachingLookup.java @@ -0,0 +1,120 @@ +/* + * 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.schema.impl; + +import com.google.common.base.Suppliers; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import com.google.common.util.concurrent.UncheckedExecutionException; + +import org.apache.calcite.adapter.jdbc.JdbcSchema; +import org.apache.calcite.adapter.jdbc.JdbcTable; +import org.apache.calcite.linq4j.function.Predicate1; +import org.apache.calcite.schema.LikePattern; +import org.apache.calcite.schema.Lookup; +import org.apache.calcite.schema.Named; +import org.apache.calcite.schema.Schema; +import org.apache.calcite.schema.Table; +import org.apache.calcite.util.NameMap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * This class can be used to cache lookups + * @param + */ +public class CachingLookup implements Lookup { + private final Lookup delegate; + + private final LoadingCache cache; + private final LoadingCache> cacheIgnoreCase; + + public CachingLookup(Lookup delegate){ + this.delegate = delegate; + this.cache = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(new CacheLoader() { + @Override + public T load(String name) throws Exception { + return Optional.ofNullable(delegate.get(name)).orElseThrow(() -> new EntryNotFoundException()); + } + }); + this.cacheIgnoreCase = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(new CacheLoader>() { + @Override + public Named load(String name) throws Exception { + return Optional.ofNullable(delegate.getIgnoreCase(name)).orElseThrow(() -> new EntryNotFoundException()); + } + }); + } + + @Override + public @Nullable T get(String name) { + try { + return cache.get(name); + } catch ( UncheckedExecutionException e) { + if ( e.getCause() instanceof EntryNotFoundException) { + return null; + } + throw e; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public @Nullable Named getIgnoreCase(String name) { + try { + return cacheIgnoreCase.get(name); + } catch ( UncheckedExecutionException e) { + if ( e.getCause() instanceof EntryNotFoundException) { + return null; + } + throw e; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public Set getNames(LikePattern pattern) { + return delegate.getNames(pattern); + } + + public void invalidate(String name) { + cache.invalidate(name); + cacheIgnoreCase.invalidate(name); + } + + public void invalidateAll() { + cache.invalidateAll(); + cacheIgnoreCase.invalidateAll(); + } +} diff --git a/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java b/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java index e63f518878b..2bf84266ecc 100644 --- a/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java +++ b/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java @@ -19,6 +19,8 @@ import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.schema.Function; +import org.apache.calcite.schema.LikePattern; +import org.apache.calcite.schema.Lookup; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.SchemaVersion; @@ -61,12 +63,16 @@ public DelegatingSchema(Schema schema) { return schema.getExpression(parentSchema, name); } - @Override public @Nullable Table getTable(String name) { - return schema.getTable(name); + @Override public @Nullable Lookup
tables() { + return schema.tables(); } - @Override public Set getTableNames() { - return schema.getTableNames(); + @Deprecated @Override public @Nullable Table getTable(String name) { + return schema.tables().get(name); + } + + @Deprecated @Override public Set getTableNames() { + return schema.tables().getNames(LikePattern.any()); } @Override public @Nullable RelProtoDataType getType(String name) { diff --git a/core/src/main/java/org/apache/calcite/schema/impl/EntryNotFoundException.java b/core/src/main/java/org/apache/calcite/schema/impl/EntryNotFoundException.java new file mode 100644 index 00000000000..4d166569f55 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/EntryNotFoundException.java @@ -0,0 +1,24 @@ +/* + * 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.schema.impl; + +/** + * Exceptions used in caches to signal none existing entries + */ +class EntryNotFoundException extends RuntimeException{ +} diff --git a/core/src/main/java/org/apache/calcite/schema/impl/IgnoreCaseLookup.java b/core/src/main/java/org/apache/calcite/schema/impl/IgnoreCaseLookup.java new file mode 100644 index 00000000000..cc2eb5b7969 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/IgnoreCaseLookup.java @@ -0,0 +1,86 @@ +/* + * 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.schema.impl; + +import com.google.common.base.Suppliers; + +import org.apache.calcite.schema.LikePattern; +import org.apache.calcite.schema.Lookup; +import org.apache.calcite.schema.Named; +import org.apache.calcite.util.NameMap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * An abstract base class for lookups. implementing case insensitive lookup + * + */ +public abstract class IgnoreCaseLookup implements Lookup { + + private NameMap nameMap; + + public IgnoreCaseLookup() { + } + + /** + * Returns a named entity with a given name, or null if not found. + * + * @param name Name + * @return Entity, or null + */ + @Nullable public abstract T get(String name) ; + + /** + * Returns a named entity with a given name ignoring the case, or null if not found. + * + * @param name Name + * @return Entity, or null + */ + @Override @Nullable public Named getIgnoreCase(String name){ + Map.Entry entry = getNameMap(false).range(name, false).firstEntry(); + if (entry == null) { + entry = getNameMap(true).range(name, false).firstEntry(); + if ( entry == null) { + return null; + } + } + T result = get(entry.getValue()); + return result == null ? null : new Named<>(entry.getKey(),result); + } + + @Nullable public abstract Set getNames(LikePattern pattern); + + private NameMap getNameMap(boolean forceReload) { + if ( nameMap == null || forceReload) { + synchronized (this) { + if ( nameMap == null || forceReload) { + NameMap tmp = new NameMap<>(); + for (String name : getNames(LikePattern.any())) { + tmp.put(name, name); + } + nameMap = tmp; + } + } + } + return nameMap; + } +} diff --git a/core/src/main/java/org/apache/calcite/schema/impl/SimpleTableLookup.java b/core/src/main/java/org/apache/calcite/schema/impl/SimpleTableLookup.java new file mode 100644 index 00000000000..bd3729de08c --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/SimpleTableLookup.java @@ -0,0 +1,55 @@ +/* + * 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.schema.impl; + +import org.apache.calcite.linq4j.function.Predicate1; +import org.apache.calcite.schema.LikePattern; + +import org.apache.calcite.schema.Schema; + +import org.apache.calcite.schema.Table; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Set; +import java.util.stream.Collectors; + +public class SimpleTableLookup extends IgnoreCaseLookup
{ + + private final Schema schema; + + public SimpleTableLookup(Schema schema) { + this.schema = schema; + } + + @SuppressWarnings("deprecation") + @Nullable + @Override + public Table get(String name) { + return schema.getTable(name); + } + + @SuppressWarnings("deprecation") + @Override + public @Nullable Set getNames(LikePattern pattern) { + final Predicate1 matcher = pattern.matcher(); + return schema.getTableNames().stream() + .filter(name -> matcher.apply(name)) + .collect(Collectors.toSet()); + } +} 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 46fce32db5c..b7e19c8f652 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -29,7 +29,6 @@ import org.apache.calcite.avatica.AvaticaStatement; import org.apache.calcite.avatica.Handler; import org.apache.calcite.avatica.HandlerImpl; -import org.apache.calcite.avatica.Meta; import org.apache.calcite.avatica.util.Casing; import org.apache.calcite.avatica.util.Quoting; import org.apache.calcite.config.CalciteConnectionConfig; @@ -38,7 +37,6 @@ import org.apache.calcite.config.Lex; import org.apache.calcite.config.NullCollation; import org.apache.calcite.jdbc.CalciteConnection; -import org.apache.calcite.jdbc.CalciteMetaImpl; import org.apache.calcite.jdbc.CalcitePrepare; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.jdbc.Driver; @@ -60,6 +58,7 @@ import org.apache.calcite.runtime.FlatLists; import org.apache.calcite.runtime.Hook; import org.apache.calcite.runtime.SqlFunctions; +import org.apache.calcite.schema.LikePattern; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaFactory; import org.apache.calcite.schema.SchemaPlus; @@ -1064,7 +1063,7 @@ private String mm(int majorVersion, int minorVersion) { } /** Unit test for - * {@link org.apache.calcite.jdbc.CalciteMetaImpl#likeToRegex(org.apache.calcite.avatica.Meta.Pat)}. */ + * {@link LikePattern#likeToRegex(org.apache.calcite.avatica.Meta.Pat)}. */ @Test void testLikeToRegex() { checkLikeToRegex(true, "%", "abc"); checkLikeToRegex(true, "abc", "abc"); @@ -1089,7 +1088,7 @@ private String mm(int majorVersion, int minorVersion) { } private void checkLikeToRegex(boolean b, String pattern, String abc) { - final Pattern regex = CalciteMetaImpl.likeToRegex(Meta.Pat.of(pattern)); + final Pattern regex = LikePattern.likeToRegex(pattern); assertTrue(b == regex.matcher(abc).matches()); } diff --git a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java index a2a326ca6b7..6ffec1341c9 100644 --- a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java +++ b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java @@ -320,7 +320,7 @@ public void execute(SqlDropObject drop, case DROP_TABLE: case DROP_MATERIALIZED_VIEW: Table materializedView = schemaExists && drop.getKind() == SqlKind.DROP_MATERIALIZED_VIEW - ? schema.plus().getTable(objectName) : null; + ? schema.plus().tables().get(objectName) : null; existed = schemaExists && schema.removeTable(objectName); if (existed) { @@ -370,7 +370,7 @@ public void execute(SqlDropObject drop, public void execute(SqlTruncateTable truncate, CalcitePrepare.Context context) { final Pair pair = schema(context, true, truncate.name); - if (pair.left.plus().getTable(pair.right) == null) { + if (pair.left.plus().tables().get(pair.right) == null) { throw SqlUtil.newContextException(truncate.name.getParserPosition(), RESOURCE.tableNotFound(pair.right)); } @@ -387,7 +387,7 @@ public void execute(SqlTruncateTable truncate, public void execute(SqlCreateMaterializedView create, CalcitePrepare.Context context) { final Pair pair = schema(context, true, create.name); - if (pair.left.plus().getTable(pair.right) != null) { + if (pair.left.plus().tables().get(pair.right) != null) { // Materialized view exists. if (!create.ifNotExists) { // They did not specify IF NOT EXISTS, so give error. @@ -541,7 +541,7 @@ public void execute(SqlCreateTable create, return super.newColumnDefaultValue(table, iColumn, context); } }; - if (pair.left.plus().getTable(pair.right) != null) { + if (pair.left.plus().tables().get(pair.right) != null) { // Table exists. if (create.ifNotExists) { return; @@ -566,7 +566,7 @@ public void execute(SqlCreateTable create, public void execute(SqlCreateTableLike create, CalcitePrepare.Context context) { final Pair pair = schema(context, true, create.name); - if (pair.left.plus().getTable(pair.right) != null) { + if (pair.left.plus().tables().get(pair.right) != null) { // Table exists. if (create.ifNotExists) { return; diff --git a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java index 089bb53b005..06d6934aac6 100644 --- a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java +++ b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java @@ -988,7 +988,7 @@ static SchemaPlus addSchema_(SchemaPlus rootSchema, SchemaSpec schema) { // They redirect requests for SqlDialect and DataSource to the real JDBC // FOODMART, and this allows statistics queries to be executed. foodmart = addSchemaIfNotExists(rootSchema, SchemaSpec.JDBC_FOODMART); - final Wrapper salesTable = (Wrapper) foodmart.getTable("sales_fact_1997"); + final Wrapper salesTable = (Wrapper) foodmart.tables().get("sales_fact_1997"); SchemaPlus fake = rootSchema.add(schema.schemaName, new AbstractSchema()); fake.add("time_by_day", new AbstractTable() { @@ -2231,11 +2231,11 @@ static List unwrap(String java) { } }; - @Override public Table getTable(String name) { + @Deprecated @Override public Table getTable(String name) { return table; } - @Override public Set getTableNames() { + @Deprecated @Override public Set getTableNames() { return ImmutableSet.of("myTable"); }