Skip to content

Commit

Permalink
#124: Started rework on metadata reading.
Browse files Browse the repository at this point in the history
  • Loading branch information
redcatbear committed Mar 28, 2019
1 parent 9a1b6c0 commit a453c9c
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.exasol.adapter.jdbc;

import static com.exasol.adapter.jdbc.RemoteMetadataReaderConstants.*;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

import com.exasol.adapter.metadata.ColumnMetadata;
import com.exasol.adapter.metadata.DataType;

public class ColumnMetadataReader {
private static final String COLUMN_NAME_COLUMN = "COLUMN_NAME";
private final Connection connection;

public ColumnMetadataReader(final Connection connection) {
this.connection = connection;
}

public List<ColumnMetadata> mapColumns(final String tableName) throws SQLException {
final List<ColumnMetadata> columns = new ArrayList<>();
try (final ResultSet remoteColumns = this.connection.getMetaData().getColumns(ANY_CATALOG, ANY_SCHEMA,
tableName, ANY_COLUMN)) {
while (remoteColumns.next()) {
final ColumnMetadata metadata = mapColumn(remoteColumns);
columns.add(metadata);
}
}
return columns;
}

private ColumnMetadata mapColumn(final ResultSet remoteColumn) throws SQLException {
return ColumnMetadata.builder() //
.name(remoteColumn.getString(COLUMN_NAME_COLUMN)) //
.type(DataType.createBool()) // FIXME
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
import com.google.common.base.Joiner;

/**
* TODO Find good solutions to handle tables with unsupported data types, or
* tables that generate exceptions. Ideas: Skip such tables by adding a boolean
* property like IGNORE_INVALID_TABLES.
* TODO Find good solutions to handle tables with unsupported data types, or tables that generate exceptions. Ideas:
* Skip such tables by adding a boolean property like IGNORE_INVALID_TABLES.
*
* @deprecated Use MetadataReader instead
*/
@Deprecated
public class JdbcMetadataReader {
private static final Logger LOGGER = Logger.getLogger(JdbcMetadataReader.class.getName());

public static SchemaMetadata readRemoteMetadata(final String connectionString, final String user,
final String password, String catalog, String schema, final List<String> tableFilter,
final String dialectName, final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode, final List<String> ignoreErrorList,
PostgreSQLIdentifierMapping postgreSQLIdentifierMapping)
final String password, String catalog, String schema, final List<String> tableFilter,
final String dialectName, final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode,
final List<String> ignoreErrorList, final PostgreSQLIdentifierMapping postgreSQLIdentifierMapping)
throws SQLException, AdapterException {
assert (catalog != null);
assert (schema != null);
Expand All @@ -45,7 +47,8 @@ public static SchemaMetadata readRemoteMetadata(final String connectionString, f

schema = findSchema(schema, dbMeta, dialect);

final List<TableMetadata> tables = findTables(catalog, schema, tableFilter, dbMeta, dialect, exceptionMode, ignoreErrorList);
final List<TableMetadata> tables = findTables(catalog, schema, tableFilter, dbMeta, dialect, exceptionMode,
ignoreErrorList);

return new SchemaMetadata(SchemaAdapterNotes.serialize(schemaAdapterNotes), tables);
} catch (final SQLException e) {
Expand All @@ -55,7 +58,7 @@ public static SchemaMetadata readRemoteMetadata(final String connectionString, f
}

private static Connection establishConnection(final String connectionString, final String user,
final String password) throws SQLException {
final String password) throws SQLException {
LOGGER.fine(() -> "Establishing connection with paramters: " + connectionString);
final java.util.Properties info = new java.util.Properties();
if (user != null) {
Expand Down Expand Up @@ -115,8 +118,8 @@ private static String findCatalog(final String catalog, final DatabaseMetaData d
res.close();
}
}
if (dialect.supportsJdbcCatalogs() == SqlDialect.SchemaOrCatalogSupport.SUPPORTED
|| dialect.supportsJdbcCatalogs() == SqlDialect.SchemaOrCatalogSupport.UNKNOWN) {
if ((dialect.supportsJdbcCatalogs() == SqlDialect.SchemaOrCatalogSupport.SUPPORTED)
|| (dialect.supportsJdbcCatalogs() == SqlDialect.SchemaOrCatalogSupport.UNKNOWN)) {
if (foundCatalog) {
return catalog;
} else {
Expand Down Expand Up @@ -199,8 +202,8 @@ private static String findSchema(final String schema, final DatabaseMetaData dbM
}
}

if (dialect.supportsJdbcSchemas() == SqlDialect.SchemaOrCatalogSupport.SUPPORTED
|| dialect.supportsJdbcSchemas() == SqlDialect.SchemaOrCatalogSupport.UNKNOWN) {
if ((dialect.supportsJdbcSchemas() == SqlDialect.SchemaOrCatalogSupport.SUPPORTED)
|| (dialect.supportsJdbcSchemas() == SqlDialect.SchemaOrCatalogSupport.UNKNOWN)) {
if (foundSchema) {
return schema;
} else {
Expand Down Expand Up @@ -241,11 +244,12 @@ private static String findSchema(final String schema, final DatabaseMetaData dbM
}

private static List<TableMetadata> findTables(final String catalog, final String schema,
final List<String> tableFilter, final DatabaseMetaData dbMeta, final SqlDialect dialect,
final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode, List<String> ignoreErrorList) throws SQLException {
final List<String> tableFilter, final DatabaseMetaData dbMeta, final SqlDialect dialect,
final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode, final List<String> ignoreErrorList)
throws SQLException {
final List<TableMetadata> tables = new ArrayList<>();

final String[] supportedTableTypes = {"TABLE", "VIEW", "SYSTEM TABLE"};
final String[] supportedTableTypes = { "TABLE", "VIEW", "SYSTEM TABLE" };

final List<SqlDialect.MappedTable> tablesMapped = new ArrayList<>();
try (final ResultSet resTables = dbMeta.getTables(catalog, schema, null, supportedTableTypes)) {
Expand Down Expand Up @@ -293,13 +297,13 @@ private static List<TableMetadata> findTables(final String catalog, final String
}

private static boolean identifiersAreCaseInsensitive(final SqlDialect dialect) {
return (dialect.getQuotedIdentifierHandling() == dialect.getUnquotedIdentifierHandling())
&& dialect.getQuotedIdentifierHandling() != SqlDialect.IdentifierCaseHandling.INTERPRET_CASE_SENSITIVE;
return (dialect.getQuotedIdentifierHandling() == dialect.getUnquotedIdentifierHandling()) && (dialect
.getQuotedIdentifierHandling() != SqlDialect.IdentifierCaseHandling.INTERPRET_CASE_SENSITIVE);
}

private static List<ColumnMetadata> readColumns(final DatabaseMetaData dbMeta, final String catalog,
final String schema, final String table, final SqlDialect dialect,
final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode) throws SQLException {
final String schema, final String table, final SqlDialect dialect,
final JdbcAdapterProperties.ExceptionHandlingMode exceptionMode) throws SQLException {
final List<ColumnMetadata> columns = new ArrayList<>();
try {
final ResultSet cols = dbMeta.getColumns(catalog, schema, table, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.exasol.adapter.jdbc;

import static com.exasol.adapter.jdbc.RemoteMetadataReaderConstants.*;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import com.exasol.adapter.metadata.*;

/**
* This class implements basic reading of database metadata from JDBC
*
* <p>
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/sql/DatabaseMetaData.html">java.sql.DatabaseMetaData</a>
*/
public class RemoteMetadataReader {
private static final Logger LOGGER = Logger.getLogger(RemoteMetadataReader.class.getName());
private static final String TABLE_NAME_COLUMN = "TABLE_NAME";

private final Connection connection;

public RemoteMetadataReader(final Connection connection) {
this.connection = connection;
}

public SchemaMetadata readRemoteSchemaMetadata() throws RemoteMetadataReaderException {
try {
final DatabaseMetaData remoteMetadata = this.connection.getMetaData();
final String adapterNotes = null;
final List<TableMetadata> tables = extractTableMetadata(remoteMetadata);
return new SchemaMetadata(adapterNotes, tables);
} catch (final SQLException exception) {
throw new RemoteMetadataReaderException("Unable to read remote schema metadata.", exception);
}
}

private List<TableMetadata> extractTableMetadata(final DatabaseMetaData remoteMetadata) throws SQLException {
final List<TableMetadata> translatedTables = new ArrayList<>();
try (final ResultSet remoteTables = remoteMetadata.getTables(ANY_CATALOG, ANY_SCHEMA, ANY_TABLE, ANY_TYPE)) {
mapTables(translatedTables, remoteTables);
return translatedTables;
}
}

private void mapTables(final List<TableMetadata> translatedTables, final ResultSet remoteTables)
throws SQLException {
while (remoteTables.next()) {
final TableMetadata tableMetadata = mapTable(remoteTables);
translatedTables.add(tableMetadata);
}
}

private TableMetadata mapTable(final ResultSet remoteTable) throws SQLException {
final String tableName = remoteTable.getString(TABLE_NAME_COLUMN);
LOGGER.info(() -> "Mapping metadata for table \"" + tableName + "\"");
final String adapterNotes = null;
final List<ColumnMetadata> columns = new ColumnMetadataReader(this.connection).mapColumns(tableName);
final String comment = null;
return new TableMetadata(tableName, adapterNotes, columns, comment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.exasol.adapter.jdbc;

public final class RemoteMetadataReaderConstants {
private RemoteMetadataReaderConstants() {
// prevent instantiation
}

public static final String ANY_CATALOG = null;
public static final String ANY_SCHEMA = null;
public static final String ANY_TABLE = "%";
public static final String[] ANY_TYPE = null;
public static final String ANY_COLUMN = "%";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.exasol.adapter.jdbc;

/**
* This class represents exceptional conditions that occur during attempts to extract schema metadata from a remote data
* source.
*/
public class RemoteMetadataReaderException extends Exception {

public RemoteMetadataReaderException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.exasol.adapter.jdbc;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.when;

import java.sql.*;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;

import com.exasol.adapter.metadata.*;

class RemoteMetadataReaderTest {
@Mock
private Connection connectionMock;
@Mock
private DatabaseMetaData remoteMetadataMock;

@BeforeEach
void beforeEach() throws SQLException {
MockitoAnnotations.initMocks(this);
when(this.connectionMock.getMetaData()).thenReturn(this.remoteMetadataMock);
}

@Test
void testReadEmptyRemoteMetadata() throws RemoteMetadataReaderException, SQLException {
mockGetAllTablesReturnsEmptyList();
final RemoteMetadataReader reader = new RemoteMetadataReader(this.connectionMock);
final SchemaMetadata metadata = reader.readRemoteSchemaMetadata();
assertThat(metadata.getTables(), emptyIterableOf(TableMetadata.class));
}

private void mockGetAllTablesReturnsEmptyList() throws SQLException {
final ResultSet remoteTables = Mockito.mock(ResultSet.class);
when(remoteTables.next()).thenReturn(false);
mockGetAllTables(remoteTables);
}

@Test
void testReadRemoteMetadata() throws RemoteMetadataReaderException, SQLException {
mockGetColumnsCalls();
mockGetTableCalls();
final RemoteMetadataReader reader = new RemoteMetadataReader(this.connectionMock);
final SchemaMetadata metadata = reader.readRemoteSchemaMetadata();
final List<TableMetadata> tables = metadata.getTables();
final TableMetadata tableAMetadata = tables.get(0);
final TableMetadata tableBMetadata = tables.get(1);
final List<ColumnMetadata> columnsAMetadata = tableAMetadata.getColumns();
final List<ColumnMetadata> columnsBMetadata = tableBMetadata.getColumns();
assertAll(() -> assertThat(tables, iterableWithSize(2)),
() -> assertThat(tableAMetadata.getName(), equalTo("TABLE_A")),
() -> assertThat(columnsAMetadata, iterableWithSize(2)),
() -> assertThat(tableBMetadata.getName(), equalTo("TABLE_B")),
() -> assertThat(columnsBMetadata, iterableWithSize(3)));
}

private void mockGetColumnsCalls() throws SQLException {
mockTableA();
mockTableB();
}

private void mockTableA() throws SQLException {
final ResultSet tableAColumns = Mockito.mock(ResultSet.class);
when(tableAColumns.next()).thenReturn(true, true, false);
when(tableAColumns.getString("COLUMN_NAME")).thenReturn("COLUMN_A1", "COLUMN_A2");
when(tableAColumns.getBoolean("NULLABLE")).thenReturn(true, false);
when(this.remoteMetadataMock.getColumns(null, null, "TABLE_A", "%")).thenReturn(tableAColumns);
}

private void mockTableB() throws SQLException {
final ResultSet tableBColumns = Mockito.mock(ResultSet.class);
when(tableBColumns.next()).thenReturn(true, true, true, false);
when(tableBColumns.getString("COLUMN_NAME")).thenReturn("COLUMN_B1", "COLUMN_B2", "COLUMN_B3");
when(tableBColumns.getBoolean("NULLABLE")).thenReturn(false, true, false);
when(this.remoteMetadataMock.getColumns(null, null, "TABLE_B", "%")).thenReturn(tableBColumns);
}

private void mockGetTableCalls() throws SQLException {
final ResultSet tables = Mockito.mock(ResultSet.class);
when(tables.next()).thenReturn(true, true, false);
when(tables.getString("TABLE_NAME")).thenReturn("TABLE_A", "TABLE_B");
mockGetAllTables(tables);
}

private void mockGetAllTables(final ResultSet tables) throws SQLException {
when(this.remoteMetadataMock.getTables(null, null, "%", null)).thenReturn(tables);
}
}

0 comments on commit a453c9c

Please sign in to comment.