Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.alfasoftware.morf.jdbc.DatabaseMetaDataProviderUtils.shouldIgnoreIndex;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
Expand Down Expand Up @@ -41,9 +42,19 @@ public class PostgreSQLMetaDataProvider extends DatabaseMetaDataProvider impleme
private static final Log log = LogFactory.getLog(PostgreSQLMetaDataProvider.class);

private static final Pattern REALNAME_COMMENT_MATCHER = Pattern.compile(".*"+PostgreSQLDialect.REAL_NAME_COMMENT_LABEL+":\\[([^\\]]*)\\](/TYPE:\\[([^\\]]*)\\])?.*");
private static final String EXTENSION_RELATIONS_SQL = "SELECT c.relname"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
+ " JOIN pg_catalog.pg_depend d ON d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass"
+ " AND d.objid = c.oid AND d.objsubid = 0"
+ " JOIN pg_catalog.pg_extension e ON d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass"
+ " AND d.refobjid = e.oid"
+ " WHERE d.deptype = 'e'";
private static final String SCHEMA_EXTENSION_RELATIONS_SQL = EXTENSION_RELATIONS_SQL + " AND n.nspname = ?";

private final Supplier<Map<AName, RealName>> allIndexNames = Suppliers.memoize(this::loadAllIndexNames);
private final Supplier<Map<String, List<Index>>> allIgnoredIndexes = Suppliers.memoize(this::loadIgnoredIndexes);
private final Supplier<Set<AName>> extensionRelationNames = Suppliers.memoize(this::loadExtensionRelationNames);
private final Set<RealName> allIgnoredIndexesTables = new HashSet<>();

public PostgreSQLMetaDataProvider(Connection connection, String schemaName) {
Expand All @@ -57,6 +68,48 @@ protected boolean isPrimaryKeyIndex(RealName indexName) {
}


@Override
protected boolean isSystemTable(RealName tableName) {
return extensionRelationNames.get().contains(tableName);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling super as well would be good pracitise :)


@Override
protected boolean isSystemView(RealName viewName) {
return extensionRelationNames.get().contains(viewName);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there was something about "SystemView" before, that is good :)
Again, or super.



private Set<AName> loadExtensionRelationNames() {
try {
if (StringUtils.isBlank(schemaName)) {
try (PreparedStatement statement = connection.prepareStatement(EXTENSION_RELATIONS_SQL)) {
return readExtensionRelationNames(statement);
}
}

try (PreparedStatement statement = connection.prepareStatement(SCHEMA_EXTENSION_RELATIONS_SQL)) {
statement.setString(1, schemaName);
return readExtensionRelationNames(statement);
}
}
catch (SQLException e) {
throw new RuntimeSqlException(e);
}
}


private Set<AName> readExtensionRelationNames(PreparedStatement statement) throws SQLException {
Set<AName> relationNames = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
relationNames.add(named(resultSet.getString(1)));
}
}
return relationNames;
}


@Override
protected DataType dataTypeFromSqlType(int sqlType, String typeName, int width) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
public class TestPostgreSqlMetaDataProvider {
private static final String TABLE_NAME = "AREALTABLE";
private static final String TEST_SCHEMA = "TestSchema";
private static final String EXTENSION_RELATIONS_SQL = "SELECT c.relname"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
+ " JOIN pg_catalog.pg_depend d ON d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass"
+ " AND d.objid = c.oid AND d.objsubid = 0"
+ " JOIN pg_catalog.pg_extension e ON d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass"
+ " AND d.refobjid = e.oid"
+ " WHERE d.deptype = 'e'";
private static final String SCHEMA_EXTENSION_RELATIONS_SQL = EXTENSION_RELATIONS_SQL + " AND n.nspname = ?";

private final DataSource dataSource = mock(DataSource.class, RETURNS_SMART_NULLS);
private final Connection connection = mock(Connection.class, RETURNS_SMART_NULLS);
Expand All @@ -69,6 +78,12 @@ public void setup() {
@Before
public void before() throws SQLException {
when(dataSource.getConnection()).thenReturn(connection);

PreparedStatement extensionStatement = mock(PreparedStatement.class, RETURNS_SMART_NULLS);
ResultSet extensionRelations = mock(ResultSet.class, RETURNS_SMART_NULLS);
when(connection.prepareStatement(SCHEMA_EXTENSION_RELATIONS_SQL)).thenReturn(extensionStatement);
when(extensionStatement.executeQuery()).thenReturn(extensionRelations);
when(extensionRelations.next()).thenReturn(false);
}


Expand All @@ -95,6 +110,70 @@ public void testLoadSequences() throws SQLException {
verify(statement).setString(1, TEST_SCHEMA);
}


/**
* Checks that tables and views belonging to extensions are excluded from application metadata.
*
* @throws SQLException exception
*/
@Test
public void testExtensionRelationsAreSystemRelations() throws SQLException {
// Given
PreparedStatement extensionStatement = mock(PreparedStatement.class, RETURNS_SMART_NULLS);
ResultSet extensionRelations = mock(ResultSet.class, RETURNS_SMART_NULLS);
when(connection.prepareStatement(SCHEMA_EXTENSION_RELATIONS_SQL)).thenReturn(extensionStatement);
when(extensionStatement.executeQuery()).thenReturn(extensionRelations);
when(extensionRelations.next()).thenReturn(true, true, false);
when(extensionRelations.getString(1)).thenReturn("extension_table", "extension_view");

DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class, RETURNS_SMART_NULLS);
ResultSet tables = mockRelations("extension_table", "application_table");
ResultSet views = mockRelations("extension_view", "application_view");
when(connection.getMetaData()).thenReturn(databaseMetaData);
when(databaseMetaData.getTables(null, TEST_SCHEMA, null, new String[] { "TABLE" }))
.thenReturn(tables);
when(databaseMetaData.getTables(null, TEST_SCHEMA, null, new String[] { "VIEW" }))
.thenReturn(views);

// When
Schema postgresMetaDataProvider = postgres.openSchema(connection, "TestDatabase", TEST_SCHEMA);

// Then
assertThat("Extension tables should be excluded", postgresMetaDataProvider.tableNames(), contains("application_table"));
assertThat("Extension views should be excluded", postgresMetaDataProvider.viewNames(), contains("application_view"));
verify(extensionStatement).setString(1, TEST_SCHEMA);
verify(extensionStatement).executeQuery();
}


/**
* Checks that extension relations can be loaded when no schema filter is supplied.
*
* @throws SQLException exception
*/
@Test
public void testExtensionRelationsWithoutSchemaFilter() throws SQLException {
// Given
PreparedStatement extensionStatement = mock(PreparedStatement.class, RETURNS_SMART_NULLS);
ResultSet extensionRelations = mock(ResultSet.class, RETURNS_SMART_NULLS);
when(connection.prepareStatement(EXTENSION_RELATIONS_SQL)).thenReturn(extensionStatement);
when(extensionStatement.executeQuery()).thenReturn(extensionRelations);
when(extensionRelations.next()).thenReturn(false);

DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class, RETURNS_SMART_NULLS);
ResultSet tables = mockRelations("application_table_one", "application_table_two");
when(connection.getMetaData()).thenReturn(databaseMetaData);
when(databaseMetaData.getTables(null, null, null, new String[] { "TABLE" })).thenReturn(tables);

// When
Schema postgresMetaDataProvider = new PostgreSQLMetaDataProvider(connection, null);

// Then
assertThat("Application tables should be retained", postgresMetaDataProvider.tableNames(),
contains("application_table_one", "application_table_two"));
verify(extensionStatement).executeQuery();
}

/**
* Checks the SQL run for retrieving sequences information
*
Expand Down Expand Up @@ -181,6 +260,15 @@ public void testLoadAllIgnoredIndexes() throws SQLException {
}


private ResultSet mockRelations(String firstRelation, String secondRelation) throws SQLException {
ResultSet resultSet = mock(ResultSet.class, RETURNS_SMART_NULLS);
when(resultSet.next()).thenReturn(true, true, false);
when(resultSet.getString(2)).thenReturn(TEST_SCHEMA);
when(resultSet.getString(3)).thenReturn(firstRelation, firstRelation, secondRelation, secondRelation);
return resultSet;
}


/**
* Mockito {@link Answer} that returns a mock result set with a given number of resultRows.
*/
Expand Down