Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions data/scripts/q_test_case_sensitive.mariadb.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Create a case-sensitive schema (database)
CREATE SCHEMA `WorldData`;

-- Case-Sensitive Schema and Table
CREATE TABLE `WorldData`.`Country`
(
id int,
name varchar(20)
);

INSERT INTO `WorldData`.`Country` VALUES (1, 'India'), (2, 'USA'), (3, 'Japan'), (4, 'Germany');


-- Case-Sensitive Partition Column
CREATE TABLE `WorldData`.`Cities`
(
id int,
name varchar(20),
`RegionID` int
);
INSERT INTO `WorldData`.`Cities` VALUES (1, 'Mumbai', 10), (2, 'New York', 20), (3, 'Tokyo', 30), (4, 'Berlin', 40), (5, 'New Delhi', 10), (6, 'Kyoto', 30);


-- Case-Sensitive Query Field Names
CREATE TABLE `WorldData`.`Geography`
(
id int,
`Description` varchar(50)
);
INSERT INTO `WorldData`.`Geography` VALUES (1, 'Asia'), (2, 'North America'), (3, 'Asia'), (4, 'Europe');
34 changes: 34 additions & 0 deletions data/scripts/q_test_case_sensitive.mssql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
CREATE SCHEMA [WorldData];

-- Case-Sensitive Schema and Table
CREATE TABLE [WorldData].[Country]
(
id int,
name varchar(20)
);
INSERT INTO [WorldData].[Country] VALUES (1, 'India'), (2, 'USA'), (3, 'Japan'), (4, 'Germany');


-- Case-Sensitive Partition Column
CREATE TABLE [WorldData].[Cities]
(
id int,
name varchar(20),
[RegionID] int
);
INSERT INTO [WorldData].[Cities] VALUES (1, 'Mumbai', 10), (2, 'New York', 20), (3, 'Tokyo', 30), (4, 'Berlin', 40), (5, 'New Delhi', 10), (6, 'Kyoto', 30);


-- Case-Sensitive Query Field Names
CREATE TABLE [WorldData].[Geography]
(
id int,
[Description] varchar(50)
);
INSERT INTO [WorldData].[Geography] VALUES (1, 'Asia'), (2, 'North America'), (3, 'Asia'), (4, 'Europe');

-- Create a user and associate them with a default schema
CREATE LOGIN greg WITH PASSWORD = 'GregPass123!$';
CREATE USER greg FOR LOGIN greg WITH DEFAULT_SCHEMA=bob;
-- Allow the user to connect to the database and run queries
GRANT CONNECT, SELECT TO greg;
53 changes: 53 additions & 0 deletions data/scripts/q_test_case_sensitive.oracle.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-- The comments below are from q_test_country_table_with_schema.oracle.sql

-- In Oracle dividing the tables in different namespaces/schemas is achieved via different users. The CREATE SCHEMA
-- statement exists in Oracle but has different semantics than those defined by SQL Standard and those adopted in other
-- DBMS.

-- In order to create the so-called "local" users in oracle you need to be connected to the Pluggable Database (PDB)
-- and not to the Container Database (CDB). In Oracle XE edition, used by this tests, the default and only PDB is
-- XEPDB1.
ALTER SESSION SET CONTAINER = XEPDB1;

-- Create a case-sensitive user, which also acts as the schema
CREATE USER "WorldData" IDENTIFIED BY QTestPassword123;
ALTER USER "WorldData" QUOTA UNLIMITED ON users;
GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW TO "WorldData";

-- Case-Sensitive Schema and Table
CREATE TABLE "WorldData"."Country"
(
id int,
name varchar(20)
);
INSERT INTO "WorldData"."Country" VALUES (1, 'India');
INSERT INTO "WorldData"."Country" VALUES (2, 'USA');
INSERT INTO "WorldData"."Country" VALUES (3, 'Japan');
INSERT INTO "WorldData"."Country" VALUES (4, 'Germany');


-- Case-Sensitive Partition Column
CREATE TABLE "WorldData"."Cities"
(
id int,
name varchar(20),
"RegionID" int
);
INSERT INTO "WorldData"."Cities" VALUES (1, 'Mumbai', 10);
INSERT INTO "WorldData"."Cities" VALUES (2, 'New York', 20);
INSERT INTO "WorldData"."Cities" VALUES (3, 'Tokyo', 30);
INSERT INTO "WorldData"."Cities" VALUES (4, 'Berlin', 40);
INSERT INTO "WorldData"."Cities" VALUES (5, 'New Delhi', 10);
INSERT INTO "WorldData"."Cities" VALUES (6, 'Kyoto', 30);


-- Case-Sensitive Query Field Names
CREATE TABLE "WorldData"."Geography"
(
id int,
"Description" varchar(50)
);
INSERT INTO "WorldData"."Geography" VALUES (1, 'Asia');
INSERT INTO "WorldData"."Geography" VALUES (2, 'North America');
INSERT INTO "WorldData"."Geography" VALUES (3, 'Asia');
INSERT INTO "WorldData"."Geography" VALUES (4, 'Europe');
37 changes: 37 additions & 0 deletions data/scripts/q_test_case_sensitive.postgres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- Create a case-sensitive schema
CREATE SCHEMA "WorldData";

-- Case-Sensitive Schema and Table
CREATE TABLE "WorldData"."Country"
(
id int,
name varchar(20)
);

INSERT INTO "WorldData"."Country" VALUES (1, 'India'), (2, 'USA'), (3, 'Japan'), (4, 'Germany');


-- Case-Sensitive Partition Column
CREATE TABLE "WorldData"."Cities"
(
id int,
name varchar(20),
"RegionID" int
);
INSERT INTO "WorldData"."Cities" VALUES (1, 'Mumbai', 10), (2, 'New York', 20), (3, 'Tokyo', 30), (4, 'Berlin', 40), (5, 'New Delhi', 10), (6, 'Kyoto', 30);


-- Case-Sensitive Query Field Names
CREATE TABLE "WorldData"."Geography"
(
id int,
"Description" varchar(50)
);
INSERT INTO "WorldData"."Geography" VALUES (1, 'Asia'), (2, 'North America'), (3, 'Asia'), (4, 'Europe');

-- Create a user and associate them with a default schema <=> search_path
CREATE ROLE greg WITH LOGIN PASSWORD 'GregPass123!$';
ALTER ROLE greg SET search_path TO "WorldData";
-- Grant the necessary permissions to be able to access the schema
GRANT USAGE ON SCHEMA "WorldData" TO greg;
GRANT SELECT ON ALL TABLES IN SCHEMA "WorldData" TO greg;
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import java.net.URI;
import java.net.URISyntaxException;

import static org.apache.hadoop.hive.ql.exec.Utilities.unescapeHiveJdbcIdentifier;

public class JdbcStorageHandler implements HiveStorageHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(JdbcStorageHandler.class);
Expand Down Expand Up @@ -101,12 +103,12 @@ public void configureInputJobProperties(TableDesc tableDesc, Map<String, String>
@Override
public URI getURIForAuth(Table table) throws URISyntaxException {
Map<String, String> tableProperties = HiveCustomStorageHandlerUtils.getTableProperties(table);
DatabaseType dbType = DatabaseType.valueOf(
DatabaseType dbType = DatabaseType.from(
tableProperties.get(JdbcStorageConfig.DATABASE_TYPE.getPropertyName()));
String host_url = DatabaseType.METASTORE == dbType ?
String hostUrl = DatabaseType.METASTORE == dbType ?
"jdbc:metastore://" : tableProperties.get(Constants.JDBC_URL);
String table_name = tableProperties.get(Constants.JDBC_TABLE);
return new URI(host_url+"/"+table_name);
String tableName = unescapeHiveJdbcIdentifier(tableProperties.get(Constants.JDBC_TABLE));
return new URI(hostUrl+"/" + tableName);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,68 @@
package org.apache.hive.storage.jdbc.conf;

public enum DatabaseType {
MYSQL,
// Default quote is "
H2,
DB2,
DERBY,
ORACLE,
POSTGRES,
MSSQL,
METASTORE,
JETHRO_DATA,
HIVE
MSSQL,

// Special quote cases
MYSQL("`"),
MARIADB("`"),
HIVE("`"),

// METASTORE will be resolved to another type
METASTORE(null, null);

// Keeping start and end quote separate to allow for future DBs
// that may have different start and end quotes
private final String startQuote;
private final String endQuote;

DatabaseType() {
this("\"", "\"");
}

DatabaseType(String quote) {
this(quote, quote);
}

DatabaseType(String startQuote, String endQuote) {
this.startQuote = startQuote;
this.endQuote = endQuote;
}

/**
* Helper to safely get the DatabaseType from a string.
*
* @param dbType The string from configuration properties.
* @return The matching DatabaseType.
* @throws IllegalArgumentException if the dbType is null or not a valid type.
*/
public static DatabaseType from(String dbType) {
if (dbType == null) {
throw new IllegalArgumentException("Database type string cannot be null");
}
// METASTORE must be handled before valueOf
if (METASTORE.name().equalsIgnoreCase(dbType)) {
return METASTORE;
}
try {
return valueOf(dbType.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid database type: " + dbType, e);
}
}

public String getStartQuote() {
return startQuote;
}

public String getEndQuote() {
return endQuote;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ public static void copyConfigurationToJob(Properties props, Map<String, String>
if (!key.equals(CONFIG_PWD) &&
!key.equals(CONFIG_PWD_KEYSTORE) &&
!key.equals(CONFIG_PWD_KEY) &&
!key.equals(CONFIG_PWD_URI) &&
!key.equals(CONFIG_USERNAME)
!key.equals(CONFIG_PWD_URI)
) {
jobProps.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import org.apache.hive.storage.jdbc.conf.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -52,6 +53,7 @@
import java.util.regex.Pattern;

import static com.google.common.base.MoreObjects.firstNonNull;
import static org.apache.hadoop.hive.ql.exec.Utilities.unescapeHiveJdbcIdentifier;

/**
* A data accessor that should in theory work with all JDBC compliant database drivers.
Expand Down Expand Up @@ -364,7 +366,7 @@ protected String addBoundaryToQuery(String tableName, String sql, String partiti
Matcher m = fromPattern.matcher(sql);
Preconditions.checkArgument(m.matches());

if (!tableName.equals(m.group(2).replaceAll("[`\"]", ""))) {
if (!tableName.equals(m.group(2))) {
throw new RuntimeException("Cannot find " + tableName + " in sql query " + sql);
}
result = String.format("%s (%s) tmptable %s", m.group(1), boundaryQuery, m.group(3));
Expand Down Expand Up @@ -537,12 +539,27 @@ public boolean needColumnQuote() {
return true;
}

/**
* Quotes an identifier based on the database type.
*
* @param identifier The identifier to quote
* @param dbType The DatabaseType enum
* @return A database-specific quoted identifier (e.g., "\"Country\"" or "`Country`")
*/
protected static String quoteIdentifier(String identifier, DatabaseType dbType) {
if (identifier == null || dbType == null || dbType.getStartQuote() == null) {
return identifier;
}
return dbType.getStartQuote() + identifier + dbType.getEndQuote();
}

private static String getQualifiedTableName(Configuration conf) {
String tableName = conf.get(Constants.JDBC_TABLE);
DatabaseType dbType = DatabaseType.from(conf.get(JdbcStorageConfig.DATABASE_TYPE.getPropertyName()));
String tableName = quoteIdentifier(unescapeHiveJdbcIdentifier(conf.get(Constants.JDBC_TABLE)), dbType);
if (tableName == null) {
return null;
}
String schemaName = conf.get(Constants.JDBC_SCHEMA);
String schemaName = quoteIdentifier(unescapeHiveJdbcIdentifier(conf.get(Constants.JDBC_SCHEMA)), dbType);
return schemaName == null ? tableName : schemaName + "." + tableName;
}

Expand Down
28 changes: 28 additions & 0 deletions ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -5094,4 +5094,32 @@ public static String getTableOrMVSuffix(Context context, boolean createTableOrMV
}
return suffix;
}

/**
* Unescape a Hive JDBC identifier, removing surrounding double quotes if present.
* @param identifier the identifier to unescape
* @return the unescaped identifier
*/
public static String unescapeHiveJdbcIdentifier(String identifier) {
return unescapeIdentifier(identifier, '"');
}

/**
* Unescape an identifier, removing surrounding characters if present.
*
* @param identifier any identifier
* @param ch the surrounding character to remove
* @return the unescaped identifier
*/
public static String unescapeIdentifier(String identifier, Character ch) {
if (identifier == null || identifier.length() < 2) {
return identifier;
}

if (identifier.charAt(0) == ch && identifier.charAt(identifier.length() - 1) == ch) {
return identifier.substring(1, identifier.length() - 1);
}

return identifier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@

import javax.sql.DataSource;

import static org.apache.hadoop.hive.ql.exec.Utilities.unescapeHiveJdbcIdentifier;
import static org.apache.hadoop.hive.ql.optimizer.calcite.HiveMaterializedViewASTSubQueryRewriteShuttle.getMaterializedViewByAST;
import static org.apache.hadoop.hive.ql.metadata.RewriteAlgorithm.ANY;

Expand Down Expand Up @@ -3179,8 +3180,8 @@ private RelNode genTableLogicalPlan(String tableAlias, QB qb) throws SemanticExc
LOG.warn("No password found for accessing {} table via JDBC", fullyQualifiedTabName);
}
final String catalogName = tabMetaData.getProperty(Constants.JDBC_CATALOG);
final String schemaName = tabMetaData.getProperty(Constants.JDBC_SCHEMA);
final String tableName = tabMetaData.getProperty(Constants.JDBC_TABLE);
final String schemaName = unescapeHiveJdbcIdentifier(tabMetaData.getProperty(Constants.JDBC_SCHEMA));
final String tableName = unescapeHiveJdbcIdentifier(tabMetaData.getProperty(Constants.JDBC_TABLE));

DataSource ds = JdbcSchema.dataSource(url, driver, user, pswd);
SqlDialect jdbcDialect = JdbcSchema.createDialect(SqlDialectFactoryImpl.INSTANCE, ds);
Expand Down
Loading