diff --git a/.github/workflows/product-tests-basic-environment.yml b/.github/workflows/product-tests-basic-environment.yml index fbbfa2265ea64..d4761dcc07162 100644 --- a/.github/workflows/product-tests-basic-environment.yml +++ b/.github/workflows/product-tests-basic-environment.yml @@ -69,4 +69,4 @@ jobs: if: needs.changes.outputs.codechange == 'true' env: OVERRIDE_JDK_DIR: ${{ env.JAVA_HOME }} - run: presto-product-tests/bin/run_on_docker.sh multinode -x quarantine,big_query,storage_formats,profile_specific_tests,tpcds,cassandra,mysql_connector,postgresql_connector,mysql,kafka,avro + run: presto-product-tests/bin/run_on_docker.sh multinode -x quarantine,big_query,storage_formats,profile_specific_tests,tpcds,cassandra,mysql_connector,mysql_mixed_case,postgresql_connector,mysql,kafka,avro,mixed_case diff --git a/.github/workflows/product-tests-specific-environment.yml b/.github/workflows/product-tests-specific-environment.yml index 27218a7b2c28e..0835370e67d21 100644 --- a/.github/workflows/product-tests-specific-environment.yml +++ b/.github/workflows/product-tests-specific-environment.yml @@ -69,7 +69,7 @@ jobs: if: needs.changes.outputs.codechange == 'true' env: OVERRIDE_JDK_DIR: ${{ env.JAVA_HOME }} - run: presto-product-tests/bin/run_on_docker.sh singlenode -g hdfs_no_impersonation,avro + run: presto-product-tests/bin/run_on_docker.sh singlenode -g hdfs_no_impersonation,avro,mixed_case - name: Product Tests Specific 1.2 if: needs.changes.outputs.codechange == 'true' env: @@ -169,3 +169,8 @@ jobs: env: OVERRIDE_JDK_DIR: ${{ env.JAVA_HOME }} run: presto-product-tests/bin/run_on_docker.sh singlenode-sqlserver -g sqlserver + - name: Product Tests Specific 2.9 + if: needs.changes.outputs.codechange == 'true' + env: + OVERRIDE_JDK_DIR: ${{ env.JAVA_HOME }} + run: presto-product-tests/bin/run_on_docker.sh singlenode-mysql-mixed-case-on -g mysql_connector,mysql_mixed_case diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java index bb704f1057bef..c8117256c4f81 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java @@ -27,10 +27,10 @@ import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.FixedSplitSource; import com.facebook.presto.spi.PrestoException; +import com.facebook.presto.spi.PrestoWarning; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.spi.TableNotFoundException; import com.facebook.presto.spi.statistics.TableStatistics; -import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -70,6 +70,7 @@ import static com.facebook.presto.common.type.VarbinaryType.VARBINARY; import static com.facebook.presto.common.type.Varchars.isVarcharType; import static com.facebook.presto.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static com.facebook.presto.plugin.jdbc.JdbcWarningCode.USE_OF_DEPRECATED_CONFIGURATION_PROPERTY; import static com.facebook.presto.plugin.jdbc.StandardReadMappings.jdbcTypeToPrestoType; import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; @@ -77,7 +78,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getOnlyElement; @@ -118,6 +118,7 @@ public class BaseJdbcClient protected final Cache> remoteSchemaNames; protected final Cache> remoteTableNames; protected final Set listSchemasIgnoredSchemas; + protected final boolean caseSensitiveNameMatchingEnabled; public BaseJdbcClient(JdbcConnectorId connectorId, BaseJdbcConfig config, String identifierQuote, ConnectionFactory connectionFactory) { @@ -132,6 +133,7 @@ public BaseJdbcClient(JdbcConnectorId connectorId, BaseJdbcConfig config, String this.remoteSchemaNames = remoteNamesCacheBuilder.build(); this.remoteTableNames = remoteNamesCacheBuilder.build(); this.listSchemasIgnoredSchemas = config.getlistSchemasIgnoredSchemas(); + this.caseSensitiveNameMatchingEnabled = config.isCaseSensitiveNameMatching(); } @PreDestroy @@ -152,7 +154,7 @@ public final Set getSchemaNames(ConnectorSession session, JdbcIdentity i { try (Connection connection = connectionFactory.openConnection(identity)) { return listSchemas(connection).stream() - .map(schemaName -> schemaName.toLowerCase(ENGLISH)) + .map(schemaName -> normalizeIdentifier(session, schemaName)) .collect(toImmutableSet()); } catch (SQLException e) { @@ -182,13 +184,14 @@ protected Collection listSchemas(Connection connection) public List getTableNames(ConnectorSession session, JdbcIdentity identity, Optional schema) { try (Connection connection = connectionFactory.openConnection(identity)) { - Optional remoteSchema = schema.map(schemaName -> toRemoteSchemaName(identity, connection, schemaName)); + Optional remoteSchema = schema.map(schemaName -> toRemoteSchemaName(session, identity, connection, schemaName)); try (ResultSet resultSet = getTables(connection, remoteSchema, Optional.empty())) { ImmutableList.Builder list = ImmutableList.builder(); while (resultSet.next()) { String tableSchema = getTableSchemaName(resultSet); String tableName = resultSet.getString("TABLE_NAME"); - list.add(new SchemaTableName(tableSchema.toLowerCase(ENGLISH), tableName.toLowerCase(ENGLISH))); + list.add(new SchemaTableName(normalizeIdentifier(session, tableSchema), + normalizeIdentifier(session, tableName))); } return list.build(); } @@ -203,8 +206,8 @@ public List getTableNames(ConnectorSession session, JdbcIdentit public JdbcTableHandle getTableHandle(ConnectorSession session, JdbcIdentity identity, SchemaTableName schemaTableName) { try (Connection connection = connectionFactory.openConnection(identity)) { - String remoteSchema = toRemoteSchemaName(identity, connection, schemaTableName.getSchemaName()); - String remoteTable = toRemoteTableName(identity, connection, remoteSchema, schemaTableName.getTableName()); + String remoteSchema = toRemoteSchemaName(session, identity, connection, schemaTableName.getSchemaName()); + String remoteTable = toRemoteTableName(session, identity, connection, remoteSchema, schemaTableName.getTableName()); try (ResultSet resultSet = getTables(connection, Optional.of(remoteSchema), Optional.of(remoteTable))) { List tableHandles = new ArrayList<>(); while (resultSet.next()) { @@ -363,8 +366,8 @@ protected JdbcOutputTableHandle createTable(ConnectorTableMetadata tableMetadata try (Connection connection = connectionFactory.openConnection(identity)) { boolean uppercase = connection.getMetaData().storesUpperCaseIdentifiers(); - String remoteSchema = toRemoteSchemaName(identity, connection, schemaTableName.getSchemaName()); - String remoteTable = toRemoteTableName(identity, connection, remoteSchema, schemaTableName.getTableName()); + String remoteSchema = toRemoteSchemaName(session, identity, connection, schemaTableName.getSchemaName()); + String remoteTable = toRemoteTableName(session, identity, connection, remoteSchema, schemaTableName.getTableName()); if (uppercase) { tableName = tableName.toUpperCase(ENGLISH); } @@ -607,6 +610,12 @@ public PreparedStatement getPreparedStatement(ConnectorSession session, Connecti return connection.prepareStatement(sql); } + @Override + public String normalizeIdentifier(ConnectorSession session, String identifier) + { + return identifier.toLowerCase(ENGLISH); + } + protected ResultSet getTables(Connection connection, Optional schemaName, Optional tableName) throws SQLException { @@ -625,12 +634,14 @@ protected String getTableSchemaName(ResultSet resultSet) return resultSet.getString("TABLE_SCHEM"); } - protected String toRemoteSchemaName(JdbcIdentity identity, Connection connection, String schemaName) + protected String toRemoteSchemaName(ConnectorSession session, JdbcIdentity identity, Connection connection, String schemaName) { requireNonNull(schemaName, "schemaName is null"); - verify(CharMatcher.forPredicate(Character::isUpperCase).matchesNoneOf(schemaName), "Expected schema name from internal metadata to be lowercase: %s", schemaName); if (caseInsensitiveNameMatching) { + session.getWarningCollector().add(new PrestoWarning(USE_OF_DEPRECATED_CONFIGURATION_PROPERTY, + "'case-insensitive-name-matching' is deprecated. Use of this configuration value may lead to query failures. " + + "Please switch to using 'case-sensitive-name-matching' for proper case sensitivity behavior.")); try { Map mapping = remoteSchemaNames.getIfPresent(identity); if (mapping != null && !mapping.containsKey(schemaName)) { @@ -669,13 +680,15 @@ protected Map listSchemasByLowerCase(Connection connection) .collect(toImmutableMap(schemaName -> schemaName.toLowerCase(ENGLISH), schemaName -> schemaName)); } - protected String toRemoteTableName(JdbcIdentity identity, Connection connection, String remoteSchema, String tableName) + protected String toRemoteTableName(ConnectorSession session, JdbcIdentity identity, Connection connection, String remoteSchema, String tableName) { requireNonNull(remoteSchema, "remoteSchema is null"); requireNonNull(tableName, "tableName is null"); - verify(CharMatcher.forPredicate(Character::isUpperCase).matchesNoneOf(tableName), "Expected table name from internal metadata to be lowercase: %s", tableName); if (caseInsensitiveNameMatching) { + session.getWarningCollector().add(new PrestoWarning(USE_OF_DEPRECATED_CONFIGURATION_PROPERTY, + "'case-insensitive-name-matching' is deprecated. Use of this configuration value may lead to query failures. " + + "Please switch to using 'case-sensitive-name-matching' for proper case sensitivity behavior.")); try { RemoteTableNameCacheKey cacheKey = new RemoteTableNameCacheKey(identity, remoteSchema); Map mapping = remoteTableNames.getIfPresent(cacheKey); diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java index fc4f8ed2c3d0d..9ad4626df5d0f 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcConfig.java @@ -14,13 +14,18 @@ package com.facebook.presto.plugin.jdbc; import com.facebook.airlift.configuration.Config; +import com.facebook.airlift.configuration.ConfigDescription; import com.facebook.airlift.configuration.ConfigSecuritySensitive; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.inject.ConfigurationException; +import com.google.inject.spi.Message; import io.airlift.units.Duration; import io.airlift.units.MinDuration; import javax.annotation.Nullable; +import javax.annotation.PostConstruct; import javax.validation.constraints.NotNull; import java.util.Set; @@ -38,6 +43,7 @@ public class BaseJdbcConfig private boolean caseInsensitiveNameMatching; private Duration caseInsensitiveNameMatchingCacheTtl = new Duration(1, MINUTES); private Set listSchemasIgnoredSchemas = ImmutableSet.of("information_schema"); + private boolean caseSensitiveNameMatchingEnabled; @NotNull public String getConnectionUrl() @@ -105,12 +111,18 @@ public BaseJdbcConfig setPasswordCredentialName(String passwordCredentialName) return this; } + @Deprecated public boolean isCaseInsensitiveNameMatching() { return caseInsensitiveNameMatching; } + @Deprecated @Config("case-insensitive-name-matching") + @ConfigDescription("Deprecated: This will be removed in future releases. Use 'case-sensitive-name-matching=true' instead for mysql. " + + "This configuration setting converts all schema/table names to lowercase. " + + "If your source database contains names differing only by case (e.g., 'Testdb' and 'testdb'), " + + "this setting can lead to conflicts and query failures.") public BaseJdbcConfig setCaseInsensitiveNameMatching(boolean caseInsensitiveNameMatching) { this.caseInsensitiveNameMatching = caseInsensitiveNameMatching; @@ -142,4 +154,27 @@ public BaseJdbcConfig setlistSchemasIgnoredSchemas(String listSchemasIgnoredSche this.listSchemasIgnoredSchemas = ImmutableSet.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().split(listSchemasIgnoredSchemas.toLowerCase(ENGLISH))); return this; } + + public boolean isCaseSensitiveNameMatching() + { + return caseSensitiveNameMatchingEnabled; + } + + @Config("case-sensitive-name-matching") + @ConfigDescription("Enable case-sensitive matching of schema, table names across the connector. " + + "When disabled, names are matched case-insensitively using lowercase normalization.") + public BaseJdbcConfig setCaseSensitiveNameMatching(boolean caseSensitiveNameMatchingEnabled) + { + this.caseSensitiveNameMatchingEnabled = caseSensitiveNameMatchingEnabled; + return this; + } + + @PostConstruct + public void validateConfig() + { + if (isCaseInsensitiveNameMatching() && isCaseSensitiveNameMatching()) { + throw new ConfigurationException(ImmutableList.of(new Message("Only one of 'case-insensitive-name-matching=true' or 'case-sensitive-name-matching=true' can be set. " + + "These options are mutually exclusive."))); + } + } } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java index 722841fe05c3e..591a73ea2ce7c 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java @@ -98,4 +98,6 @@ PreparedStatement getPreparedStatement(ConnectorSession session, Connection conn throws SQLException; TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle, List columnHandles, TupleDomain tupleDomain); + + String normalizeIdentifier(ConnectorSession session, String identifier); } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcMetadata.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcMetadata.java index fd92e8730af74..ae0768913587e 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcMetadata.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcMetadata.java @@ -267,4 +267,10 @@ public TableStatistics getTableStatistics(ConnectorSession session, ConnectorTab List columns = columnHandles.stream().map(JdbcColumnHandle.class::cast).collect(Collectors.toList()); return jdbcClient.getTableStatistics(session, handle, columns, constraint.getSummary()); } + + @Override + public String normalizeIdentifier(ConnectorSession session, String identifier) + { + return jdbcClient.normalizeIdentifier(session, identifier); + } } diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcWarningCode.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcWarningCode.java new file mode 100644 index 0000000000000..32cd545adeed7 --- /dev/null +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcWarningCode.java @@ -0,0 +1,38 @@ +/* + * Licensed 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 com.facebook.presto.plugin.jdbc; + +import com.facebook.presto.spi.WarningCode; +import com.facebook.presto.spi.WarningCodeSupplier; + +public enum JdbcWarningCode + implements WarningCodeSupplier +{ + USE_OF_DEPRECATED_CONFIGURATION_PROPERTY(1), + /**/; + private final WarningCode warningCode; + + public static final int WARNING_CODE_MASK = 0x0300_0000; + + JdbcWarningCode(int code) + { + warningCode = new WarningCode(code + WARNING_CODE_MASK, name()); + } + + @Override + public WarningCode toWarningCode() + { + return warningCode; + } +} diff --git a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java index 2c3d151bb19f6..70d08ce1c399b 100644 --- a/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java +++ b/presto-base-jdbc/src/test/java/com/facebook/presto/plugin/jdbc/TestBaseJdbcConfig.java @@ -36,7 +36,8 @@ public void testDefaults() .setPasswordCredentialName(null) .setCaseInsensitiveNameMatching(false) .setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, MINUTES)) - .setlistSchemasIgnoredSchemas("information_schema")); + .setlistSchemasIgnoredSchemas("information_schema") + .setCaseSensitiveNameMatching(false)); } @Test @@ -51,6 +52,7 @@ public void testExplicitPropertyMappings() .put("case-insensitive-name-matching", "true") .put("case-insensitive-name-matching.cache-ttl", "1s") .put("list-schemas-ignored-schemas", "test,test2") + .put("case-sensitive-name-matching", "true") .build(); BaseJdbcConfig expected = new BaseJdbcConfig() @@ -61,7 +63,8 @@ public void testExplicitPropertyMappings() .setPasswordCredentialName("bar") .setCaseInsensitiveNameMatching(true) .setlistSchemasIgnoredSchemas("test,test2") - .setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, SECONDS)); + .setCaseInsensitiveNameMatchingCacheTtl(new Duration(1, SECONDS)) + .setCaseSensitiveNameMatching(true); ConfigAssertions.assertFullMapping(properties, expected); } diff --git a/presto-common/src/main/java/com/facebook/presto/common/CatalogSchemaName.java b/presto-common/src/main/java/com/facebook/presto/common/CatalogSchemaName.java index ed85a12904a5c..3b63bc89cb385 100644 --- a/presto-common/src/main/java/com/facebook/presto/common/CatalogSchemaName.java +++ b/presto-common/src/main/java/com/facebook/presto/common/CatalogSchemaName.java @@ -34,7 +34,7 @@ public final class CatalogSchemaName public CatalogSchemaName(String catalogName, String schemaName) { this.catalogName = requireNonNull(catalogName, "catalogName is null").toLowerCase(ENGLISH); - this.schemaName = requireNonNull(schemaName, "schemaName is null").toLowerCase(ENGLISH); + this.schemaName = requireNonNull(schemaName, "schemaName is null"); } @ThriftField(1) diff --git a/presto-common/src/main/java/com/facebook/presto/common/QualifiedObjectName.java b/presto-common/src/main/java/com/facebook/presto/common/QualifiedObjectName.java index 70d724822bfa3..c5566b0d73882 100644 --- a/presto-common/src/main/java/com/facebook/presto/common/QualifiedObjectName.java +++ b/presto-common/src/main/java/com/facebook/presto/common/QualifiedObjectName.java @@ -62,8 +62,6 @@ public static QualifiedObjectName valueOf(String catalogName, String schemaName, public QualifiedObjectName(String catalogName, String schemaName, String objectName) { checkLowerCase(catalogName, "catalogName"); - checkLowerCase(schemaName, "schemaName"); - checkLowerCase(objectName, "objectName"); this.catalogName = catalogName; this.schemaName = schemaName; this.objectName = objectName; diff --git a/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java b/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java index 321576bb5eb7b..860fcdb9cb733 100644 --- a/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java +++ b/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java @@ -58,6 +58,7 @@ private RuntimeMetricName() public static final String GET_CANONICAL_INFO_TIME_NANOS = "getCanonicalInfoTimeNanos"; public static final String FRAGMENT_PLAN_TIME_NANOS = "fragmentPlanTimeNanos"; public static final String GET_LAYOUT_TIME_NANOS = "getLayoutTimeNanos"; + public static final String GET_IDENTIFIER_NORMALIZATION_TIME_NANOS = "getIdentifierNormalizationTimeNanos"; public static final String REWRITE_AGGREGATION_IF_TO_FILTER_APPLIED = "rewriteAggregationIfToFilterApplied"; // Time between task creation and start. public static final String TASK_QUEUED_TIME_NANOS = "taskQueuedTimeNanos"; diff --git a/presto-docs/src/main/sphinx/connector/mysql.rst b/presto-docs/src/main/sphinx/connector/mysql.rst index 85070da8fb6e6..758bfa110ceb9 100644 --- a/presto-docs/src/main/sphinx/connector/mysql.rst +++ b/presto-docs/src/main/sphinx/connector/mysql.rst @@ -68,6 +68,10 @@ Property Name Description cached. Set to ``0ms`` to disable the cache. ``1m`` ``list-schemas-ignored-schemas`` List of schemas to ignore when listing schemas. ``information_schema,mysql`` + +``case-sensitive-name-matching`` Enable case sensitive identifier support for schema and table ``false`` + names for the connector. When disabled, names are matched + case-insensitively using lowercase normalization. ================================================== ==================================================================== =========== Querying MySQL diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java index 82ca971d5619d..ed91212089c3a 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java @@ -2548,6 +2548,7 @@ private Table updateTable(String tableName) protected Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(catalogType.getCatalogImpl(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of("tpch", tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergDistributedHive.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergDistributedHive.java index 40eaa3ed59562..f0ff6cf69b0c0 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergDistributedHive.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergDistributedHive.java @@ -190,6 +190,7 @@ public void testManifestFileCachingDisabled() @Override protected Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); CatalogManager catalogManager = getDistributedQueryRunner().getCoordinator().getCatalogManager(); ConnectorId connectorId = catalogManager.getCatalog(ICEBERG_CATALOG).get().getConnectorId(); diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergHiveStatistics.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergHiveStatistics.java index b03a7da1ed188..f154cae8c4190 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergHiveStatistics.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergHiveStatistics.java @@ -593,6 +593,7 @@ private void deleteTableStatistics(String tableName) private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); CatalogManager catalogManager = getDistributedQueryRunner().getCoordinator().getCatalogManager(); ConnectorId connectorId = catalogManager.getCatalog(ICEBERG_CATALOG).get().getConnectorId(); diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestExpireSnapshotProcedure.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestExpireSnapshotProcedure.java index cb5389106148a..5ecb275cfd10b 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestExpireSnapshotProcedure.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestExpireSnapshotProcedure.java @@ -248,6 +248,7 @@ private String getTimestampString(long timeMillsUtc, String zoneId) private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HadoopCatalog.class.getName(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestFastForwardBranchProcedure.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestFastForwardBranchProcedure.java index 708b053a4a42e..fa685cb30d163 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestFastForwardBranchProcedure.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestFastForwardBranchProcedure.java @@ -262,6 +262,7 @@ public void testFastForwardNonExistingBranch() private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HadoopCatalog.class.getName(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHadoop.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHadoop.java index d65f134583042..ba62a81799786 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHadoop.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHadoop.java @@ -74,6 +74,7 @@ Table createTable(String tableName, String targetPath, Map table @Override Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HADOOP.getCatalogImpl(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHive.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHive.java index 856d59625580c..b186ef87cf663 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHive.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRemoveOrphanFilesProcedureHive.java @@ -90,6 +90,7 @@ Table createTable(String tableName, String targetPath, Map table @Override Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); CatalogManager catalogManager = getDistributedQueryRunner().getCoordinator().getCatalogManager(); ConnectorId connectorId = catalogManager.getCatalog(ICEBERG_CATALOG).get().getConnectorId(); diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRollbackToTimestampProcedure.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRollbackToTimestampProcedure.java index 2f5c1ba3eb6af..11397d91c53e8 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRollbackToTimestampProcedure.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestRollbackToTimestampProcedure.java @@ -251,6 +251,7 @@ private static String getTimestampString(long timeMillsUtc, String zoneId) private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HADOOP.getCatalogImpl(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetCurrentSnapshotProcedure.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetCurrentSnapshotProcedure.java index 7cb88bc9d4c19..fa50afa142727 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetCurrentSnapshotProcedure.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetCurrentSnapshotProcedure.java @@ -174,6 +174,7 @@ public void testSetCurrentSnapshotToRef() private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HadoopCatalog.class.getName(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetTablePropertyProcedure.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetTablePropertyProcedure.java index 6f98befeca4c3..0f227ab770152 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetTablePropertyProcedure.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/procedure/TestSetTablePropertyProcedure.java @@ -160,6 +160,7 @@ public void testInvalidSetTablePropertyProcedureCases() private Table loadTable(String tableName) { + tableName = normalizeIdentifier(tableName, ICEBERG_CATALOG); Catalog catalog = CatalogUtil.loadCatalog(HadoopCatalog.class.getName(), ICEBERG_CATALOG, getProperties(), new Configuration()); return catalog.loadTable(TableIdentifier.of(TEST_SCHEMA, tableName)); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java index 9a5deaab70716..89fcf82f66074 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java +++ b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java @@ -54,6 +54,7 @@ import static com.facebook.presto.metadata.MetadataUtil.SchemaMetadataBuilder.schemaMetadataBuilder; import static com.facebook.presto.metadata.MetadataUtil.TableMetadataBuilder.tableMetadataBuilder; import static com.facebook.presto.metadata.MetadataUtil.findColumnMetadata; +import static com.facebook.presto.metadata.QualifiedTablePrefix.toQualifiedTablePrefix; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Predicates.compose; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -264,7 +265,6 @@ private Set calculatePrefixesWithSchemaName( Optional> schemas = filterString(constraint, SCHEMA_COLUMN_HANDLE); if (schemas.isPresent()) { return schemas.get().stream() - .filter(this::isLowerCase) .map(schema -> new QualifiedTablePrefix(catalogName, schema)) .collect(toImmutableSet()); } @@ -289,11 +289,12 @@ public Set calculatePrefixesWithTableName( if (tables.isPresent()) { return prefixes.stream() .flatMap(prefix -> tables.get().stream() - .filter(this::isLowerCase) - .map(table -> table.toLowerCase(ENGLISH)) .map(table -> new QualifiedObjectName(catalogName, prefix.getSchemaName().get(), table))) .filter(objectName -> metadataResolver.getView(objectName).isPresent() || metadataResolver.getTableHandle(objectName).isPresent()) - .map(QualifiedTablePrefix::toQualifiedTablePrefix) + .map(value -> toQualifiedTablePrefix(new QualifiedObjectName( + value.getCatalogName(), + metadata.normalizeIdentifier(session, value.getCatalogName(), value.getSchemaName()), + metadata.normalizeIdentifier(session, value.getCatalogName(), value.getObjectName())))) .collect(toImmutableSet()); } @@ -302,7 +303,10 @@ public Set calculatePrefixesWithTableName( metadata.listTables(session, prefix).stream(), metadata.listViews(session, prefix).stream())) .filter(objectName -> !predicate.isPresent() || predicate.get().test(asFixedValues(objectName))) - .map(QualifiedTablePrefix::toQualifiedTablePrefix) + .map(value -> toQualifiedTablePrefix(new QualifiedObjectName( + value.getCatalogName(), + metadata.normalizeIdentifier(session, value.getCatalogName(), value.getSchemaName()), + metadata.normalizeIdentifier(session, value.getCatalogName(), value.getObjectName())))) .collect(toImmutableSet()); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/system/SystemTableHandle.java b/presto-main-base/src/main/java/com/facebook/presto/connector/system/SystemTableHandle.java index 56fbab3b9173b..71132d30d5045 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/connector/system/SystemTableHandle.java +++ b/presto-main-base/src/main/java/com/facebook/presto/connector/system/SystemTableHandle.java @@ -21,8 +21,6 @@ import java.util.Objects; -import static com.facebook.presto.metadata.MetadataUtil.checkSchemaName; -import static com.facebook.presto.metadata.MetadataUtil.checkTableName; import static java.util.Objects.requireNonNull; public class SystemTableHandle @@ -39,8 +37,8 @@ public SystemTableHandle( @JsonProperty("tableName") String tableName) { this.connectorId = requireNonNull(connectorId, "connectorId is null"); - this.schemaName = checkSchemaName(schemaName); - this.tableName = checkTableName(tableName); + this.schemaName = requireNonNull(schemaName, "schemaName is null"); + this.tableName = requireNonNull(tableName, "tableName is null"); } public static SystemTableHandle fromSchemaTableName(ConnectorId connectorId, SchemaTableName tableName) diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/AddColumnTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/AddColumnTask.java index 031e8e282f413..c10e2d1694a01 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/AddColumnTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/AddColumnTask.java @@ -61,7 +61,7 @@ public String getName() @Override public ListenableFuture execute(AddColumn statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { if (!statement.isTableExists()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/AddConstraintTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/AddConstraintTask.java index fa9754337e6a9..202e13c9df22f 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/AddConstraintTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/AddConstraintTask.java @@ -89,7 +89,7 @@ public String getName() @Override public ListenableFuture execute(AddConstraint statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { if (!statement.isTableExists()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/AlterColumnNotNullTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/AlterColumnNotNullTask.java index f5bedb4880a1b..fdf78196c7a1a 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/AlterColumnNotNullTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/AlterColumnNotNullTask.java @@ -57,7 +57,7 @@ public String getName() @Override public ListenableFuture execute(AlterColumnNotNull statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable(), metadata); Optional tableHandleOptional = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandleOptional.isPresent()) { if (!statement.isTableExists()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java index 139e55db991a7..da84f56afbed0 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/CallTask.java @@ -81,7 +81,7 @@ public ListenableFuture execute(Call call, TransactionManager transactionMana throw new PrestoException(NOT_SUPPORTED, "Procedures cannot be called within a transaction (use autocommit mode)"); } - QualifiedObjectName procedureName = createQualifiedObjectName(session, call, call.getName()); + QualifiedObjectName procedureName = createQualifiedObjectName(session, call, call.getName(), metadata); ConnectorId connectorId = getConnectorIdOrThrow(session, metadata, procedureName.getCatalogName(), call, catalogError); Procedure procedure = metadata.getProcedureRegistry().resolve(connectorId, toSchemaTableName(procedureName)); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java index dfaebac50954b..7fa707e6089aa 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java @@ -75,7 +75,7 @@ public String getName() @Override public ListenableFuture execute(CreateMaterializedView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getName(), metadata); Optional viewHandle = metadata.getMetadataResolver(session).getTableHandle(viewName); if (viewHandle.isPresent()) { @@ -119,7 +119,7 @@ public ListenableFuture execute(CreateMaterializedView statement, Transaction List baseTables = analysis.getTableNodes().stream() .map(table -> { - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); if (!viewName.getCatalogName().equals(tableName.getCatalogName())) { throw new SemanticException( NOT_SUPPORTED, @@ -132,7 +132,7 @@ public ListenableFuture execute(CreateMaterializedView statement, Transaction .distinct() .collect(toImmutableList()); - MaterializedViewColumnMappingExtractor extractor = new MaterializedViewColumnMappingExtractor(analysis, session); + MaterializedViewColumnMappingExtractor extractor = new MaterializedViewColumnMappingExtractor(analysis, session, metadata); MaterializedViewDefinition viewDefinition = new MaterializedViewDefinition( sql, viewName.getSchemaName(), diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateSchemaTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateSchemaTask.java index 4dc2b8d13386a..b0b7a42d263e5 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateSchemaTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateSchemaTask.java @@ -53,7 +53,7 @@ public String explain(CreateSchema statement, List parameters) @Override public ListenableFuture execute(CreateSchema statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - CatalogSchemaName schema = createCatalogSchemaName(session, statement, Optional.of(statement.getSchemaName())); + CatalogSchemaName schema = createCatalogSchemaName(session, statement, Optional.of(statement.getSchemaName()), metadata); // TODO: validate that catalog exists diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateTableTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateTableTask.java index 60f2c8c083129..f1c56285859c9 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateTableTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateTableTask.java @@ -103,7 +103,7 @@ public ListenableFuture internalExecute(CreateTable statement, Metadata metad checkArgument(!statement.getElements().isEmpty(), "no columns for table"); Map, Expression> parameterLookup = parameterExtractor(statement, parameters); - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (tableHandle.isPresent()) { if (!statement.isNotExists()) { @@ -158,7 +158,7 @@ public ListenableFuture internalExecute(CreateTable statement, Metadata metad } else if (element instanceof LikeClause) { LikeClause likeClause = (LikeClause) element; - QualifiedObjectName likeTableName = createQualifiedObjectName(session, statement, likeClause.getTableName()); + QualifiedObjectName likeTableName = createQualifiedObjectName(session, statement, likeClause.getTableName(), metadata); getConnectorIdOrThrow(session, metadata, likeTableName.getCatalogName(), statement, likeTableCatalogError); if (!tableName.getCatalogName().equals(likeTableName.getCatalogName())) { throw new SemanticException(NOT_SUPPORTED, statement, "LIKE table across catalogs is not supported"); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateViewTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateViewTask.java index 865010f4cc184..495b8368b006f 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/CreateViewTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/CreateViewTask.java @@ -80,7 +80,7 @@ public String explain(CreateView statement, List parameters) @Override public ListenableFuture execute(CreateView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName(), metadata); accessControl.checkCanCreateView(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), name); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropColumnTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropColumnTask.java index 1ced892f6e01f..fa7e418f940f3 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropColumnTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropColumnTask.java @@ -48,7 +48,7 @@ public String getName() @Override public ListenableFuture execute(DropColumn statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable(), metadata); Optional tableHandleOptional = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandleOptional.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropConstraintTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropConstraintTask.java index 4e0986d93a9df..562e4ceac1c74 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropConstraintTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropConstraintTask.java @@ -49,7 +49,7 @@ public String getName() @Override public ListenableFuture execute(DropConstraint statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Optional tableHandleOptional = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandleOptional.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropMaterializedViewTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropMaterializedViewTask.java index 2fa0ec25e5107..283a70d86bb99 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropMaterializedViewTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropMaterializedViewTask.java @@ -44,7 +44,7 @@ public String getName() @Override public ListenableFuture execute(DropMaterializedView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName(), metadata); Optional view = metadata.getMetadataResolver(session).getMaterializedView(name); if (!view.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropSchemaTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropSchemaTask.java index ff5084e7933d6..7df311897e67d 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropSchemaTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropSchemaTask.java @@ -55,7 +55,7 @@ public ListenableFuture execute(DropSchema statement, TransactionManager tran throw new PrestoException(NOT_SUPPORTED, "CASCADE is not yet supported for DROP SCHEMA"); } - CatalogSchemaName schema = createCatalogSchemaName(session, statement, Optional.of(statement.getSchemaName())); + CatalogSchemaName schema = createCatalogSchemaName(session, statement, Optional.of(statement.getSchemaName()), metadata); if (!metadata.getMetadataResolver(session).schemaExists(schema)) { if (!statement.isExists()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropTableTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropTableTask.java index e2931ab899559..463cd226e854a 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropTableTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropTableTask.java @@ -46,7 +46,7 @@ public String getName() @Override public ListenableFuture execute(DropTable statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/DropViewTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/DropViewTask.java index 2e3d1099caf5b..65c64aa88349e 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/DropViewTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/DropViewTask.java @@ -44,7 +44,7 @@ public String getName() @Override public ListenableFuture execute(DropView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName()); + QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName(), metadata); Optional view = metadata.getMetadataResolver(session).getView(name); if (!view.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/GrantTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/GrantTask.java index 084161aa75c23..952f875bb6075 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/GrantTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/GrantTask.java @@ -50,7 +50,7 @@ public String getName() @Override public ListenableFuture execute(Grant statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { throw new SemanticException(MISSING_TABLE, statement, "Table '%s' does not exist", tableName); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameColumnTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameColumnTask.java index 8c63ab92f96cf..a8b48a1fb7239 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameColumnTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameColumnTask.java @@ -50,7 +50,7 @@ public String getName() @Override public ListenableFuture execute(RenameColumn statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTable(), metadata); Optional tableHandleOptional = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandleOptional.isPresent()) { if (!statement.isTableExists()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameSchemaTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameSchemaTask.java index 3ec6ee641e694..c105a452bf1ea 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameSchemaTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameSchemaTask.java @@ -45,7 +45,7 @@ public String getName() @Override public ListenableFuture execute(RenameSchema statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - CatalogSchemaName source = createCatalogSchemaName(session, statement, Optional.of(statement.getSource())); + CatalogSchemaName source = createCatalogSchemaName(session, statement, Optional.of(statement.getSource()), metadata); CatalogSchemaName target = new CatalogSchemaName(source.getCatalogName(), statement.getTarget().getValue()); MetadataResolver metadataResolver = metadata.getMetadataResolver(session); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameTableTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameTableTask.java index 1fa57877f0cc8..b05f7950063b2 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameTableTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameTableTask.java @@ -49,7 +49,7 @@ public String getName() @Override public ListenableFuture execute(RenameTable statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getSource()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getSource(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { if (!statement.isExists()) { @@ -66,7 +66,7 @@ public ListenableFuture execute(RenameTable statement, TransactionManager tra return immediateFuture(null); } - QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget()); + QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget(), metadata); getConnectorIdOrThrow(session, metadata, target.getCatalogName(), statement, targetTableCatalogError); if (metadata.getMetadataResolver(session).getTableHandle(target).isPresent()) { throw new SemanticException(TABLE_ALREADY_EXISTS, statement, "Target table '%s' already exists", target); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameViewTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameViewTask.java index b105f9a936ced..3408adc7b8ca4 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/RenameViewTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/RenameViewTask.java @@ -46,7 +46,7 @@ public String getName() public ListenableFuture execute(RenameView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getSource()); + QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getSource(), metadata); Optional view = metadata.getMetadataResolver(session).getView(viewName); if (!view.isPresent()) { @@ -56,7 +56,7 @@ public ListenableFuture execute(RenameView statement, TransactionManager tran return immediateFuture(null); } - QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget()); + QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget(), metadata); if (!metadata.getCatalogHandle(session, target.getCatalogName()).isPresent()) { throw new SemanticException(MISSING_CATALOG, statement, "Target catalog '%s' does not exist", target.getCatalogName()); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/RevokeTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/RevokeTask.java index 601f7cd2a0afe..651b781235708 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/RevokeTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/RevokeTask.java @@ -50,7 +50,7 @@ public String getName() @Override public ListenableFuture execute(Revoke statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(tableName); if (!tableHandle.isPresent()) { throw new SemanticException(MISSING_TABLE, statement, "Table '%s' does not exist", tableName); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/SetPropertiesTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/SetPropertiesTask.java index 4793f957f964d..3c5e47b887b04 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/SetPropertiesTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/SetPropertiesTask.java @@ -51,7 +51,7 @@ public String getName() @Override public ListenableFuture execute(SetProperties statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { - QualifiedObjectName tableName = MetadataUtil.createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = MetadataUtil.createQualifiedObjectName(session, statement, statement.getTableName(), metadata); Map sqlProperties = mapFromProperties(statement.getProperties()); if (statement.getType() == TABLE) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/TruncateTableTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/TruncateTableTask.java index 34df5768bebf1..9136084915565 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/TruncateTableTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/TruncateTableTask.java @@ -47,7 +47,7 @@ public String getName() public ListenableFuture execute(TruncateTable statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector, String query) { MetadataResolver metadataResolver = metadata.getMetadataResolver(session); - QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName(), metadata); if (metadataResolver.isMaterializedView(tableName)) { throw new SemanticException(NOT_SUPPORTED, statement, "Cannot truncate a materialized view"); diff --git a/presto-main-base/src/main/java/com/facebook/presto/execution/UseTask.java b/presto-main-base/src/main/java/com/facebook/presto/execution/UseTask.java index c1ba8bc5991e1..86fe1eebbfb3b 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/execution/UseTask.java +++ b/presto-main-base/src/main/java/com/facebook/presto/execution/UseTask.java @@ -92,7 +92,9 @@ private void checkAndSetSchema(Use statement, Metadata metadata, QueryStateMachi String catalog = statement.getCatalog() .map(Identifier::getValueLowerCase) .orElseGet(() -> session.getCatalog().map(String::toLowerCase).get()); - String schema = statement.getSchema().getValueLowerCase(); + + Identifier schemaIdentifier = statement.getSchema(); + String schema = metadata.normalizeIdentifier(session, catalog, schemaIdentifier.getValue()); if (!metadata.getMetadataResolver(session).schemaExists(new CatalogSchemaName(catalog, schema))) { throw new SemanticException(MISSING_SCHEMA, format("Schema does not exist: %s.%s", catalog, schema)); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java index 63b8782214e1a..9c68582cf9042 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java @@ -654,4 +654,10 @@ public void addConstraint(Session session, TableHandle tableHandle, TableConstra { delegate.addConstraint(session, tableHandle, tableConstraint); } + + @Override + public String normalizeIdentifier(Session session, String catalogName, String identifier) + { + return delegate.normalizeIdentifier(session, catalogName, identifier); + } } diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java index a316277f8a96d..52a9948dfa0e1 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java @@ -540,4 +540,6 @@ default boolean isPushdownSupportedForFilter(Session session, TableHandle tableH { return false; } + + String normalizeIdentifier(Session session, String catalogName, String identifier); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java index 9e1f0bcb41651..aae8ddebf04a7 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java @@ -99,7 +99,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; @@ -110,6 +109,7 @@ import static com.facebook.airlift.concurrent.MoreFutures.toListenableFuture; import static com.facebook.presto.SystemSessionProperties.isIgnoreStatsCalculatorFailures; +import static com.facebook.presto.common.RuntimeMetricName.GET_IDENTIFIER_NORMALIZATION_TIME_NANOS; import static com.facebook.presto.common.RuntimeMetricName.GET_LAYOUT_TIME_NANOS; import static com.facebook.presto.common.RuntimeMetricName.GET_MATERIALIZED_VIEW_STATUS_TIME_NANOS; import static com.facebook.presto.common.RuntimeUnit.NANO; @@ -316,7 +316,7 @@ public List listSchemaNames(Session session, String catalogName) for (ConnectorId connectorId : catalogMetadata.listConnectorIds()) { ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); metadata.listSchemaNames(connectorSession).stream() - .map(schema -> schema.toLowerCase(Locale.ENGLISH)) + .map(schema -> normalizeIdentifier(session, connectorId.getCatalogName(), schema)) .forEach(schemaNames::add); } } @@ -349,7 +349,7 @@ public Optional getTableHandleForStatisticsCollection(Session sessi ConnectorId connectorId = catalogMetadata.getConnectorId(session, table); ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); - ConnectorTableHandle tableHandle = metadata.getTableHandleForStatisticsCollection(session.toConnectorSession(connectorId), toSchemaTableName(table), analyzeProperties); + ConnectorTableHandle tableHandle = metadata.getTableHandleForStatisticsCollection(session.toConnectorSession(connectorId), toSchemaTableName(table.getSchemaName(), table.getObjectName()), analyzeProperties); if (tableHandle != null) { return Optional.of(new TableHandle( connectorId, @@ -381,7 +381,7 @@ public Optional getSystemTable(Session session, QualifiedObjectName ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); - return metadata.getSystemTable(session.toConnectorSession(connectorId), toSchemaTableName(tableName)); + return metadata.getSystemTable(session.toConnectorSession(connectorId), toSchemaTableName(tableName.getSchemaName(), tableName.getObjectName())); } return Optional.empty(); } @@ -564,13 +564,14 @@ public List listTables(Session session, QualifiedTablePrefi Set tables = new LinkedHashSet<>(); if (catalog.isPresent()) { CatalogMetadata catalogMetadata = catalog.get(); - for (ConnectorId connectorId : catalogMetadata.listConnectorIds()) { ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); ConnectorSession connectorSession = session.toConnectorSession(connectorId); metadata.listTables(connectorSession, prefix.getSchemaName()).stream() .map(convertFromSchemaTableName(prefix.getCatalogName())) - .filter(prefix::matches) + .filter(name -> prefix.matches(new QualifiedObjectName(name.getCatalogName(), + normalizeIdentifier(session, connectorId.getCatalogName(), name.getSchemaName()), + normalizeIdentifier(session, connectorId.getCatalogName(), name.getObjectName())))) .forEach(tables::add); } } @@ -694,7 +695,9 @@ public void renameTable(Session session, TableHandle tableHandle, QualifiedObjec } ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.renameTable(session.toConnectorSession(connectorId), tableHandle.getConnectorHandle(), toSchemaTableName(newTableName)); + + metadata.renameTable(session.toConnectorSession(connectorId), tableHandle.getConnectorHandle(), + toSchemaTableName(newTableName.getSchemaName(), newTableName.getObjectName())); } @Override @@ -982,7 +985,9 @@ public List listViews(Session session, QualifiedTablePrefix ConnectorSession connectorSession = session.toConnectorSession(connectorId); metadata.listViews(connectorSession, prefix.getSchemaName()).stream() .map(convertFromSchemaTableName(prefix.getCatalogName())) - .filter(prefix::matches) + .filter(name -> prefix.matches(new QualifiedObjectName(name.getCatalogName(), + normalizeIdentifier(session, connectorId.getCatalogName(), name.getSchemaName()), + normalizeIdentifier(session, connectorId.getCatalogName(), name.getObjectName())))) .forEach(views::add); } } @@ -1033,7 +1038,9 @@ public void renameView(Session session, QualifiedObjectName source, QualifiedObj ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.renameView(session.toConnectorSession(connectorId), toSchemaTableName(source), toSchemaTableName(target)); + metadata.renameView(session.toConnectorSession(connectorId), + toSchemaTableName(source.getSchemaName(), source.getObjectName()), + toSchemaTableName(target.getSchemaName(), target.getObjectName())); } @Override @@ -1043,7 +1050,7 @@ public void dropView(Session session, QualifiedObjectName viewName) ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.dropView(session.toConnectorSession(connectorId), toSchemaTableName(viewName)); + metadata.dropView(session.toConnectorSession(connectorId), toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName())); } @Override @@ -1063,7 +1070,7 @@ public void dropMaterializedView(Session session, QualifiedObjectName viewName) ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.dropMaterializedView(session.toConnectorSession(connectorId), toSchemaTableName(viewName)); + metadata.dropMaterializedView(session.toConnectorSession(connectorId), toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName())); } private MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName materializedViewName, TupleDomain baseQueryDomain) @@ -1075,7 +1082,9 @@ private MaterializedViewStatus getMaterializedViewStatus(Session session, Qualif return session.getRuntimeStats().recordWallTime( GET_MATERIALIZED_VIEW_STATUS_TIME_NANOS, - () -> metadata.getMaterializedViewStatus(session.toConnectorSession(connectorId), toSchemaTableName(materializedViewName), baseQueryDomain)); + () -> metadata.getMaterializedViewStatus(session.toConnectorSession(connectorId), + toSchemaTableName(materializedViewName.getSchemaName(), materializedViewName.getObjectName()), + baseQueryDomain)); } @Override @@ -1106,7 +1115,8 @@ public List getReferencedMaterializedViews(Session session, if (catalog.isPresent()) { ConnectorMetadata metadata = catalog.get().getMetadata(); ConnectorSession connectorSession = session.toConnectorSession(catalog.get().getConnectorId()); - Optional> materializedViews = metadata.getReferencedMaterializedViews(connectorSession, toSchemaTableName(tableName)); + + Optional> materializedViews = metadata.getReferencedMaterializedViews(connectorSession, toSchemaTableName(tableName.getSchemaName(), tableName.getObjectName())); if (materializedViews.isPresent()) { return materializedViews.get().stream().map(convertFromSchemaTableName(tableName.getCatalogName())).collect(toImmutableList()); } @@ -1227,7 +1237,7 @@ public void grantTablePrivileges(Session session, QualifiedObjectName tableName, ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.grantTablePrivileges(session.toConnectorSession(connectorId), toSchemaTableName(tableName), privileges, grantee, grantOption); + metadata.grantTablePrivileges(session.toConnectorSession(connectorId), toSchemaTableName(tableName.getSchemaName(), tableName.getObjectName()), privileges, grantee, grantOption); } @Override @@ -1237,7 +1247,7 @@ public void revokeTablePrivileges(Session session, QualifiedObjectName tableName ConnectorId connectorId = catalogMetadata.getConnectorId(); ConnectorMetadata metadata = catalogMetadata.getMetadata(); - metadata.revokeTablePrivileges(session.toConnectorSession(connectorId), toSchemaTableName(tableName), privileges, grantee, grantOption); + metadata.revokeTablePrivileges(session.toConnectorSession(connectorId), toSchemaTableName(tableName.getSchemaName(), tableName.getObjectName()), privileges, grantee, grantOption); } @Override @@ -1423,8 +1433,8 @@ public Optional getView(QualifiedObjectName viewName) Map views = metadata.getViews( session.toConnectorSession(connectorId), - toSchemaTableName(viewName).toSchemaTablePrefix()); - ConnectorViewDefinition view = views.get(toSchemaTableName(viewName)); + toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName()).toSchemaTablePrefix()); + ConnectorViewDefinition view = views.get(toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName())); if (view != null) { ViewDefinition definition = deserializeView(view.getViewData()); if (view.getOwner().isPresent() && !definition.isRunAsInvoker()) { @@ -1445,7 +1455,7 @@ public Optional getMaterializedView(QualifiedObjectN ConnectorId connectorId = catalogMetadata.getConnectorId(session, viewName); ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); - return metadata.getMaterializedView(session.toConnectorSession(connectorId), toSchemaTableName(viewName)); + return metadata.getMaterializedView(session.toConnectorSession(connectorId), toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName())); } return Optional.empty(); } @@ -1496,6 +1506,21 @@ public void addConstraint(Session session, TableHandle tableHandle, TableConstra metadata.addConstraint(session.toConnectorSession(connectorId), tableHandle.getConnectorHandle(), tableConstraint); } + @Override + public String normalizeIdentifier(Session session, String catalogName, String identifier) + { + long startTime = System.nanoTime(); + String normalizedString = identifier.toLowerCase(ENGLISH); + Optional catalogMetadata = getOptionalCatalogMetadata(session, transactionManager, catalogName); + if (catalogMetadata.isPresent()) { + ConnectorId connectorId = catalogMetadata.get().getConnectorId(); + ConnectorMetadata metadata = catalogMetadata.get().getMetadataFor(connectorId); + normalizedString = metadata.normalizeIdentifier(session.toConnectorSession(connectorId), identifier); + } + session.getRuntimeStats().addMetricValue(GET_IDENTIFIER_NORMALIZATION_TIME_NANOS, NANO, System.nanoTime() - startTime); + return normalizedString; + } + private ViewDefinition deserializeView(String data) { try { diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataUtil.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataUtil.java index d33ed25c2cbf8..0ccc75dada260 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataUtil.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataUtil.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Optional; +import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.INFORMATION_SCHEMA; import static com.facebook.presto.spi.StandardErrorCode.SYNTAX_ERROR; import static com.facebook.presto.spi.security.PrincipalType.ROLE; import static com.facebook.presto.spi.security.PrincipalType.USER; @@ -60,34 +61,22 @@ private MetadataUtil() {} public static final String likeTableCatalogError = "LIKE table catalog '%s' does not exist"; public static final String catalogError = "Catalog %s does not exist"; public static final String targetTableCatalogError = "Target catalog '%s' does not exist"; - - public static void checkTableName(String catalogName, Optional schemaName, Optional tableName) - { - checkCatalogName(catalogName); - schemaName.ifPresent(name -> checkLowerCase(name, "schemaName")); - tableName.ifPresent(name -> checkLowerCase(name, "tableName")); - - checkArgument(schemaName.isPresent() || !tableName.isPresent(), "tableName specified but schemaName is missing"); - } - public static String checkCatalogName(String catalogName) { return checkLowerCase(catalogName, "catalogName"); } - public static String checkSchemaName(String schemaName) - { - return checkLowerCase(schemaName, "schemaName"); - } - - public static String checkTableName(String tableName) + public static SchemaTableName toSchemaTableName(QualifiedObjectName qualifiedObjectName) { - return checkLowerCase(tableName, "tableName"); + return new SchemaTableName(qualifiedObjectName.getSchemaName(), qualifiedObjectName.getObjectName()); } - public static SchemaTableName toSchemaTableName(QualifiedObjectName qualifiedObjectName) + public static SchemaTableName toSchemaTableName(String schemaName, String tableName) { - return new SchemaTableName(qualifiedObjectName.getSchemaName(), qualifiedObjectName.getObjectName()); + if (schemaName.equalsIgnoreCase(INFORMATION_SCHEMA)) { + return new SchemaTableName(schemaName.toLowerCase(ENGLISH), tableName.toLowerCase(ENGLISH)); + } + return new SchemaTableName(schemaName, tableName); } public static ConnectorId getConnectorIdOrThrow(Session session, Metadata metadata, String catalogName) @@ -132,20 +121,23 @@ public static String createCatalogName(Session session, Node node) return sessionCatalog.get(); } - public static CatalogSchemaName createCatalogSchemaName(Session session, Node node, Optional schema) + public static CatalogSchemaName createCatalogSchemaName(Session session, Node node, Optional schema, Metadata metadata) { String catalogName = session.getCatalog().orElse(null); String schemaName = session.getSchema().orElse(null); if (schema.isPresent()) { - List parts = schema.get().getParts(); + List parts = schema.get().getOriginalParts(); if (parts.size() > 2) { throw new SemanticException(INVALID_SCHEMA_NAME, node, "Too many parts in schema name: %s", schema.get()); } if (parts.size() == 2) { catalogName = parts.get(0); } - schemaName = schema.get().getSuffix(); + if (catalogName == null) { + throw new SemanticException(CATALOG_NOT_SPECIFIED, node, "Catalog must be specified when session catalog is not set"); + } + schemaName = metadata.normalizeIdentifier(session, catalogName, schema.get().getOriginalSuffix()); } if (catalogName == null) { @@ -158,21 +150,25 @@ public static CatalogSchemaName createCatalogSchemaName(Session session, Node no return new CatalogSchemaName(catalogName, schemaName); } - public static QualifiedObjectName createQualifiedObjectName(Session session, Node node, QualifiedName name) + public static QualifiedObjectName createQualifiedObjectName(Session session, Node node, QualifiedName name, Metadata metadata) { requireNonNull(session, "session is null"); requireNonNull(name, "name is null"); - if (name.getParts().size() > 3) { + if (name.getOriginalParts().size() > 3) { throw new PrestoException(SYNTAX_ERROR, format("Too many dots in table name: %s", name)); } - List parts = Lists.reverse(name.getParts()); + List parts = Lists.reverse(name.getOriginalParts()); String objectName = parts.get(0); String schemaName = (parts.size() > 1) ? parts.get(1) : session.getSchema().orElseThrow(() -> new SemanticException(SCHEMA_NOT_SPECIFIED, node, "Schema must be specified when session schema is not set")); String catalogName = (parts.size() > 2) ? parts.get(2) : session.getCatalog().orElseThrow(() -> new SemanticException(CATALOG_NOT_SPECIFIED, node, "Catalog must be specified when session catalog is not set")); + catalogName = catalogName.toLowerCase(ENGLISH); + schemaName = metadata.normalizeIdentifier(session, catalogName, schemaName); + objectName = metadata.normalizeIdentifier(session, catalogName, objectName); + return new QualifiedObjectName(catalogName, schemaName, objectName); } @@ -197,6 +193,7 @@ public static Optional getOptionalTableHandle(Session session, Tran ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); ConnectorTableHandle tableHandle; + tableHandle = tableVersion .map(expression -> metadata.getTableHandle(session.toConnectorSession(connectorId), toSchemaTableName(table), Optional.of(expression))) .orElseGet(() -> metadata.getTableHandle(session.toConnectorSession(connectorId), toSchemaTableName(table))); diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/QualifiedTablePrefix.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/QualifiedTablePrefix.java index c473aff3ae3eb..a2ad59983f016 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/QualifiedTablePrefix.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/QualifiedTablePrefix.java @@ -27,8 +27,6 @@ import java.util.Optional; import static com.facebook.presto.metadata.MetadataUtil.checkCatalogName; -import static com.facebook.presto.metadata.MetadataUtil.checkSchemaName; -import static com.facebook.presto.metadata.MetadataUtil.checkTableName; @Immutable @ThriftStruct @@ -48,15 +46,15 @@ public QualifiedTablePrefix(String catalogName) public QualifiedTablePrefix(String catalogName, String schemaName) { this.catalogName = checkCatalogName(catalogName); - this.schemaName = Optional.of(checkSchemaName(schemaName)); + this.schemaName = Optional.of(schemaName); this.tableName = Optional.empty(); } public QualifiedTablePrefix(String catalogName, String schemaName, String tableName) { this.catalogName = checkCatalogName(catalogName); - this.schemaName = Optional.of(checkSchemaName(schemaName)); - this.tableName = Optional.of(checkTableName(tableName)); + this.schemaName = Optional.of(schemaName); + this.tableName = Optional.of(tableName); } @JsonCreator @@ -66,7 +64,7 @@ public QualifiedTablePrefix( @JsonProperty("schemaName") Optional schemaName, @JsonProperty("tableName") Optional tableName) { - checkTableName(catalogName, schemaName, tableName); + checkCatalogName(catalogName); this.catalogName = catalogName; this.schemaName = schemaName; this.tableName = tableName; diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewColumnMappingExtractor.java b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewColumnMappingExtractor.java index f9cefc11117ea..80a406edf6bf4 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewColumnMappingExtractor.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewColumnMappingExtractor.java @@ -14,6 +14,7 @@ package com.facebook.presto.sql.analyzer; import com.facebook.presto.Session; +import com.facebook.presto.metadata.Metadata; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.sql.tree.ComparisonExpression; import com.facebook.presto.sql.tree.CreateMaterializedView; @@ -48,6 +49,7 @@ public class MaterializedViewColumnMappingExtractor { private final Analysis analysis; private final Session session; + private final Metadata metadata; /** * We create a undirected graph where each node corresponds to a base table column. @@ -79,10 +81,11 @@ public class MaterializedViewColumnMappingExtractor */ private List baseTablesOnOuterJoinSide; - public MaterializedViewColumnMappingExtractor(Analysis analysis, Session session) + public MaterializedViewColumnMappingExtractor(Analysis analysis, Session session, Metadata metadata) { this.analysis = requireNonNull(analysis, "analysis is null"); this.session = requireNonNull(session, "session is null"); + this.metadata = requireNonNull(metadata, "metadata is null"); this.mappedBaseColumns = new HashMap<>(); this.directMappedBaseColumns = new HashMap<>(); this.baseTablesOnOuterJoinSide = new ArrayList<>(); @@ -166,7 +169,7 @@ protected Void visitTable(Table node, MaterializedViewPlanValidatorContext conte super.visitTable(node, context); if (context.isWithinOuterJoin()) { - baseTablesOnOuterJoinSide.add(toSchemaTableName(createQualifiedObjectName(session, node, node.getName()))); + baseTablesOnOuterJoinSide.add(toSchemaTableName(createQualifiedObjectName(session, node, node.getName(), metadata))); } return null; diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewQueryOptimizer.java b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewQueryOptimizer.java index edeb6898a8838..ede26ac0b319d 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewQueryOptimizer.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MaterializedViewQueryOptimizer.java @@ -344,7 +344,7 @@ private QuerySpecification rewriteQuerySpecificationIfCompatible(QuerySpecificat List referencedMaterializedViews = metadata.getReferencedMaterializedViews( session, - createQualifiedObjectName(session, baseTable, baseTable.getName())); + createQualifiedObjectName(session, baseTable, baseTable.getName(), metadata)); // TODO: Select the most compatible and efficient materialized view for query rewrite optimization https://github.com/prestodb/presto/issues/16431 // TODO: Refactor query optimization code https://github.com/prestodb/presto/issues/16759 @@ -815,7 +815,7 @@ private Expression coerceIfNecessary(Expression original, Expression rewritten) private Scope extractScope(Table table, QuerySpecification node, Expression whereClause) { - QualifiedObjectName baseTableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName baseTableName = createQualifiedObjectName(session, table, table.getName(), metadata); Optional tableHandle = metadata.getMetadataResolver(session).getTableHandle(baseTableName); if (!tableHandle.isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MetadataExtractor.java b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MetadataExtractor.java index 3eda7c8204334..f5a4b8afcf7b5 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MetadataExtractor.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/MetadataExtractor.java @@ -189,7 +189,7 @@ public Visitor(Session session) @Override protected Void visitTable(Table table, MetadataExtractorContext context) { - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); if (tableName.getObjectName().isEmpty()) { throw new SemanticException(MISSING_TABLE, table, "Table name is empty"); } @@ -207,7 +207,7 @@ protected Void visitTable(Table table, MetadataExtractorContext context) @Override protected Void visitInsert(Insert insert, MetadataExtractorContext context) { - QualifiedObjectName tableName = createQualifiedObjectName(session, insert, insert.getTarget()); + QualifiedObjectName tableName = createQualifiedObjectName(session, insert, insert.getTarget(), metadata); if (tableName.getObjectName().isEmpty()) { throw new SemanticException(MISSING_TABLE, insert, "Table name is empty"); } @@ -224,7 +224,7 @@ protected Void visitInsert(Insert insert, MetadataExtractorContext context) protected Void visitDelete(Delete node, MetadataExtractorContext context) { Table table = node.getTable(); - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); if (tableName.getObjectName().isEmpty()) { throw new SemanticException(MISSING_TABLE, node, "Table name is empty"); } @@ -239,7 +239,7 @@ protected Void visitDelete(Delete node, MetadataExtractorContext context) @Override protected Void visitAnalyze(Analyze node, MetadataExtractorContext context) { - QualifiedObjectName tableName = createQualifiedObjectName(session, node, node.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, node, node.getTableName(), metadata); if (tableName.getObjectName().isEmpty()) { throw new SemanticException(MISSING_TABLE, node, "Table name is empty"); } diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/PredicateStitcher.java b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/PredicateStitcher.java index a39c808d91238..47fd5046b57ef 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/PredicateStitcher.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/PredicateStitcher.java @@ -14,6 +14,7 @@ package com.facebook.presto.sql.analyzer; import com.facebook.presto.Session; +import com.facebook.presto.metadata.Metadata; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.sql.tree.AliasedRelation; import com.facebook.presto.sql.tree.AllColumns; @@ -60,11 +61,13 @@ public class PredicateStitcher { private final Map predicates; private final Session session; + private final Metadata metadata; - public PredicateStitcher(Session session, Map predicates) + public PredicateStitcher(Session session, Map predicates, Metadata metadata) { this.session = requireNonNull(session, "session is null"); this.predicates = requireNonNull(predicates, "predicates is null"); + this.metadata = requireNonNull(metadata, "metadata is null"); } @Override @@ -185,7 +188,7 @@ protected Node visitAliasedRelation(AliasedRelation node, PredicateStitcherConte @Override protected Node visitTable(Table table, PredicateStitcherContext context) { - SchemaTableName schemaTableName = toSchemaTableName(createQualifiedObjectName(session, table, table.getName())); + SchemaTableName schemaTableName = toSchemaTableName(createQualifiedObjectName(session, table, table.getName(), metadata)); if (!predicates.containsKey(schemaTableName)) { return table; } diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java index c92edc1cdc57d..04f87c20bb89c 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java @@ -398,7 +398,7 @@ protected Scope visitUse(Use node, Optional scope) @Override protected Scope visitInsert(Insert insert, Optional scope) { - QualifiedObjectName targetTable = createQualifiedObjectName(session, insert, insert.getTarget()); + QualifiedObjectName targetTable = createQualifiedObjectName(session, insert, insert.getTarget(), metadata); MetadataHandle metadataHandle = analysis.getMetadataHandle(); if (getViewDefinition(session, metadataResolver, metadataHandle, targetTable).isPresent()) { @@ -588,7 +588,7 @@ private void checkTypesMatchForNestedStructs( protected Scope visitDelete(Delete node, Optional scope) { Table table = node.getTable(); - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); MetadataHandle metadataHandle = analysis.getMetadataHandle(); if (getViewDefinition(session, metadataResolver, metadataHandle, tableName).isPresent()) { @@ -633,7 +633,7 @@ protected Scope visitDelete(Delete node, Optional scope) protected Scope visitAnalyze(Analyze node, Optional scope) { analysis.setUpdateType("ANALYZE"); - QualifiedObjectName tableName = createQualifiedObjectName(session, node, node.getTableName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, node, node.getTableName(), metadata); MetadataHandle metadataHandle = analysis.getMetadataHandle(); // verify the target table exists, and it's not a view @@ -670,7 +670,7 @@ protected Scope visitCreateTableAsSelect(CreateTableAsSelect node, Optional scope) { analysis.setUpdateType("CREATE VIEW"); - QualifiedObjectName viewName = createQualifiedObjectName(session, node, node.getName()); + QualifiedObjectName viewName = createQualifiedObjectName(session, node, node.getName(), metadata); // analyze the query that creates the view StatementAnalyzer analyzer = new StatementAnalyzer(analysis, metadata, sqlParser, accessControl, session, warningCollector); @@ -736,7 +736,7 @@ protected Scope visitCreateMaterializedView(CreateMaterializedView node, Optiona { analysis.setUpdateType("CREATE MATERIALIZED VIEW"); - QualifiedObjectName viewName = createQualifiedObjectName(session, node, node.getName()); + QualifiedObjectName viewName = createQualifiedObjectName(session, node, node.getName(), metadata); analysis.setCreateTableDestination(viewName); if (metadataResolver.tableExists(viewName)) { @@ -768,7 +768,7 @@ protected Scope visitRefreshMaterializedView(RefreshMaterializedView node, Optio { analysis.setUpdateType("INSERT"); - QualifiedObjectName viewName = createQualifiedObjectName(session, node.getTarget(), node.getTarget().getName()); + QualifiedObjectName viewName = createQualifiedObjectName(session, node.getTarget(), node.getTarget().getName(), metadata); MaterializedViewDefinition view = getMaterializedViewDefinition(session, metadataResolver, analysis.getMetadataHandle(), viewName) .orElseThrow(() -> new SemanticException(MISSING_MATERIALIZED_VIEW, node, "Materialized view '%s' does not exist", viewName)); @@ -828,14 +828,14 @@ private Optional analyzeBaseTableForRefreshMaterializedView(Table checkState(analysis.getStatement() instanceof RefreshMaterializedView, "Not analyzing RefreshMaterializedView statement"); RefreshMaterializedView refreshMaterializedView = (RefreshMaterializedView) analysis.getStatement(); - QualifiedObjectName viewName = createQualifiedObjectName(session, refreshMaterializedView.getTarget(), refreshMaterializedView.getTarget().getName()); + QualifiedObjectName viewName = createQualifiedObjectName(session, refreshMaterializedView.getTarget(), refreshMaterializedView.getTarget().getName(), metadata); // Use AllowAllAccessControl; otherwise Analyzer will check SELECT permission on the materialized view, which is not necessary. StatementAnalyzer viewAnalyzer = new StatementAnalyzer(analysis, metadata, sqlParser, new AllowAllAccessControl(), session, warningCollector); Scope viewScope = viewAnalyzer.analyze(refreshMaterializedView.getTarget(), scope); Map tablePredicates = extractTablePredicates(viewName, refreshMaterializedView.getWhere(), viewScope, metadata, session); - SchemaTableName baseTableName = toSchemaTableName(createQualifiedObjectName(session, baseTable, baseTable.getName())); + SchemaTableName baseTableName = toSchemaTableName(createQualifiedObjectName(session, baseTable, baseTable.getName(), metadata)); if (tablePredicates.containsKey(baseTableName)) { Query tableSubquery = buildQueryWithPredicate(baseTable, tablePredicates.get(baseTableName)); analysis.registerNamedQuery(baseTable, tableSubquery, true); @@ -1159,7 +1159,7 @@ private void validateColumnAliases(List columnAliases, int sourceCol private void validateBaseTables(List baseTables, Node node) { for (Table baseTable : baseTables) { - QualifiedObjectName baseName = createQualifiedObjectName(session, baseTable, baseTable.getName()); + QualifiedObjectName baseName = createQualifiedObjectName(session, baseTable, baseTable.getName(), metadata); Optional optionalMaterializedView = getMaterializedViewDefinition(session, metadataResolver, analysis.getMetadataHandle(), baseName); if (optionalMaterializedView.isPresent()) { @@ -1328,7 +1328,7 @@ protected Scope visitTable(Table table, Optional scope) } } - QualifiedObjectName name = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName name = createQualifiedObjectName(session, table, table.getName(), metadata); if (name.getObjectName().isEmpty()) { throw new SemanticException(MISSING_TABLE, table, "Table name is empty"); } @@ -1483,7 +1483,7 @@ private Optional processTableVersion(Table table, QualifiedObjectNa private Scope getScopeFromTable(Table table, Optional scope) { - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); TableColumnMetadata tableColumnsMetadata = getTableColumnsMetadata(session, metadataResolver, analysis.getMetadataHandle(), tableName); // TODO: discover columns lazily based on where they are needed (to support connectors that can't enumerate all tables) @@ -1512,7 +1512,7 @@ private Scope processView(Table table, Optional scope, QualifiedObjectNam Statement statement = analysis.getStatement(); if (statement instanceof CreateView) { CreateView viewStatement = (CreateView) statement; - QualifiedObjectName viewNameFromStatement = createQualifiedObjectName(session, viewStatement, viewStatement.getName()); + QualifiedObjectName viewNameFromStatement = createQualifiedObjectName(session, viewStatement, viewStatement.getName(), metadata); if (viewStatement.isReplace() && viewNameFromStatement.equals(name)) { throw new SemanticException(VIEW_IS_RECURSIVE, table, "Statement would create a recursive view"); } @@ -1614,7 +1614,7 @@ else if (materializedViewStatus.isPartiallyMaterialized()) { baseTablePredicates = generateBaseTablePredicates(materializedViewStatus.getPartitionsFromBaseTables(), metadata); } - Query predicateStitchedQuery = (Query) new PredicateStitcher(session, baseTablePredicates).process(createSqlStatement, new PredicateStitcherContext()); + Query predicateStitchedQuery = (Query) new PredicateStitcher(session, baseTablePredicates, metadata).process(createSqlStatement, new PredicateStitcherContext()); // TODO: consider materialized view predicates https://github.com/prestodb/presto/issues/16034 QuerySpecification materializedViewQuerySpecification = new QuerySpecification( @@ -2093,7 +2093,7 @@ private String createWarningMessage(Node node, String description) protected Scope visitUpdate(Update update, Optional scope) { Table table = update.getTable(); - QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); + QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName(), metadata); MetadataHandle metadataHandle = analysis.getMetadataHandle(); if (getViewDefinition(session, metadataResolver, metadataHandle, tableName).isPresent()) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/planner/RelationPlanner.java b/presto-main-base/src/main/java/com/facebook/presto/sql/planner/RelationPlanner.java index a32672c70674b..b172bcd558421 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/planner/RelationPlanner.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/planner/RelationPlanner.java @@ -183,7 +183,7 @@ protected RelationPlan visitTable(Table node, SqlPlannerContext context) if (namedQuery != null) { String cteName = node.getName().toString(); if (namedQuery.isFromView()) { - cteName = createQualifiedObjectName(session, node, node.getName()).toString(); + cteName = createQualifiedObjectName(session, node, node.getName(), metadata).toString(); } RelationPlan subPlan = process(namedQuery.getQuery(), context); if (getCteMaterializationStrategy(session).equals(NONE)) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowQueriesRewrite.java b/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowQueriesRewrite.java index ea58bb3ec537c..93feddd051fa8 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowQueriesRewrite.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowQueriesRewrite.java @@ -225,7 +225,7 @@ protected Node visitExplain(Explain node, Void context) @Override protected Node visitShowTables(ShowTables showTables, Void context) { - CatalogSchemaName schema = createCatalogSchemaName(session, showTables, showTables.getSchema()); + CatalogSchemaName schema = createCatalogSchemaName(session, showTables, showTables.getSchema(), metadata); accessControl.checkCanShowTablesMetadata(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), schema); @@ -263,7 +263,7 @@ protected Node visitShowGrants(ShowGrants showGrants, Void context) Optional tableName = showGrants.getTableName(); if (tableName.isPresent()) { - QualifiedObjectName qualifiedTableName = createQualifiedObjectName(session, showGrants, tableName.get()); + QualifiedObjectName qualifiedTableName = createQualifiedObjectName(session, showGrants, tableName.get(), metadata); if (!metadataResolver.getView(qualifiedTableName).isPresent() && !metadataResolver.getTableHandle(qualifiedTableName).isPresent()) { @@ -403,7 +403,7 @@ protected Node visitShowCatalogs(ShowCatalogs node, Void context) @Override protected Node visitShowColumns(ShowColumns showColumns, Void context) { - QualifiedObjectName tableName = createQualifiedObjectName(session, showColumns, showColumns.getTable()); + QualifiedObjectName tableName = createQualifiedObjectName(session, showColumns, showColumns.getTable(), metadata); if (!metadataResolver.getView(tableName).isPresent() && !metadataResolver.getTableHandle(tableName).isPresent()) { @@ -462,7 +462,7 @@ private static Expression toExpression(Object value) protected Node visitShowCreate(ShowCreate node, Void context) { if (node.getType() == SCHEMA) { - CatalogSchemaName catalogSchemaName = createCatalogSchemaName(session, node, Optional.of(node.getName())); + CatalogSchemaName catalogSchemaName = createCatalogSchemaName(session, node, Optional.of(node.getName()), metadata); if (!metadataResolver.schemaExists(catalogSchemaName)) { throw new SemanticException(MISSING_SCHEMA, node, "Schema '%s' does not exist", catalogSchemaName); } @@ -477,7 +477,7 @@ protected Node visitShowCreate(ShowCreate node, Void context) return singleValueQuery("Create Schema", formatSql(createSchema, Optional.of(parameters)).trim()); } - QualifiedObjectName objectName = createQualifiedObjectName(session, node, node.getName()); + QualifiedObjectName objectName = createQualifiedObjectName(session, node, node.getName(), metadata); Optional viewDefinition = metadataResolver.getView(objectName); Optional materializedViewDefinition = metadataResolver.getMaterializedView(objectName); diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowStatsRewrite.java b/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowStatsRewrite.java index 7d163c68bd3f5..e485217bb40df 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowStatsRewrite.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/rewrite/ShowStatsRewrite.java @@ -255,7 +255,7 @@ private Constraint getConstraint(Plan plan) private TableHandle getTableHandle(ShowStats node, QualifiedName table) { - QualifiedObjectName qualifiedTableName = createQualifiedObjectName(session, node, table); + QualifiedObjectName qualifiedTableName = createQualifiedObjectName(session, node, table, metadata); return metadata.getMetadataResolver(session).getTableHandle(qualifiedTableName) .orElseThrow(() -> new SemanticException(MISSING_TABLE, node, "Table %s not found", table)); } diff --git a/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java b/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java index e29d5a4777415..de337d7f44fb2 100644 --- a/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java +++ b/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java @@ -59,6 +59,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import static java.util.Locale.ENGLISH; public abstract class AbstractMockMetadata implements Metadata @@ -679,4 +680,10 @@ public void addConstraint(Session session, TableHandle tableHandle, TableConstra { throw new UnsupportedOperationException(); } + + @Override + public String normalizeIdentifier(Session session, String catalogName, String identifier) + { + return identifier.toLowerCase(ENGLISH); + } } diff --git a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java index a2e2e487b1d38..8626f65550fc5 100644 --- a/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java +++ b/presto-mysql/src/main/java/com/facebook/presto/plugin/mysql/MySqlClient.java @@ -323,4 +323,10 @@ protected void renameTable(JdbcIdentity identity, String catalogName, SchemaTabl // catalogName parameter to null it will be omitted in the alter table statement. super.renameTable(identity, null, oldTable, newTable); } + + @Override + public String normalizeIdentifier(ConnectorSession session, String identifier) + { + return caseSensitiveNameMatchingEnabled ? identifier : identifier.toLowerCase(ENGLISH); + } } diff --git a/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlIntegrationMixedCaseTest.java b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlIntegrationMixedCaseTest.java new file mode 100644 index 0000000000000..9af93187a1824 --- /dev/null +++ b/presto-mysql/src/test/java/com/facebook/presto/plugin/mysql/TestMySqlIntegrationMixedCaseTest.java @@ -0,0 +1,205 @@ +/* + * Licensed 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 com.facebook.presto.plugin.mysql; + +import com.facebook.presto.Session; +import com.facebook.presto.testing.MaterializedResult; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.testing.mysql.MySqlOptions; +import com.facebook.presto.testing.mysql.TestingMySqlServer; +import com.facebook.presto.tests.AbstractTestQueryFramework; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.tpch.TpchTable; +import io.airlift.units.Duration; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import static com.facebook.presto.common.type.VarcharType.VARCHAR; +import static com.facebook.presto.plugin.mysql.MySqlQueryRunner.createMySqlQueryRunner; +import static com.facebook.presto.testing.TestingSession.testSessionBuilder; +import static com.facebook.presto.testing.assertions.Assert.assertEquals; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +@Test +public class TestMySqlIntegrationMixedCaseTest + extends AbstractTestQueryFramework +{ + private static final MySqlOptions MY_SQL_OPTIONS = MySqlOptions.builder() + .setCommandTimeout(new Duration(90, SECONDS)) + .build(); + + private final TestingMySqlServer mysqlServer; + + public TestMySqlIntegrationMixedCaseTest() + throws Exception + { + this.mysqlServer = new TestingMySqlServer("testuser", "testpass", ImmutableList.of("tpch", "Mixed_Test_Database"), MY_SQL_OPTIONS); + } + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + return createMySqlQueryRunner(mysqlServer, ImmutableMap.of("case-sensitive-name-matching", "true"), TpchTable.getTables()); + } + + @AfterClass(alwaysRun = true) + public final void destroy() + throws IOException + { + mysqlServer.close(); + } + + public void testDescribeTable() + { + // CI tests run on Linux, where MySQL is case-sensitive by default (lower_case_table_names=0), + // treating "orders" and "ORDERS" as different tables. + // Since the test runs with case-sensitive-name-matching=true, ensure "ORDERS" exists if not already present. + try { + execute("CREATE TABLE IF NOT EXISTS tpch.ORDERS AS SELECT * FROM tpch.orders"); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + // we need specific implementation of this tests due to specific Presto<->Mysql varchar length mapping. + MaterializedResult actualColumns = computeActual("DESC ORDERS").toTestTypes(); + + MaterializedResult expectedColumns = MaterializedResult.resultBuilder(getQueryRunner().getDefaultSession(), VARCHAR, VARCHAR, VARCHAR, VARCHAR) + .row("orderkey", "bigint", "", "") + .row("custkey", "bigint", "", "") + .row("orderstatus", "varchar(255)", "", "") + .row("totalprice", "double", "", "") + .row("orderdate", "date", "", "") + .row("orderpriority", "varchar(255)", "", "") + .row("clerk", "varchar(255)", "", "") + .row("shippriority", "integer", "", "") + .row("comment", "varchar(255)", "", "") + .build(); + assertEquals(actualColumns, expectedColumns); + } + + @Test + public void testCreateTable() + { + Session session = testSessionBuilder() + .setCatalog("mysql") + .setSchema("Mixed_Test_Database") + .build(); + + getQueryRunner().execute(session, "CREATE TABLE TEST_CREATE(name VARCHAR(50), id int)"); + assertTrue(getQueryRunner().tableExists(session, "TEST_CREATE")); + + getQueryRunner().execute(session, "CREATE TABLE IF NOT EXISTS test_create(name VARCHAR(50), id int)"); + assertTrue(getQueryRunner().tableExists(session, "test_create")); + + assertUpdate(session, "DROP TABLE IF EXISTS TEST_CREATE"); + assertFalse(getQueryRunner().tableExists(session, "TEST_CREATE")); + + assertUpdate(session, "DROP TABLE IF EXISTS test_create"); + assertFalse(getQueryRunner().tableExists(session, "test_create")); + } + + @Test + public void testCreateTableAs() + { + Session session = testSessionBuilder() + .setCatalog("mysql") + .setSchema("Mixed_Test_Database") + .build(); + + getQueryRunner().execute(session, "CREATE TABLE TEST_CREATEAS AS SELECT * FROM tpch.region"); + assertTrue(getQueryRunner().tableExists(session, "TEST_CREATEAS")); + + getQueryRunner().execute(session, "CREATE TABLE IF NOT EXISTS test_createas AS SELECT * FROM tpch.region"); + assertTrue(getQueryRunner().tableExists(session, "test_createas")); + + getQueryRunner().execute(session, "CREATE TABLE TEST_CREATEAS_Join AS SELECT c.custkey, o.orderkey FROM " + + "tpch.customer c INNER JOIN tpch.orders o ON c.custkey = o.custkey WHERE c.mktsegment = 'BUILDING'"); + assertTrue(getQueryRunner().tableExists(session, "TEST_CREATEAS_Join")); + + assertQueryFails("CREATE TABLE Mixed_Test_Database.TEST_CREATEAS_FAIL_Join AS SELECT c.custkey, o.orderkey FROM " + + "tpch.customer c INNER JOIN tpch.ORDERS1 o ON c.custkey = o.custkey WHERE c.mktsegment = 'BUILDING'", "Table mysql.tpch.ORDERS1 does not exist"); //failure scenario since tpch.ORDERS1 doesn't exist + assertFalse(getQueryRunner().tableExists(session, "TEST_CREATEAS_FAIL_Join")); + + getQueryRunner().execute(session, "CREATE TABLE Test_CreateAs_Mixed_Join AS SELECT Cus.custkey, Ord.orderkey FROM " + + "tpch.customer Cus INNER JOIN tpch.orders Ord ON Cus.custkey = Ord.custkey WHERE Cus.mktsegment = 'BUILDING'"); + assertTrue(getQueryRunner().tableExists(session, "Test_CreateAs_Mixed_Join")); + } + + @Test + public void testInsert() + { + Session session = testSessionBuilder() + .setCatalog("mysql") + .setSchema("Mixed_Test_Database") + .build(); + + getQueryRunner().execute(session, "CREATE TABLE Test_Insert (x bigint, y varchar(100))"); + getQueryRunner().execute(session, "INSERT INTO Test_Insert VALUES (123, 'test')"); + assertTrue(getQueryRunner().tableExists(session, "Test_Insert")); + assertQuery("SELECT * FROM Mixed_Test_Database.Test_Insert", "SELECT 123 x, 'test' y"); + + getQueryRunner().execute(session, "CREATE TABLE IF NOT EXISTS TEST_INSERT (x bigint, y varchar(100))"); + getQueryRunner().execute(session, "INSERT INTO TEST_INSERT VALUES (1234, 'test1')"); + assertTrue(getQueryRunner().tableExists(session, "TEST_INSERT")); + + getQueryRunner().execute(session, "DROP TABLE IF EXISTS Test_Insert"); + getQueryRunner().execute(session, "DROP TABLE IF EXISTS TEST_INSERT"); + } + + @Test + public void testSelectInformationSchemaColumnIsNullable() + { + assertUpdate("CREATE TABLE test_column (name VARCHAR NOT NULL, email VARCHAR)"); + assertQueryFails("SELECT is_nullable FROM Information_Schema.columns WHERE table_name = 'test_column'", "Schema Information_Schema does not exist"); + assertQuery("SELECT is_nullable FROM information_schema.columns WHERE table_name = 'test_column'", "VALUES 'NO','YES'"); + } + + @Test + public void testDuplicatedRowCreateTable() + { + assertQueryFails("CREATE TABLE test (a integer, a integer)", + "line 1:31: Column name 'a' specified more than once"); + assertQueryFails("CREATE TABLE TEST (a integer, a integer)", + "line 1:31: Column name 'a' specified more than once"); + assertQueryFails("CREATE TABLE test (a integer, orderkey integer, LIKE orders INCLUDING PROPERTIES)", + "line 1:49: Column name 'orderkey' specified more than once"); + + assertQueryFails("CREATE TABLE test (a integer, A integer)", + "line 1:31: Column name 'A' specified more than once"); + assertQueryFails("CREATE TABLE TEST (a integer, A integer)", + "line 1:31: Column name 'A' specified more than once"); + assertQueryFails("CREATE TABLE test (a integer, OrderKey integer, LIKE orders INCLUDING PROPERTIES)", + "line 1:49: Column name 'orderkey' specified more than once"); + } + + private void execute(String sql) + throws SQLException + { + try (Connection connection = DriverManager.getConnection(mysqlServer.getJdbcUrl()); + Statement statement = connection.createStatement()) { + statement.execute(sql); + } + } +} diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/DereferenceExpression.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/DereferenceExpression.java index 351113137d229..93264ebb6bcf9 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/DereferenceExpression.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/DereferenceExpression.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -76,7 +75,7 @@ public Identifier getField() */ public static QualifiedName getQualifiedName(DereferenceExpression expression) { - List parts = tryParseParts(expression.base, expression.field.getValue().toLowerCase(Locale.ENGLISH)); + List parts = tryParseParts(expression.base, expression.field.getValue()); return parts == null ? null : QualifiedName.of(parts); } @@ -104,7 +103,7 @@ private static List tryParseParts(Expression base, String fieldName) else if (base instanceof DereferenceExpression) { QualifiedName baseQualifiedName = getQualifiedName((DereferenceExpression) base); if (baseQualifiedName != null) { - List newList = new ArrayList<>(baseQualifiedName.getParts()); + List newList = new ArrayList<>(baseQualifiedName.getOriginalParts()); newList.add(fieldName); return newList; } diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/QualifiedName.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/QualifiedName.java index 1a9f17a048b13..9b1d6dd3acc0a 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/QualifiedName.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/QualifiedName.java @@ -15,13 +15,13 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.util.List; import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.isEmpty; import static com.google.common.collect.Iterables.transform; import static java.util.Locale.ENGLISH; @@ -81,12 +81,13 @@ public String toString() */ public Optional getPrefix() { - if (parts.size() == 1) { + if (originalParts.size() == 1) { return Optional.empty(); } + List originalSubList = originalParts.subList(0, originalParts.size() - 1); List subList = parts.subList(0, parts.size() - 1); - return Optional.of(new QualifiedName(subList, subList)); + return Optional.of(new QualifiedName(originalSubList, subList)); } public boolean hasSuffix(QualifiedName suffix) @@ -102,7 +103,12 @@ public boolean hasSuffix(QualifiedName suffix) public String getSuffix() { - return Iterables.getLast(parts); + return getLast(parts); + } + + public String getOriginalSuffix() + { + return getLast(originalParts); } @Override diff --git a/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java b/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java index 14f5649ef9ba3..7bb5143e19a9a 100644 --- a/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java +++ b/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java @@ -76,7 +76,7 @@ public void testTableRules() accessControl.checkCanDropTable(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobtable")); assertDenied(() -> accessControl.checkCanRenameTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), new SchemaTableName("bobschema", "newbobtable"))); accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableMap.of()); - accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceSchema", "aliceTable"), ImmutableMap.of()); + accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceschema", "alicetable"), ImmutableMap.of()); assertDenied(() -> accessControl.checkCanInsertIntoTable(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("bobschema", "bobtable"))); assertDenied(() -> accessControl.checkCanDropTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable"))); assertDenied(() -> accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableMap.of())); diff --git a/presto-product-tests/bin/run_on_docker.sh b/presto-product-tests/bin/run_on_docker.sh index 80a99f5d72875..6fb5b4fa1bf12 100755 --- a/presto-product-tests/bin/run_on_docker.sh +++ b/presto-product-tests/bin/run_on_docker.sh @@ -149,6 +149,8 @@ elif [[ "$ENVIRONMENT" == "singlenode-ldap" ]]; then EXTERNAL_SERVICES="hadoop-master ldapserver" elif [[ "$ENVIRONMENT" == "singlenode-mysql" ]]; then EXTERNAL_SERVICES="hadoop-master mysql" +elif [[ "$ENVIRONMENT" == "singlenode-mysql-mixed-case-on" ]]; then + EXTERNAL_SERVICES="hadoop-master mysql-mixed-case-on" elif [[ "$ENVIRONMENT" == "singlenode-postgresql" ]]; then EXTERNAL_SERVICES="hadoop-master postgres" elif [[ "$ENVIRONMENT" == "singlenode-cassandra" ]]; then diff --git a/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/compose.sh b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/compose.sh new file mode 100755 index 0000000000000..5e3805e58626e --- /dev/null +++ b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/compose.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "${BASH_SOURCE%/*}/../common/compose-commons.sh" +MYSQL_INIT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/mysql-init" && pwd)" +export MYSQL_INIT_PATH +docker compose \ + -f ${BASH_SOURCE%/*}/../common/standard.yml \ + -f ${BASH_SOURCE%/*}/docker-compose.yml \ + "$@" diff --git a/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/docker-compose.yml b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/docker-compose.yml new file mode 100644 index 0000000000000..9dc63d4a7e705 --- /dev/null +++ b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/docker-compose.yml @@ -0,0 +1,20 @@ +services: + + mysql-mixed-case-on: + hostname: mysql + image: 'mysql:8.0' + ports: + - '13306:13306' + command: + mysqld --port 13306 --lower_case_table_names=0 # Configuration to enforce case-sensitive behavior in MySQL, regardless of the underlying operating system + environment: + MYSQL_USER: swarm + MYSQL_PASSWORD: swarm + MYSQL_ROOT_PASSWORD: swarm + MYSQL_DATABASE: test + volumes: + - ${MYSQL_INIT_PATH}:/docker-entrypoint-initdb.d + + presto-master: + volumes: + - ../../../conf/presto/etc/environment-specific-catalogs/singlenode-mysql-mixed-case-on/mysql.properties:/docker/volumes/conf/presto/etc/catalog/mysql.properties diff --git a/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/mysql-init/init.sql b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/mysql-init/init.sql new file mode 100644 index 0000000000000..e47d3d6303369 --- /dev/null +++ b/presto-product-tests/conf/docker/singlenode-mysql-mixed-case-on/mysql-init/init.sql @@ -0,0 +1,3 @@ +CREATE DATABASE IF NOT EXISTS mysqlmixedcase; +CREATE DATABASE IF NOT EXISTS MYSQLMIXEDCASE1; +CREATE DATABASE IF NOT EXISTS MySqlMixedCase2; diff --git a/presto-product-tests/conf/docker/singlenode-mysql/compose.sh b/presto-product-tests/conf/docker/singlenode-mysql/compose.sh index 9023c0b0d3788..5e3805e58626e 100755 --- a/presto-product-tests/conf/docker/singlenode-mysql/compose.sh +++ b/presto-product-tests/conf/docker/singlenode-mysql/compose.sh @@ -3,7 +3,8 @@ set -euo pipefail source "${BASH_SOURCE%/*}/../common/compose-commons.sh" - +MYSQL_INIT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/mysql-init" && pwd)" +export MYSQL_INIT_PATH docker compose \ -f ${BASH_SOURCE%/*}/../common/standard.yml \ -f ${BASH_SOURCE%/*}/docker-compose.yml \ diff --git a/presto-product-tests/conf/docker/singlenode-mysql/docker-compose.yml b/presto-product-tests/conf/docker/singlenode-mysql/docker-compose.yml index 33f84e6082dc5..400819d1affd2 100644 --- a/presto-product-tests/conf/docker/singlenode-mysql/docker-compose.yml +++ b/presto-product-tests/conf/docker/singlenode-mysql/docker-compose.yml @@ -6,9 +6,11 @@ services: ports: - '13306:13306' command: - mysqld --port 13306 + mysqld --port 13306 --lower_case_table_names=0 # Configuration to enforce case-sensitive behavior in MySQL, regardless of the underlying operating system environment: MYSQL_USER: swarm MYSQL_PASSWORD: swarm MYSQL_ROOT_PASSWORD: swarm MYSQL_DATABASE: test + volumes: + - ${MYSQL_INIT_PATH}:/docker-entrypoint-initdb.d diff --git a/presto-product-tests/conf/docker/singlenode-mysql/mysql-init/init.sql b/presto-product-tests/conf/docker/singlenode-mysql/mysql-init/init.sql new file mode 100644 index 0000000000000..b7e1573e3e62e --- /dev/null +++ b/presto-product-tests/conf/docker/singlenode-mysql/mysql-init/init.sql @@ -0,0 +1,3 @@ +CREATE DATABASE IF NOT EXISTS mysqlmixedcase; +CREATE DATABASE IF NOT EXISTS mysqlmixedcase1; +CREATE DATABASE IF NOT EXISTS mysqlmixedcase2; diff --git a/presto-product-tests/conf/presto/etc/environment-specific-catalogs/singlenode-mysql-mixed-case-on/mysql.properties b/presto-product-tests/conf/presto/etc/environment-specific-catalogs/singlenode-mysql-mixed-case-on/mysql.properties new file mode 100644 index 0000000000000..4a4ae832164d0 --- /dev/null +++ b/presto-product-tests/conf/presto/etc/environment-specific-catalogs/singlenode-mysql-mixed-case-on/mysql.properties @@ -0,0 +1,5 @@ +connector.name=mysql +connection-url=jdbc:mysql://mysql:13306?enabledTLSProtocols=TLSv1.2 +connection-user=root +connection-password=swarm +case-sensitive-name-matching=true diff --git a/presto-product-tests/src/main/java/com/facebook/presto/tests/TestGroups.java b/presto-product-tests/src/main/java/com/facebook/presto/tests/TestGroups.java index a750c5ce1f79f..ba7b8b109c141 100644 --- a/presto-product-tests/src/main/java/com/facebook/presto/tests/TestGroups.java +++ b/presto-product-tests/src/main/java/com/facebook/presto/tests/TestGroups.java @@ -28,6 +28,7 @@ public final class TestGroups public static final String SMOKE = "smoke"; public static final String JDBC = "jdbc"; public static final String MYSQL = "mysql"; + public static final String MYSQL_MIXED_CASE = "mysql_mixed_case"; public static final String PRESTO_JDBC = "presto_jdbc"; public static final String SIMBA_JDBC = "simba_jdbc"; public static final String QUERY_ENGINE = "qe"; @@ -67,6 +68,7 @@ public final class TestGroups public static final String ICEBERG = "iceberg"; public static final String HIVE_LIST_CACHING = "hive_list_caching"; public static final String INVALIDATE_METASTORE_CACHE = "invalidate_metastore_cache"; + public static final String MIXED_CASE = "mixed_case"; private TestGroups() {} } diff --git a/presto-product-tests/src/main/java/com/facebook/presto/tests/hive/TestHiveMixedCaseSupport.java b/presto-product-tests/src/main/java/com/facebook/presto/tests/hive/TestHiveMixedCaseSupport.java new file mode 100644 index 0000000000000..8d3acea1fce70 --- /dev/null +++ b/presto-product-tests/src/main/java/com/facebook/presto/tests/hive/TestHiveMixedCaseSupport.java @@ -0,0 +1,190 @@ +/* + * Licensed 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 com.facebook.presto.tests.hive; + +import io.prestodb.tempto.ProductTest; +import org.testng.annotations.Test; + +import static com.facebook.presto.tests.TestGroups.MIXED_CASE; +import static io.prestodb.tempto.assertions.QueryAssert.Row.row; +import static io.prestodb.tempto.assertions.QueryAssert.assertThat; +import static io.prestodb.tempto.query.QueryExecutor.query; +import static java.lang.String.format; +import static java.util.Locale.ENGLISH; + +public class TestHiveMixedCaseSupport + extends ProductTest +{ + private static final String SCHEMA_NAME = "hivemixedcaseon"; + private static final String SCHEMA_NAME_UPPER = "HIVEMIXEDCASEON1"; + private static final String SCHEMA_NAME_MIXED = "HiveMixedCase"; + + /* + * Test cases for creating schemas with different naming conventions in Hive. + * + * This class includes tests for: + * 1. Creating a schema with a lowercase name. + * 2. Creating a schema as the existing schema name with a mixed-case name. + * 3. Creating a schema with a mixed-case name. + * 4. Creating a schema with an uppercase name. + * + * Each test ensures the schema is created successfully. + */ + @Test(groups = {MIXED_CASE}) + public void testCreateSchemasWithMixedCaseNames() + { + String schemaNameMixedSyllables = "HiveMixedCaseOn"; + + query("CREATE SCHEMA IF NOT EXISTS " + SCHEMA_NAME); + query("CREATE SCHEMA IF NOT EXISTS " + schemaNameMixedSyllables); + query("CREATE SCHEMA IF NOT EXISTS " + SCHEMA_NAME_MIXED); + query("CREATE SCHEMA " + SCHEMA_NAME_UPPER); + + assertThat(query("SHOW SCHEMAS")) + .contains(row(SCHEMA_NAME.toLowerCase(ENGLISH))) + .contains(row(SCHEMA_NAME_MIXED.toLowerCase(ENGLISH))) + .contains(row(SCHEMA_NAME_UPPER.toLowerCase(ENGLISH))); + } + + /* + * Test cases for creating tables with different naming conventions in Hive. + * + * This test verifies table creation, schema behavior, and column definitions + * when using different case variations for table and column names. + * + * Scenarios covered: + * 1. Creating tables with lowercase, mixed-case, and uppercase names. + * 2. Creating tables with uppercase column names. + * 3. Creating tables in schemas with lowercase and uppercase names. + * + * The test ensures: + * - Tables are created successfully. + * - Table names are stored and retrieved correctly. + * - Column definitions match expected types. + */ + @Test(groups = {MIXED_CASE}, dependsOnMethods = "testCreateSchemasWithMixedCaseNames") + public void testCreateTablesWithMixedCaseNames() + { + query("CREATE TABLE " + SCHEMA_NAME + ".testtable0 (name VARCHAR(50), id INT)"); + query("CREATE TABLE " + SCHEMA_NAME + ".testtable (name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + SCHEMA_NAME + ".TestTable1 (Name VARCHAR(50), id INT)"); + query("CREATE TABLE " + SCHEMA_NAME + ".TESTTABLE2 (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + SCHEMA_NAME_UPPER + ".TESTTABLE4 (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + SCHEMA_NAME_UPPER + ".testtable02 (name VARCHAR(50), id INT, num DOUBLE)"); + query("CREATE TABLE " + SCHEMA_NAME_UPPER + ".TESTTABLE3 (name VARCHAR(50), id INT, num DOUBLE)"); + query("CREATE TABLE " + SCHEMA_NAME_MIXED + ".\"TestTable\" (Name VARCHAR(50), id INT)"); + + assertThat(query("SHOW TABLES FROM " + SCHEMA_NAME)) + .containsOnly(row("testtable0"), row("testtable"), row("testtable1"), row("testtable2")); + + assertThat(query("SHOW TABLES FROM " + SCHEMA_NAME_UPPER)) + .containsOnly(row("testtable4"), row("testtable02"), row("testtable3")); + + assertThat(query("DESCRIBE " + SCHEMA_NAME + ".testtable0")) + .contains(row("name", "varchar(50)", "", ""), row("id", "integer", "", "")); + + assertThat(query("DESCRIBE " + SCHEMA_NAME + ".testtable")) + .contains(row("name", "varchar(50)", "", ""), row("id", "integer", "", "")); + + assertThat(query("DESCRIBE " + SCHEMA_NAME_UPPER + ".testtable4")) + .contains(row("name", "varchar(50)", "", ""), row("id", "integer", "", "")); + + assertThat(query("DESCRIBE " + SCHEMA_NAME_UPPER + ".testtable02")) + .contains(row("name", "varchar(50)", "", ""), row("id", "integer", "", ""), row("num", "double", "", "")); + + assertThat(() -> query("CREATE TABLE " + SCHEMA_NAME + ".TESTTABLE0 (name VARCHAR(50), id INT)")) + .failsWithMessage(format("line 1:1: Table 'hive.%s.testtable0' already exists", SCHEMA_NAME)); + } + + /* + * This test validates inserting data into tables with different case variations in schema and table names. + * It ensures that data is inserted and retrieved correctly regardless of case sensitivity. + */ + @Test(groups = {MIXED_CASE}, dependsOnMethods = "testCreateTablesWithMixedCaseNames") + public void testInsertDataWithMixedCaseNames() + { + query("INSERT INTO " + SCHEMA_NAME + ".testtable VALUES ('amy', 112), ('mia', 123)"); + query("INSERT INTO " + SCHEMA_NAME + ".TESTTABLE2 VALUES ('ann', 112), ('mary', 123)"); + query("INSERT INTO " + SCHEMA_NAME_UPPER + ".testtable02 VALUES ('emma', 112, 200.002), ('mark', 123, 300.003)"); + query("INSERT INTO " + SCHEMA_NAME_UPPER + ".TESTTABLE3 VALUES ('emma', 112, 200.002), ('mark', 123, 300.003)"); + + query("INSERT INTO " + SCHEMA_NAME_MIXED + ".testtable VALUES ('amy1', 112)"); + query("INSERT INTO " + SCHEMA_NAME_MIXED + ".TestTable VALUES ('mary1', 123)"); + query("INSERT INTO \"" + SCHEMA_NAME_MIXED + "\".testtable VALUES ('amy2', 112)"); + query("INSERT INTO \"" + SCHEMA_NAME_MIXED + "\".TestTable VALUES ('mary2', 123)"); + query("INSERT INTO " + SCHEMA_NAME_MIXED + ".\"testtable\" VALUES ('amy3', 112)"); + query("INSERT INTO " + SCHEMA_NAME_MIXED + ".\"TestTable\" VALUES ('mary3', 123)"); + + assertThat(query("SELECT * FROM " + SCHEMA_NAME + ".testtable")) + .containsOnly(row("amy", 112), row("mia", 123)); + assertThat(query("SELECT * FROM " + SCHEMA_NAME + ".TESTTABLE2")) + .containsOnly(row("ann", 112), row("mary", 123)); + assertThat(query("SELECT * FROM " + SCHEMA_NAME_UPPER + ".testtable02")) + .containsOnly(row("emma", 112, 200.002), row("mark", 123, 300.003)); + assertThat(query("SELECT * FROM " + SCHEMA_NAME_UPPER + ".TESTTABLE3")) + .containsOnly(row("emma", 112, 200.002), row("mark", 123, 300.003)); + assertThat(query("SELECT * FROM \"" + SCHEMA_NAME_MIXED + "\".\"TestTable\"")).containsOnly( + row("amy1", 112), + row("mary1", 123), + row("amy2", 112), + row("mary2", 123), + row("amy3", 112), + row("mary3", 123)); + } + + /* + * This test verifies altering tables with different case variations in schema and table names. + * It ensures that columns can be added renamed irrespective of case sensitivity. + */ + @Test(groups = {MIXED_CASE}, dependsOnMethods = "testInsertDataWithMixedCaseNames") + public void testTableAlterWithMixedCaseNames() + { + query("ALTER TABLE " + SCHEMA_NAME + ".testtable ADD COLUMN num REAL"); + query("ALTER TABLE " + SCHEMA_NAME_UPPER + ".testtable02 ADD COLUMN num1 REAL"); + query("ALTER TABLE " + SCHEMA_NAME + ".TESTTABLE2 ADD COLUMN num01 REAL"); + query("ALTER TABLE " + SCHEMA_NAME_UPPER + ".TESTTABLE02 ADD COLUMN num2 REAL"); + + assertThat(query("DESCRIBE " + SCHEMA_NAME + ".testtable")) + .contains(row("num", "real", "", "")); + assertThat(query("DESCRIBE " + SCHEMA_NAME_UPPER + ".testtable02")) + .contains(row("num1", "real", "", "")); + assertThat(query("DESCRIBE " + SCHEMA_NAME + ".TESTTABLE2")) + .contains(row("num01", "real", "", "")); + assertThat(query("DESCRIBE " + SCHEMA_NAME_UPPER + ".TESTTABLE02")) + .contains(row("num2", "real", "", "")); + + query("ALTER TABLE " + SCHEMA_NAME + ".testtable RENAME COLUMN num TO numb"); + query("ALTER TABLE " + SCHEMA_NAME_UPPER + ".testtable02 RENAME COLUMN num1 TO numb01"); + + assertThat(query("DESCRIBE " + SCHEMA_NAME + ".testtable")) + .contains(row("numb", "real", "", "")); + assertThat(query("DESCRIBE " + SCHEMA_NAME_UPPER + ".testtable02")) + .contains(row("numb01", "real", "", "")); + } + + @Test(groups = {MIXED_CASE}, dependsOnMethods = "testTableAlterWithMixedCaseNames") + public void testDropMixedCaseTablesAndSchemas() + { + query("DROP TABLE IF EXISTS " + SCHEMA_NAME + ".testtable"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME + ".testtable0"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME + ".TestTable1"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME + ".TESTTABLE2"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME_UPPER + ".TESTTABLE4"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME_UPPER + ".testtable02"); + query("DROP TABLE IF EXISTS " + SCHEMA_NAME_UPPER + ".TESTTABLE3"); + + query("DROP SCHEMA IF EXISTS " + SCHEMA_NAME); + query("DROP SCHEMA IF EXISTS " + SCHEMA_NAME_UPPER); + } +} diff --git a/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOff.java b/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOff.java new file mode 100644 index 0000000000000..b70aea36fab8f --- /dev/null +++ b/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOff.java @@ -0,0 +1,138 @@ +/* + * Licensed 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 com.facebook.presto.tests.mysql; + +import io.prestodb.tempto.ProductTest; +import org.testng.annotations.Test; + +import static com.facebook.presto.tests.TestGroups.MYSQL; +import static com.facebook.presto.tests.utils.QueryExecutors.onMySql; +import static io.prestodb.tempto.assertions.QueryAssert.Row.row; +import static io.prestodb.tempto.assertions.QueryAssert.assertThat; +import static io.prestodb.tempto.query.QueryExecutor.query; + +public class TestMySQLMixedCaseSupportOff + extends ProductTest +{ + private static final String CATALOG = "mysql"; + private static final String SCHEMA_NAME = "mysqlmixedcase"; + private static final String SCHEMA_NAME_UPPER = "MYSQLMIXEDCASE1"; + + private static final String TABLE_NAME = "testtable"; + private static final String TABLE_NAME_0 = "testtable0"; + private static final String TABLE_NAME_MIXED_1 = "TestTable1"; + private static final String TABLE_NAME_UPPER_2 = "TESTTABLE2"; + private static final String TABLE_NAME_UPPER_SCHEMA_3 = "TESTTABLE3"; + private static final String TABLE_NAME_UPPER_SCHEMA_4 = "TESTTABLE4"; + private static final String TABLE_NAME_UPPER_SCHEMA_02 = "testtable02"; + + /* + * Test cases for creating tables with different naming conventions in MySQL. + * + * This test verifies table creation, schema behavior, and column definitions + * when using different case variations for table and column names. + */ + @Test(groups = {MYSQL}) + public void testCreateTablesWithMixedCaseNames() + { + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_0 + " (name VARCHAR(50), id INT)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME + " (name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_MIXED_1 + " (Name VARCHAR(50), id INT)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_UPPER_2 + " (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_4 + " (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02 + " (name VARCHAR(50), id INT, num DOUBLE)"); + query("CREATE TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_3 + " (name VARCHAR(50), id INT, num DOUBLE)"); + + assertThat(query("SHOW TABLES FROM " + CATALOG + "." + SCHEMA_NAME)) + .containsOnly(row("testtable0"), row("testtable"), row("testtable1"), row("testtable2")); + + assertThat(query("SHOW TABLES FROM " + CATALOG + "." + SCHEMA_NAME_UPPER)) + .containsOnly(row("testtable4"), row("testtable02"), row("testtable3")); + } + + /* + * This test validates inserting data into tables with different case variations in schema and table names. + * It ensures that data is inserted and retrieved correctly regardless of case sensitivity. + */ + @Test(groups = {MYSQL}, dependsOnMethods = "testCreateTablesWithMixedCaseNames") + public void testInsertDataWithMixedCaseNames() + { + query("INSERT INTO " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME + " VALUES ('amy', 112), ('mia', 123)"); + query("INSERT INTO " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_UPPER_2 + " VALUES ('ann', 112), ('mary', 123)"); + query("INSERT INTO " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02 + " VALUES ('emma', 112, 200.002), ('mark', 123, 300.003)"); + query("INSERT INTO " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_3 + " VALUES ('emma', 112, 200.002), ('mark', 123, 300.003)"); + + assertThat(query("SELECT * FROM " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME)) + .containsOnly(row("amy", 112), row("mia", 123)); + + assertThat(query("SELECT * FROM " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_UPPER_2)) + .containsOnly(row("ann", 112), row("mary", 123)); + + assertThat(query("SELECT * FROM " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02)) + .containsOnly(row("emma", 112, 200.002), row("mark", 123, 300.003)); + + assertThat(query("SELECT * FROM " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_3)) + .containsOnly(row("emma", 112, 200.002), row("mark", 123, 300.003)); + } + + /* + * This test verifies altering tables with different case variations in schema and table names. + * + * Scenarios covered: + * 1. Adding columns to tables with lowercase, mixed-case, and uppercase names. + * 2. Renaming columns in tables with various case patterns. + * + * The test ensures: + * - Column addition works correctly across cases. + * - Column renaming functions as expected. + * - Case variations do not impact alter operations. + */ + @Test(groups = {MYSQL}, dependsOnMethods = "testInsertDataWithMixedCaseNames") + public void testTableAlterWithMixedCaseNames() + { + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME + " ADD COLUMN num REAL"); + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02 + " ADD COLUMN num1 REAL"); + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_UPPER_2 + " ADD COLUMN num01 REAL"); + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02.toUpperCase() + " ADD COLUMN num2 REAL"); + + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME)) + .contains(row("num", "real", "", "")); + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02)) + .contains(row("num1", "real", "", "")); + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME_UPPER_2)) + .contains(row("num01", "real", "", "")); + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02.toUpperCase())) + .contains(row("num2", "real", "", "")); + + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME + " RENAME COLUMN num TO numb"); + query("ALTER TABLE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02 + " RENAME COLUMN num1 TO numb01"); + + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME + "." + TABLE_NAME)) + .contains(row("numb", "real", "", "")); + assertThat(query("DESCRIBE " + CATALOG + "." + SCHEMA_NAME_UPPER + "." + TABLE_NAME_UPPER_SCHEMA_02)) + .contains(row("numb01", "real", "", "")); + } + + @Test(groups = {MYSQL}, dependsOnMethods = "testTableAlterWithMixedCaseNames") + public void testDropMixedCaseTablesAndSchemas() + { + onMySql().executeQuery("DROP TABLE IF EXISTS mysqlmixedcase.testtable"); + onMySql().executeQuery("DROP TABLE IF EXISTS mysqlmixedcase.testtable0"); + onMySql().executeQuery("DROP TABLE IF EXISTS mysqlmixedcase.TestTable1"); + onMySql().executeQuery("DROP TABLE IF EXISTS mysqlmixedcase.TESTTABLE2"); + onMySql().executeQuery("DROP TABLE IF EXISTS MYSQLMIXEDCASE1.TESTTABLE3"); + onMySql().executeQuery("DROP TABLE IF EXISTS MYSQLMIXEDCASE1.TESTTABLE4"); + onMySql().executeQuery("DROP TABLE IF EXISTS MYSQLMIXEDCASE1.testtable02"); + } +} diff --git a/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOn.java b/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOn.java new file mode 100644 index 0000000000000..a4bddeed2abbd --- /dev/null +++ b/presto-product-tests/src/main/java/com/facebook/presto/tests/mysql/TestMySQLMixedCaseSupportOn.java @@ -0,0 +1,220 @@ +/* + * Licensed 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 com.facebook.presto.tests.mysql; + +import io.prestodb.tempto.ProductTest; +import org.testng.annotations.Test; + +import static com.facebook.presto.tests.TestGroups.MYSQL_MIXED_CASE; +import static com.facebook.presto.tests.utils.QueryExecutors.onMySql; +import static io.prestodb.tempto.assertions.QueryAssert.Row.row; +import static io.prestodb.tempto.assertions.QueryAssert.assertThat; +import static io.prestodb.tempto.query.QueryExecutor.query; +public class TestMySQLMixedCaseSupportOn + extends ProductTest +{ + private static final String CATALOG = "mysql"; + private static final String SCHEMA_NAME = "mysqlmixedcase"; + private static final String SCHEMA_NAME_UPPER = "MYSQLMIXEDCASE1"; + private static final String SCHEMA_NAME_MIXED = "MySqlMixedCase2"; + private static final String TABLE_NAME = "testtable"; + private static final String TABLE_NAME_0 = "testtable0"; + private static final String TABLE_NAME_MIXED_1 = "TestTable1"; + private static final String TABLE_NAME_UPPER_2 = "TESTTABLE2"; + private static final String TABLE_NAME_UPPER_SCHEMA_3 = "TESTTABLE3"; + private static final String TABLE_NAME_UPPER_SCHEMA_4 = "TESTTABLE4"; + private static final String TABLE_NAME_UPPER_SCHEMA_02 = "testtable02"; + private static final String TABLE_NAME_FROM_LOWER = "createdfromlowercase"; + private static final String TABLE_NAME_FROM_MIXED = "createdFromMixedCase"; + private static final String TABLE_NAME_JOIN_LOWER = "createdwithjoinlower"; + private static final String TABLE_NAME_JOIN_MIXED = "createdwithjoinmixed"; + + /** + * This comprehensive test covers various scenarios for creating tables with different combinations + * of schema, table, and column casing. The objective is to validate Presto's handling of case sensitivity + * with MySQL when mixed-case support is enabled. + * + * Covered scenarios: + * - Lowercase schema with lowercase table and columns + * - Lowercase schema with lowercase table and uppercase columns + * - Lowercase schema with mixed-case table name + * - Lowercase schema with uppercase table name and columns + * - Uppercase schema with uppercase table name and columns + * - Uppercase schema with lowercase table and columns + * - Uppercase schema with uppercase table and columns + * - Mixed-case schema with new table + * - Lowercase schema: CREATE TABLE AS SELECT from lowercase table + * - Mixed-case schema: CREATE TABLE AS SELECT from lowercase table + * - Join queries using lowercase aliases + */ + @Test(groups = {MYSQL_MIXED_CASE}) + public void testCreateAllTablesWithMixedCaseScenarios() + { + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_0 + "\" (name VARCHAR(50), id INT)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" (name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\" (Name VARCHAR(50), id INT)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_UPPER_2 + "\" (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_4 + "\" (Name VARCHAR(50), ID INT)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" (name VARCHAR(50), id INT, num DOUBLE)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_3 + "\" (name VARCHAR(50), id INT, num DOUBLE)"); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_FROM_LOWER + "\" AS SELECT * FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\""); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\".\"" + TABLE_NAME_FROM_MIXED + "\" AS SELECT * FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\""); + query("CREATE TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_JOIN_LOWER + "\" AS " + + "SELECT d.* FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" d " + + "INNER JOIN " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" m " + + "ON d.id = m.id WHERE d.id = 1"); + + assertThat(query("SHOW TABLES FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\"")) + .containsOnly(row("testtable0"), row("testtable"), row("TestTable1"), row("TESTTABLE2"), row("createdfromlowercase"), row("createdwithjoinlower")); + + assertThat(query("SHOW TABLES FROM " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\"")) + .containsOnly(row("TESTTABLE4"), row("testtable02"), row("TESTTABLE3")); + + assertThat(query("SHOW TABLES FROM " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\"")) + .containsOnly(row("createdFromMixedCase")); + } + + /** + * This test validates inserting data into tables using various combinations + * of schema names and table names with different casing. It ensures that + * Presto honors case sensitivity when `lower_case_table_names=0` and + * mixed-case support is enabled in MySQL. + * + * Covered scenarios: + * 1. Inserting into a table using lowercase schema and lowercase table name. + * 2. Inserting into a table using lowercase schema and mixed-case table name. + * 3. Inserting into a table using mixed-case schema and lowercase table name. + * 4. Inserting into a table using uppercase schema and uppercase table name with mixed-case columns. + * 5. Inserting into a table using mixed-case schema and mixed-case table name. + * 6. Inserting into a table with mixed-case name from another table with lowercase name. + * 7. Inserting into a table with lowercase name from another table with mixed-case name. + */ + @Test(groups = {MYSQL_MIXED_CASE}, dependsOnMethods = "testCreateAllTablesWithMixedCaseScenarios") + public void testInsertDataIntoExistingMixedCaseTables() + { + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" VALUES ('eva', 301), ('lisa', 302)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\" VALUES ('ivan', 401), ('nora', 402)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\".\"" + TABLE_NAME_FROM_MIXED + "\" VALUES ('kate', 501), ('leo', 502)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" VALUES ('kate', 501, 200), ('leo', 502, 201)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_4 + "\" VALUES ('kate', 501), ('leo', 502)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_3 + "\" VALUES ('zack', 601, 100.1), ('jane', 602, 200.2)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\".\"" + TABLE_NAME_FROM_MIXED + "\" VALUES ('ruby', 701), ('ted', 702)"); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\" " + + "SELECT name, id FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\""); + query("INSERT INTO " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" " + + "SELECT Name, id FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\""); + + assertThat(query("SELECT COUNT(*) FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\"")) + .hasRowsCount(1); + assertThat(query("SELECT COUNT(*) FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\"")) + .hasRowsCount(1); + assertThat(query("SELECT COUNT(*) FROM " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_3 + "\"")) + .hasRowsCount(1); + assertThat(query("SELECT COUNT(*) FROM " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\".\"" + TABLE_NAME_FROM_MIXED + "\"")) + .hasRowsCount(1); + } + + /** + * This test verifies that selecting data from MySQL tables with various combinations + * of case-sensitive schema names, table names, and column names works correctly + * when mixed-case support is enabled in Presto. + * + * Covered scenarios: + * 1. Select all data from a lowercase schema and lowercase table. + * 2. Select filtered data from a mixed-case table in a lowercase schema. + * 3. Select filtered data from a lowercase table in a mixed-case schema. + * 4. Select filtered data from a mixed-case table in a mixed-case schema. + * 5. Select (with subquery) from a mixed-case schema and table using mixed-case columns. + */ + @Test(groups = {MYSQL_MIXED_CASE}, dependsOnMethods = "testInsertDataIntoExistingMixedCaseTables") + public void testSelectDataWithMixedCaseNames() + { + assertThat(query("SELECT * FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\"")) + .containsOnly(row("eva", 301), row("lisa", 302), row("ivan", 401), row("nora", 402), row("eva", 301), row("lisa", 302)); + + assertThat(query("SELECT name FROM " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_MIXED_1 + "\" WHERE id = 401")) + .containsOnly(row("ivan")); + + assertThat(query("SELECT name FROM " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" WHERE id = 501")) + .containsOnly(row("kate")); + + assertThat(query("SELECT name FROM " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_3 + "\" WHERE id = 601")) + .containsOnly(row("zack")); + + assertThat(query("SELECT Name, ID " + + "FROM " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_4 + "\" " + + "WHERE name IN (" + + "SELECT Name FROM " + CATALOG + ".\"" + SCHEMA_NAME_MIXED + "\".\"" + TABLE_NAME_FROM_MIXED + "\" WHERE id = 501)")) + .containsOnly(row("kate", 501)); + } + + /** + * This test verifies that altering MySQL tables with various combinations + * of case-sensitive schema names and table names works correctly + * when mixed-case support is enabled in Presto. + * + * Covered scenarios: + * 1. Add a new column to a table in a lowercase schema with a lowercase table name. + * 2. Add new columns to a table in an uppercase schema with an uppercase table name. + * 3. Add a new column to a table in a lowercase schema with an uppercase table name. + * 4. Rename columns in tables with different schema and table casing. + * 5. Verify added and renamed columns using DESCRIBE queries. + */ + + @Test(groups = {MYSQL_MIXED_CASE}, dependsOnMethods = "testSelectDataWithMixedCaseNames") + public void testTableAlterWithMixedCaseNames() + { + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" ADD COLUMN num REAL"); + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" ADD COLUMN num1 REAL"); + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_UPPER_2 + "\" ADD COLUMN num01 REAL"); + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" ADD COLUMN num2 REAL"); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\"")) + .contains(row("num", "real", "", "")); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\"")) + .contains(row("num1", "real", "", "")); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME_UPPER_2 + "\"")) + .contains(row("num01", "real", "", "")); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\"")) + .contains(row("num2", "real", "", "")); + + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" RENAME COLUMN num TO numb"); + query("ALTER TABLE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\" RENAME COLUMN num1 TO numb01"); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\"")) + .contains(row("numb", "real", "", "")); + + assertThat(query("DESCRIBE " + CATALOG + ".\"" + SCHEMA_NAME_UPPER + "\".\"" + TABLE_NAME_UPPER_SCHEMA_02 + "\"")) + .contains(row("numb01", "real", "", "")); + } + + @Test(groups = {MYSQL_MIXED_CASE}, dependsOnMethods = "testTableAlterWithMixedCaseNames") + public void testDropMixedCaseTablesAndSchemas() + { + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_0 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_MIXED_1 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_UPPER_2 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME_UPPER + "`.`" + TABLE_NAME_UPPER_SCHEMA_3 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME_UPPER + "`.`" + TABLE_NAME_UPPER_SCHEMA_4 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME_UPPER + "`.`" + TABLE_NAME_UPPER_SCHEMA_02 + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_FROM_LOWER + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME_MIXED + "`.`" + TABLE_NAME_FROM_MIXED + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_JOIN_LOWER + "`"); + onMySql().executeQuery("DROP TABLE IF EXISTS `" + SCHEMA_NAME + "`.`" + TABLE_NAME_JOIN_MIXED + "`"); + } +} diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/SchemaTableName.java b/presto-spi/src/main/java/com/facebook/presto/spi/SchemaTableName.java index 8a954d9a898f6..349c41e7eb205 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/SchemaTableName.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/SchemaTableName.java @@ -22,7 +22,6 @@ import java.util.Objects; import static com.facebook.presto.spi.SchemaUtil.checkNotEmpty; -import static java.util.Locale.ENGLISH; @ThriftStruct public class SchemaTableName @@ -34,8 +33,8 @@ public class SchemaTableName @ThriftConstructor public SchemaTableName(@JsonProperty("schema") String schemaName, @JsonProperty("table") String tableName) { - this.schemaName = checkNotEmpty(schemaName, "schemaName").toLowerCase(ENGLISH); - this.tableName = checkNotEmpty(tableName, "tableName").toLowerCase(ENGLISH); + this.schemaName = checkNotEmpty(schemaName, "schemaName"); + this.tableName = checkNotEmpty(tableName, "tableName"); } public static SchemaTableName valueOf(String schemaTableName) diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java index ed0e7e2298b64..7372a4170ff97 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java @@ -67,6 +67,7 @@ import static com.facebook.presto.spi.TableLayoutFilterCoverage.NOT_APPLICABLE; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import static java.util.Locale.ENGLISH; import static java.util.stream.Collectors.toList; public interface ConnectorMetadata @@ -876,4 +877,12 @@ default boolean isPushdownSupportedForFilter(ConnectorSession session, Connector { return false; } + + /** + * Normalize the provided SQL identifier according to connector-specific rules + */ + default String normalizeIdentifier(ConnectorSession session, String identifier) + { + return identifier.toLowerCase(ENGLISH); + } } diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java index 075f4e8c2aae4..1427ffa396053 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java @@ -811,4 +811,11 @@ public boolean isPushdownSupportedForFilter(ConnectorSession session, ConnectorT return delegate.isPushdownSupportedForFilter(session, tableHandle, filter, symbolToColumnHandleMap); } } + + public String normalizeIdentifier(ConnectorSession session, String identifier) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.normalizeIdentifier(session, identifier); + } + } } diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java index 7c5971c010898..86c744faf3bdc 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java @@ -15,6 +15,7 @@ import com.facebook.airlift.node.NodeInfo; import com.facebook.presto.Session; +import com.facebook.presto.common.transaction.TransactionId; import com.facebook.presto.common.type.Type; import com.facebook.presto.cost.CostCalculator; import com.facebook.presto.cost.CostCalculatorUsingExchanges; @@ -28,6 +29,7 @@ import com.facebook.presto.nodeManager.PluginNodeManager; import com.facebook.presto.spi.WarningCollector; import com.facebook.presto.spi.security.AccessDeniedException; +import com.facebook.presto.spi.security.AllowAllAccessControl; import com.facebook.presto.sql.analyzer.FeaturesConfig; import com.facebook.presto.sql.analyzer.QueryExplainer; import com.facebook.presto.sql.expressions.ExpressionOptimizerManager; @@ -644,4 +646,17 @@ public static void dropTableIfExists(QueryRunner queryRunner, String catalogName { queryRunner.execute(format("DROP TABLE IF EXISTS %s.%s.%s", catalogName, schemaName, tableName)); } + + protected String normalizeIdentifier(String name, String catalogName) + { + Metadata metadata = getQueryRunner().getMetadata(); + TransactionId txid = getQueryRunner().getTransactionManager().beginTransaction(false); + Session session = getSession().beginTransactionId(txid, getQueryRunner().getTransactionManager(), new AllowAllAccessControl()); + try { + return metadata.normalizeIdentifier(session, catalogName, name); + } + finally { + getQueryRunner().getTransactionManager().asyncAbort(txid); + } + } } diff --git a/presto-verifier/src/main/java/com/facebook/presto/verifier/rewrite/QueryRewriter.java b/presto-verifier/src/main/java/com/facebook/presto/verifier/rewrite/QueryRewriter.java index c3b9e6e694c20..1ca072cc926d1 100644 --- a/presto-verifier/src/main/java/com/facebook/presto/verifier/rewrite/QueryRewriter.java +++ b/presto-verifier/src/main/java/com/facebook/presto/verifier/rewrite/QueryRewriter.java @@ -350,7 +350,7 @@ private QualifiedName generateTemporaryName(Optional originalName parts.addAll(originalName.get().getOriginalParts().subList(0, originalSize - prefixSize)); } parts.addAll(prefix.getOriginalParts()); - parts.set(parts.size() - 1, prefix.getSuffix() + "_" + randomUUID().toString().replace("-", "")); + parts.set(parts.size() - 1, prefix.getOriginalSuffix() + "_" + randomUUID().toString().replace("-", "")); return QualifiedName.of(parts); }