Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for unquoted Postgres identifiers (fix for #74) #75

Merged
merged 12 commits into from
Feb 12, 2019
Merged
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
1 change: 1 addition & 0 deletions jdbc-adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Property | Value
**ORA_CONNECTION_NAME** | Name of the connection to an Oracle database created with `CREATE CONNECTION`. Used by `IMPORT FROM ORA`.
**IS_LOCAL** | Only relevant if your data source is the same Exasol database where you create the virtual schema. Either `TRUE` or `FALSE` (default). If true, you are connecting to the local Exasol database (e.g. for testing purposes). In this case, the adapter can avoid the `IMPORT FROM JDBC` overhead.
**EXCEPTION_HANDLING** | Activates or deactivates different exception handling modes. Supported values: `IGNORE_INVALID_VIEWS` and `NONE` (default). Currently this property only affects the Teradata dialect.
**IGNORE_ERRORS** | Is used to ignore errors thrown by the adapter. Supported values: 'POSTGRESQL_UPPERCASE_TABLES' (see PostgreSQL dialect documentation).


## Debugging
Expand Down
23 changes: 22 additions & 1 deletion jdbc-adapter/doc/sql_dialects/postgresql.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,25 @@ CREATE VIRTUAL SCHEMA postgres
SCHEMA_NAME = 'public'
CONNECTION_NAME = 'POSTGRES_DOCKER'
;
```
```

## Postgres identifiers

In contrast to EXASOL, PostgreSQL does not treat identifiers as specified in the SQL standard. PostgreSQL folds unquoted identifiers to lower case instead of upper case. The adapter can do the identifier conversion as long as there are no identifiers in the PostgreSQL database that contain upper case characters. If that is the case an error will be thrown when creating or refreshing the virtual schema.
In order to create or refresh the virtual schema regrardlessly, you can specifiy that the adapter should ignore this specific error:
```sql
CREATE VIRTUAL SCHEMA postgres
USING adapter.jdbc_adapter
WITH
SQL_DIALECT = 'POSTGRESQL'
CATALOG_NAME = 'postgres'
SCHEMA_NAME = 'public'
CONNECTION_NAME = 'POSTGRES_DOCKER'
IGNORE_ERRORS = 'POSTGRESQL_UPPERCASE_TABLES'
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
;
```
You can also set this property for an exitsing virtual schema:
```sql
ALTER VIRTUAL SCHEMA postgres SET IGNORE_ERRORS = 'POSTGRESQL_UPPERCASE_TABLES';
```
However you won't be able to query the identifier containing the upper case character.
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;

import com.exasol.adapter.jdbc.ColumnAdapterNotes;
import com.exasol.adapter.jdbc.JdbcAdapterProperties;
Expand Down Expand Up @@ -40,7 +36,7 @@ public String getTableCatalogAndSchemaSeparator() {
}

@Override
public MappedTable mapTable(final ResultSet tables) throws SQLException {
public MappedTable mapTable(final ResultSet tables, final List<String> ignoreErrorList) throws SQLException {
String commentString = tables.getString("REMARKS");
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
if (commentString == null) {
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
commentString = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import com.exasol.adapter.capabilities.Capabilities;
Expand Down Expand Up @@ -153,9 +154,10 @@ public String getTableComment() {
* @param tables A jdbc Resultset for the
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
* {@link DatabaseMetaData#getTables(String, String, String, String[])}
* call, pointing to the current table.
* @param ignoreErrorList The elements of this list suppress certain errors the adapter would throw
* @return An instance of {@link MappedTable} describing the mapped table.
*/
public MappedTable mapTable(ResultSet tables) throws SQLException;
public MappedTable mapTable(ResultSet tables, final List<String> ignoreErrorList) throws SQLException;

/**
* @param columns A jdbc Resultset for the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.sql.SQLException;
import java.sql.Types;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import com.exasol.adapter.capabilities.AggregateFunctionCapability;
Expand Down Expand Up @@ -331,7 +332,7 @@ public SchemaOrCatalogSupport supportsJdbcSchemas() {
}

@Override
public MappedTable mapTable(final ResultSet tables) throws SQLException {
public MappedTable mapTable(final ResultSet tables, final List<String> ignoreErrorList) throws SQLException {
final String tableName = tables.getString("TABLE_NAME");
if (tableName.startsWith("BIN$")) {
// In case of Oracle we may see deleted tables with strange names
Expand All @@ -341,7 +342,7 @@ public MappedTable mapTable(final ResultSet tables) throws SQLException {
System.out.println("Skip table: " + tableName);
return MappedTable.createIgnoredTable();
} else {
return super.mapTable(tables);
return super.mapTable(tables, ignoreErrorList);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.exasol.adapter.dialects.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import com.exasol.adapter.capabilities.AggregateFunctionCapability;
Expand All @@ -20,6 +22,9 @@
import com.exasol.adapter.sql.ScalarFunction;

public class PostgreSQLSqlDialect extends AbstractSqlDialect {

public static final String POSTGRES_IGNORE_UPPERCASE_TABLES = "POSTGRESQL_UPPERCASE_TABLES";

public PostgreSQLSqlDialect(final SqlDialectContext context) {
super(context);
}
Expand Down Expand Up @@ -298,6 +303,21 @@ public DataType dialectSpecificMapJdbcType(final JdbcTypeDescription jdbcTypeDes
return colType;
}

@Override
public MappedTable mapTable(final ResultSet tables, final List<String> ignoreErrorList) throws SQLException {
final String tableName = tables.getString("TABLE_NAME");
if (!ignoreErrorList.contains(POSTGRES_IGNORE_UPPERCASE_TABLES) && containsUppercaseCharacter(tableName)) {
throw new IllegalArgumentException("Table " + tableName + " cannot be used in virtual schema. " +
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
"Set property IGNORE_ERRORS to POSTGRES_UPPERCASE_TABLES to enforce schema creation.");
} else {
return super.mapTable(tables, ignoreErrorList);
}
}

private boolean containsUppercaseCharacter(final String tableName) {
return !tableName.equals(tableName.toLowerCase());
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public Map<ScalarFunction, String> getScalarFunctionAliases() {

Expand Down Expand Up @@ -345,7 +365,12 @@ public IdentifierCaseHandling getQuotedIdentifierHandling() {

@Override
public String applyQuote(final String identifier) {
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
return "\"" + identifier.replace("\"", "\"\"") + "\"";
final String lowercaseIdentifier = convertIdentifierToLowerCase(identifier);
return "\"" + lowercaseIdentifier.replace("\"", "\"\"") + "\"";
}

private String convertIdentifierToLowerCase(final String identifier) {
return identifier.toLowerCase();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ private static SchemaMetadata readMetadata(final SchemaMetadataInfo meta, final
return JdbcMetadataReader.readRemoteMetadata(connection.getAddress(), connection.getUser(),
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
connection.getPassword(), catalog, schema, tables,
JdbcAdapterProperties.getSqlDialectName(meta.getProperties()),
JdbcAdapterProperties.getExceptionHandlingMode(meta.getProperties()));
JdbcAdapterProperties.getExceptionHandlingMode(meta.getProperties()),
JdbcAdapterProperties.getIgnoreErrorList(meta.getProperties()));
}

private static String handleRefresh(final RefreshRequest request, final ExaMetadata meta)
Expand Down Expand Up @@ -165,7 +166,8 @@ private static String handleSetProperty(final SetPropertiesRequest request, fina
connection.getUser(), connection.getPassword(), JdbcAdapterProperties.getCatalog(newSchemaMeta),
JdbcAdapterProperties.getSchema(newSchemaMeta), tableFilter,
JdbcAdapterProperties.getSqlDialectName(newSchemaMeta),
JdbcAdapterProperties.getExceptionHandlingMode(newSchemaMeta));
JdbcAdapterProperties.getExceptionHandlingMode(newSchemaMeta),
JdbcAdapterProperties.getIgnoreErrorList(newSchemaMeta));
return ResponseJsonSerializer.makeSetPropertiesResponse(remoteMeta);
}
return ResponseJsonSerializer.makeSetPropertiesResponse(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.exasol.adapter.jdbc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import com.exasol.ExaConnectionAccessException;
import com.exasol.ExaConnectionInformation;
Expand Down Expand Up @@ -43,6 +40,7 @@ public final class JdbcAdapterProperties {
static final String PROP_EXCLUDED_CAPABILITIES = "EXCLUDED_CAPABILITIES";
static final String PROP_EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
static final String PROP_LOG_LEVEL = "LOG_LEVEL";
static final String PROP_IGNORE_ERROR_LIST = "IGNORE_ERRORS";

private static final String DEFAULT_LOG_LEVEL = "INFO";

Expand All @@ -64,21 +62,37 @@ private static String getProperty(final Map<String, String> properties, final St
}
}

private static String getProperty(final Map<String, String> properties, final String name) {
return getProperty(properties, name, "");
}

public static List<String> getIgnoreErrorList(final Map<String, String> properties) {
snehlsen marked this conversation as resolved.
Show resolved Hide resolved
final String ignoreErrors = getProperty(properties, PROP_IGNORE_ERROR_LIST);
if (ignoreErrors.trim().isEmpty()) {
return Collections.emptyList();
}
return Arrays.
stream(ignoreErrors.split(","))
.map(String::trim)
.map(String::toUpperCase)
.collect(Collectors.toList());
}

public static String getCatalog(final Map<String, String> properties) {
return getProperty(properties, PROP_CATALOG_NAME, "");
return getProperty(properties, PROP_CATALOG_NAME);
}

public static String getSchema(final Map<String, String> properties) {
return getProperty(properties, PROP_SCHEMA_NAME, "");
return getProperty(properties, PROP_SCHEMA_NAME);
}

public static boolean userSpecifiedConnection(final Map<String, String> properties) {
final String connName = getProperty(properties, PROP_CONNECTION_NAME, "");
final String connName = getProperty(properties, PROP_CONNECTION_NAME);
return (connName != null && !connName.isEmpty());
}

public static String getConnectionName(final Map<String, String> properties) {
final String connName = getProperty(properties, PROP_CONNECTION_NAME, "");
final String connName = getProperty(properties, PROP_CONNECTION_NAME);
assert (connName != null && !connName.isEmpty());
return connName;
}
Expand All @@ -90,7 +104,7 @@ public static String getConnectionName(final Map<String, String> properties) {
*/
public static ExaConnectionInformation getConnectionInformation(final Map<String, String> properties,
final ExaMetadata exaMeta) {
final String connName = getProperty(properties, PROP_CONNECTION_NAME, "");
final String connName = getProperty(properties, PROP_CONNECTION_NAME);
if (connName != null && !connName.isEmpty()) {
try {
final ExaConnectionInformation connInfo = exaMeta.getConnection(connName);
Expand All @@ -112,12 +126,25 @@ public static void checkPropertyConsistency(final Map<String, String> properties
checkMandatoryProperties(properties);
checkImportPropertyConsistency(properties, PROP_IMPORT_FROM_EXA, PROP_EXA_CONNECTION_STRING);
checkImportPropertyConsistency(properties, PROP_IMPORT_FROM_ORA, PROP_ORA_CONNECTION_NAME);
checkIgnoreErrors(properties);
}

private static void checkIgnoreErrors(final Map<String, String> properties) throws InvalidPropertyException {
final String dialect = getSqlDialectName(properties);
List<String> errorsToIgnore = getIgnoreErrorList(properties);
for (String errorToIgnore : errorsToIgnore) {
if (!errorToIgnore.startsWith(dialect)) {
throw new InvalidPropertyException(
"Error " + errorToIgnore + " cannot be ignored in " + dialect + " dialect."
);
}
}
}

private static void checkImportPropertyConsistency(final Map<String, String> properties,
final String propImportFromX, final String propConnection) throws InvalidPropertyException {
final boolean isImport = getProperty(properties, propImportFromX, "").toUpperCase().equals("TRUE");
final boolean connectionIsEmpty = getProperty(properties, propConnection, "").isEmpty();
final boolean isImport = getProperty(properties, propImportFromX).toUpperCase().equals("TRUE");
final boolean connectionIsEmpty = getProperty(properties, propConnection).isEmpty();
if (isImport) {
if (connectionIsEmpty) {
throw new InvalidPropertyException(
Expand Down Expand Up @@ -206,23 +233,23 @@ private static void checkMandatoryProperties(final Map<String, String> propertie
}

public static boolean isImportFromExa(final Map<String, String> properties) {
return getProperty(properties, PROP_IMPORT_FROM_EXA, "").toUpperCase().equals("TRUE");
return getProperty(properties, PROP_IMPORT_FROM_EXA).toUpperCase().equals("TRUE");
}

public static boolean isImportFromOra(final Map<String, String> properties) {
return getProperty(properties, PROP_IMPORT_FROM_ORA, "").toUpperCase().equals("TRUE");
return getProperty(properties, PROP_IMPORT_FROM_ORA).toUpperCase().equals("TRUE");
}

public static String getExaConnectionString(final Map<String, String> properties) {
return getProperty(properties, PROP_EXA_CONNECTION_STRING, "");
return getProperty(properties, PROP_EXA_CONNECTION_STRING);
}

public static String getOraConnectionName(final Map<String, String> properties) {
return getProperty(properties, PROP_ORA_CONNECTION_NAME, "");
return getProperty(properties, PROP_ORA_CONNECTION_NAME);
}

public static List<String> getTableFilter(final Map<String, String> properties) {
final String tableNames = getProperty(properties, PROP_TABLES, "");
final String tableNames = getProperty(properties, PROP_TABLES);
if (!tableNames.isEmpty()) {
final List<String> tables = Arrays.asList(tableNames.split(","));
for (int i = 0; i < tables.size(); ++i) {
Expand All @@ -235,24 +262,24 @@ public static List<String> getTableFilter(final Map<String, String> properties)
}

public static String getExcludedCapabilities(final Map<String, String> properties) {
return getProperty(properties, PROP_EXCLUDED_CAPABILITIES, "");
return getProperty(properties, PROP_EXCLUDED_CAPABILITIES);
}

public static String getDebugAddress(final Map<String, String> properties) {
return getProperty(properties, PROP_DEBUG_ADDRESS, "");
return getProperty(properties, PROP_DEBUG_ADDRESS);
}

public static boolean isLocal(final Map<String, String> properties) {
return getProperty(properties, PROP_IS_LOCAL, "").toUpperCase().equals("TRUE");
return getProperty(properties, PROP_IS_LOCAL).toUpperCase().equals("TRUE");
}

public static String getSqlDialectName(final Map<String, String> properties) {
return getProperty(properties, PROP_SQL_DIALECT, "");
return getProperty(properties, PROP_SQL_DIALECT);
}

public static SqlDialect getSqlDialect(final Map<String, String> properties, final SqlDialectContext dialectContext)
throws InvalidPropertyException {
final String dialectName = getProperty(properties, PROP_SQL_DIALECT, "");
final String dialectName = getProperty(properties, PROP_SQL_DIALECT);
final SqlDialect dialect = SqlDialects.getInstance().getDialectInstanceForNameWithContext(dialectName,
dialectContext);
if (dialect == null) {
Expand All @@ -263,7 +290,7 @@ public static SqlDialect getSqlDialect(final Map<String, String> properties, fin
}

public static ExceptionHandlingMode getExceptionHandlingMode(final Map<String, String> properties) {
final String propertyValue = getProperty(properties, PROP_EXCEPTION_HANDLING, "");
final String propertyValue = getProperty(properties, PROP_EXCEPTION_HANDLING);
if (propertyValue == null || propertyValue.isEmpty()) {
return ExceptionHandlingMode.NONE;
}
Expand Down
Loading