-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement feature switch for data type converter
- Loading branch information
1 parent
309cd02
commit 9c997d0
Showing
13 changed files
with
294 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.exasol.adapter.dialects.exasol; | ||
|
||
import com.exasol.adapter.dialects.SqlDialect; | ||
import com.exasol.adapter.dialects.rewriting.AbstractQueryRewriter; | ||
import com.exasol.adapter.jdbc.RemoteMetadataReader; | ||
|
||
/** | ||
* Exasol-specific query rewriter for {@code IMPORT FROM EXA} that does not add data types to the pushdown query. Data | ||
* types like {@code HASHTYPE} will be reported as {@code VARCHAR}. | ||
*/ | ||
public class ExasolFromExaQueryRewriter extends AbstractQueryRewriter { | ||
|
||
/** | ||
* Create a new instance of the {@link ExasolFromExaQueryRewriter}. | ||
* | ||
* @param dialect dialect | ||
* @param remoteMetadataReader remote metadata reader | ||
*/ | ||
public ExasolFromExaQueryRewriter(final SqlDialect dialect, final RemoteMetadataReader remoteMetadataReader) { | ||
super(dialect, remoteMetadataReader, new ExasolConnectionDefinitionBuilder()); | ||
} | ||
|
||
@Override | ||
protected String generateImportStatement(final String connectionDefinition, final String pushdownQuery) { | ||
return "IMPORT FROM EXA " + connectionDefinition + " STATEMENT '" + pushdownQuery.replace("'", "''") + "'"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.exasol.adapter.dialects.exasol; | ||
|
||
import static com.exasol.adapter.AdapterProperties.CONNECTION_NAME_PROPERTY; | ||
import static com.exasol.adapter.dialects.exasol.ExasolProperties.EXASOL_CONNECTION_PROPERTY; | ||
import static com.exasol.adapter.dialects.exasol.ExasolProperties.EXASOL_IMPORT_PROPERTY; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.when; | ||
|
||
import java.sql.Connection; | ||
import java.sql.SQLException; | ||
import java.util.*; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import com.exasol.ExaMetadata; | ||
import com.exasol.adapter.AdapterException; | ||
import com.exasol.adapter.AdapterProperties; | ||
import com.exasol.adapter.dialects.*; | ||
import com.exasol.adapter.jdbc.ConnectionFactory; | ||
import com.exasol.adapter.jdbc.RemoteMetadataReader; | ||
import com.exasol.adapter.metadata.DataType; | ||
import com.exasol.adapter.sql.TestSqlStatementFactory; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class ExasolFromExaQueryRewriterTest { | ||
private static final List<DataType> EMPTY_SELECT_LIST_DATA_TYPES = Collections.emptyList(); | ||
@Mock | ||
private RemoteMetadataReader metadataReaderMock; | ||
@Mock | ||
private ConnectionFactory connectionFactoryMock; | ||
@Mock | ||
private ExaMetadata exaMetadataMock; | ||
@Mock | ||
private SqlDialect dialectMock; | ||
@Mock | ||
private SqlGenerator sqlGeneratorMock; | ||
@Mock | ||
private Connection connectionMock; | ||
|
||
@Test | ||
void rewritePushdownQuery() throws AdapterException, SQLException { | ||
final AdapterProperties properties = createAdapterProperties(); | ||
final SqlDialect dialect = new ExasolSqlDialect(connectionFactoryMock, properties); | ||
final QueryRewriter queryRewriter = new ExasolFromExaQueryRewriter(dialect, | ||
new ExasolMetadataReader(connectionMock, properties)); | ||
assertThat( | ||
queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, | ||
exaMetadataMock, properties), | ||
equalTo("IMPORT FROM EXA AT \"THE_EXA_CONNECTION\" STATEMENT 'SELECT 1 FROM \"DUAL\"'")); | ||
} | ||
|
||
private AdapterProperties createAdapterProperties() { | ||
return new AdapterProperties(Map.of(EXASOL_IMPORT_PROPERTY, "true", // | ||
CONNECTION_NAME_PROPERTY, "exasol_connection", // | ||
EXASOL_CONNECTION_PROPERTY, "THE_EXA_CONNECTION", // | ||
"GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA", "false")); | ||
} | ||
|
||
@Test | ||
void rewritePushdownQueryEscapesSingleQuotes() throws AdapterException, SQLException { | ||
final AdapterProperties properties = createAdapterProperties(); | ||
when(dialectMock.getSqlGenerator(any())).thenReturn(sqlGeneratorMock); | ||
when(sqlGeneratorMock.generateSqlFor(any())).thenReturn("string ' with '' quotes \"..."); | ||
final QueryRewriter queryRewriter = new ExasolFromExaQueryRewriter(dialectMock, | ||
new ExasolMetadataReader(connectionMock, properties)); | ||
assertThat( | ||
queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, | ||
exaMetadataMock, properties), | ||
equalTo("IMPORT FROM EXA AT \"THE_EXA_CONNECTION\" STATEMENT 'string '' with '''' quotes \"...'")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package com.exasol.adapter.dialects.exasol; | ||
Check failure on line 1 in src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java GitHub Actions / Test ReportExasolSqlDialectExaConnectionIT.joinHashtypeTables
Raw output
|
||
|
||
import static com.exasol.adapter.dialects.exasol.ExasolProperties.EXASOL_CONNECTION_PROPERTY; | ||
import static com.exasol.matcher.ResultSetStructureMatcher.table; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.*; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import org.junit.jupiter.api.*; | ||
import org.testcontainers.containers.JdbcDatabaseContainer.NoDriverFoundException; | ||
|
||
import com.exasol.adapter.properties.PropertyValidationException; | ||
import com.exasol.dbbuilder.dialects.Table; | ||
import com.exasol.dbbuilder.dialects.exasol.ConnectionDefinition; | ||
|
||
/** | ||
* This class exercises a set of tests defined in the base class on a local Exasol, using {@code IMPORT} via a EXA | ||
* connection. | ||
* <p> | ||
* In this case the Adapter uses a different (JDBC) connection to attach to the database than the ExaLoader which runs | ||
* this {@code IMPORT}. | ||
* </p> | ||
* <p> | ||
* These tests take the following specialties of a local connection into account: | ||
* </p> | ||
* <ul> | ||
* <li>{@code INTERVAL} types are reported with JDBC type name {@code VARCHAR} in ResultSets</li> | ||
* <li>{@code HASHTYPE} types are reported with JDBC type name {@code VARCHAR} in ResultSets</li> | ||
* <li>{@code GEOMETRY} types are reported with JDBC type name {@code VARCHAR} in ResultSets</li> | ||
* <ul> | ||
*/ | ||
class ExasolSqlDialectExaConnectionIT extends AbstractRemoteExasolVirtualSchemaConnectionIT { | ||
private static final String EXA_CONNECTION_NAME = "EXA_CONNECTION"; | ||
private ConnectionDefinition exaConnection; | ||
|
||
@Override | ||
@BeforeEach | ||
void beforeEach() { | ||
super.beforeEach(); | ||
this.exaConnection = objectFactory.createConnectionDefinition(EXA_CONNECTION_NAME, getTargetAddress(), | ||
this.user.getName(), this.user.getPassword()); | ||
} | ||
|
||
private String getTargetAddress() { | ||
return "127.0.0.1" + "/" + EXASOL.getTlsCertificateFingerprint().orElseThrow() + ":" | ||
+ EXASOL.getDefaultInternalDatabasePort(); | ||
} | ||
|
||
@Override | ||
@AfterEach | ||
void afterEach() { | ||
dropAll(this.exaConnection); | ||
this.exaConnection = null; | ||
super.afterEach(); | ||
} | ||
|
||
@Override | ||
protected Set<String> expectVarcharFor() { | ||
return Set.of("GEOMETRY", "INTERVAL", "INTERVAL YEAR TO MONTH", "INTERVAL DAY TO SECOND", "HASHTYPE"); | ||
} | ||
|
||
@Override | ||
protected Map<String, String> getConnectionSpecificVirtualSchemaProperties() { | ||
return Map.of("IMPORT_FROM_EXA", "true", EXASOL_CONNECTION_PROPERTY, this.exaConnection.getName()); | ||
} | ||
|
||
@Test | ||
void testPasswordNotVisibleInImportFromExa() throws NoDriverFoundException, SQLException { | ||
final Table table = this.sourceSchema.createTable("T1", "C1", "VARCHAR(20)").insert("Hello."); | ||
this.virtualSchema = createVirtualSchema(this.sourceSchema); | ||
final String sql = "SELECT * FROM " + this.virtualSchema.getFullyQualifiedName() + ".\"" + table.getName() | ||
+ "\""; | ||
assertThat(explainVirtual(sql), // | ||
table().row( // | ||
anything(), // | ||
not(anyOf( // | ||
containsString(this.user.getName()), // | ||
containsString(this.user.getPassword()), // | ||
containsString(EXASOL.getUsername()), // | ||
containsString(EXASOL.getPassword()) // | ||
)), // | ||
anything(), // | ||
anything() // | ||
).matches()); | ||
} | ||
|
||
@Test | ||
void testAlterVirtualSchemaTriggersPropertyValidation() throws SQLException { | ||
this.virtualSchema = createVirtualSchema(this.sourceSchema); | ||
final String name = this.virtualSchema.getFullyQualifiedName(); | ||
final SQLException exception = assertThrows(SQLException.class, | ||
() -> query("alter virtual schema {0} set EXA_CONNECTION = Null", name)); | ||
final String expected = PropertyValidationException.class.getName() + ": E-VSCJDBC-17"; | ||
assertThat(exception.getMessage(), containsString(expected)); | ||
} | ||
|
||
private ResultSet explainVirtual(final String sql) throws SQLException { | ||
return query("EXPLAIN VIRTUAL " + sql); | ||
} | ||
|
||
@Override | ||
@Test | ||
void testCharMappingAscii() { | ||
final Table table = createSingleColumnTable("CHAR(20) ASCII").insert("sun").insert("rain"); | ||
assertVirtualTableContents(table, table("VARCHAR").row(pad("sun", 20)).row(pad("rain", 20)).matches()); | ||
} | ||
|
||
@Override | ||
@Test | ||
void testCharMappingUtf8() { | ||
verifyCharMappingUtf8("VARCHAR"); | ||
} | ||
|
||
@Override | ||
@Test | ||
void testCastVarcharToChar() { | ||
castFrom("VARCHAR(20)").to("CHAR(40)").input("Hello.").accept("VARCHAR").verify(pad("Hello.", 40)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.