Skip to content

Commit

Permalink
Add checks for duplicated and intersected foreign keys (#451)
Browse files Browse the repository at this point in the history
* Add DuplicatedForeignKeysCheckOnHost

* Add test for DuplicatedForeignKeysCheckOnHost

* Add IntersectedForeignKeysCheckOnHost

* Add tests for IntersectedForeignKeysCheckOnHost

* Add checks on cluster

* Fix error in tests

* Add code to HealthLogger

* Add new beans to the starter
  • Loading branch information
mfvanek authored Oct 12, 2024
1 parent 76da7f7 commit 2a35da2
Show file tree
Hide file tree
Showing 38 changed files with 799 additions and 50 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ All checks can be divided into 2 groups:
| 17 | Tables with [not valid constraints](https://habr.com/ru/articles/800121/) | **runtime**/static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/check_not_valid_constraints.sql) |
| 18 | B-tree indexes [on array columns](https://habr.com/ru/articles/800121/) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/btree_indexes_on_array_columns.sql) |
| 19 | [Sequence overflow](https://habr.com/ru/articles/800121/) | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/sequence_overflow.sql) |
| 20 | Primary keys with [serial types](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_serial) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/primary_keys_with_serial_types.sql) | |
| 20 | Primary keys with [serial types](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_serial) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/primary_keys_with_serial_types.sql) |
| 21 | Duplicated ([completely identical](https://habr.com/ru/articles/803841/)) foreign keys | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/duplicated_foreign_keys.sql) |
| 22 | Intersected ([partially identical](https://habr.com/ru/articles/803841/)) foreign keys | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/intersected_foreign_keys.sql) |

For raw sql queries see [pg-index-health-sql](https://github.com/mfvanek/pg-index-health-sql) project.

Expand Down Expand Up @@ -126,7 +128,7 @@ All these cases are covered with examples in the [pg-index-health-demo](https://

There is a Spring Boot starter [pg-index-health-test-starter](spring-boot-integration%2Fpg-index-health-test-starter)
for unit/integration testing as well.
More examples you can find in [pg-index-health-spring-boot-demo](https://github.com/mfvanek/pg-index-health-spring-boot-demo) project.
More examples you can find in [pg-index-health-demo](https://github.com/mfvanek/pg-index-health-demo) project.

### Starter installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public Column extractData(@Nonnull final ResultSet resultSet) throws SQLExceptio
return Column.ofNullable(tableName, columnName);
}

/**
* Creates {@code ColumnExtractor} instance.
*
* @return {@code ColumnExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<Column> of() {
return new ColumnExtractor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public ColumnWithSerialType extractData(@Nonnull final ResultSet resultSet) thro
return ColumnWithSerialType.of(column, SerialType.valueFrom(columnType), sequenceName);
}

/**
* Creates {@code ColumnWithSerialTypeExtractor} instance.
*
* @return {@code ColumnWithSerialTypeExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<ColumnWithSerialType> of() {
return new ColumnWithSerialTypeExtractor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2024. Ivan Vakhrushev and others.
* https://github.com/mfvanek/pg-index-health
*
* This file is a part of "pg-index-health" - a Java library for
* analyzing and maintaining indexes health in PostgreSQL databases.
*
* Licensed under the Apache License 2.0
*/

package io.github.mfvanek.pg.checks.extractors;

import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor;
import io.github.mfvanek.pg.model.constraint.DuplicatedForeignKeys;
import io.github.mfvanek.pg.model.constraint.ForeignKey;

import java.sql.ResultSet;
import java.sql.SQLException;
import javax.annotation.Nonnull;

/**
* A mapper from raw data to {@link DuplicatedForeignKeys} model.
*
* @author Ivan Vahrushev
* @see ForeignKeyExtractor
* @since 0.13.1
*/
public class DuplicatedForeignKeysExtractor implements ResultSetExtractor<DuplicatedForeignKeys> {

private final ResultSetExtractor<ForeignKey> defaultExtractor;
private final ResultSetExtractor<ForeignKey> duplicateKeyExtractor;

private DuplicatedForeignKeysExtractor(@Nonnull final String prefix) {
this.defaultExtractor = ForeignKeyExtractor.ofDefault();
this.duplicateKeyExtractor = ForeignKeyExtractor.withPrefix(prefix);
}

/**
* {@inheritDoc}
*/
@Nonnull
@Override
public DuplicatedForeignKeys extractData(@Nonnull final ResultSet resultSet) throws SQLException {
final ForeignKey first = defaultExtractor.extractData(resultSet);
final ForeignKey second = duplicateKeyExtractor.extractData(resultSet);
return DuplicatedForeignKeys.of(first, second);
}

/**
* Creates {@code DuplicatedForeignKeysExtractor} instance.
*
* @param prefix given prefix; must be non-null
* @return {@code DuplicatedForeignKeysExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<DuplicatedForeignKeys> of(@Nonnull final String prefix) {
return new DuplicatedForeignKeysExtractor(prefix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2019-2024. Ivan Vakhrushev and others.
* https://github.com/mfvanek/pg-index-health
*
* This file is a part of "pg-index-health" - a Java library for
* analyzing and maintaining indexes health in PostgreSQL databases.
*
* Licensed under the Apache License 2.0
*/

package io.github.mfvanek.pg.checks.extractors;

import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor;
import io.github.mfvanek.pg.model.column.Column;
import io.github.mfvanek.pg.model.constraint.ForeignKey;
import io.github.mfvanek.pg.utils.ColumnsInForeignKeyParser;

import java.sql.Array;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;

import static io.github.mfvanek.pg.checks.extractors.TableExtractor.TABLE_NAME;

/**
* A mapper from raw data to {@link ForeignKey} model.
*
* @author Ivan Vahrushev
* @since 0.13.1
*/
public class ForeignKeyExtractor implements ResultSetExtractor<ForeignKey> {

public static final String CONSTRAINT_NAME = "constraint_name";

private final String prefix;

private ForeignKeyExtractor(@Nonnull final String prefix) {
this.prefix = Objects.requireNonNull(prefix, "prefix cannot be null");
}

/**
* {@inheritDoc}
*/
@Nonnull
@Override
public ForeignKey extractData(@Nonnull final ResultSet resultSet) throws SQLException {
final String tableName = resultSet.getString(TABLE_NAME);
final String constraintName = resultSet.getString(getConstraintNameField());
final Array columnsArray = resultSet.getArray(getColumnsField());
final String[] rawColumns = (String[]) columnsArray.getArray();
final List<Column> columns = ColumnsInForeignKeyParser.parseRawColumnData(tableName, rawColumns);
return ForeignKey.of(tableName, constraintName, columns);
}

@Nonnull
private String getConstraintNameField() {
if (!prefix.isBlank()) {
return prefix + "_" + CONSTRAINT_NAME;
}
return CONSTRAINT_NAME;
}

@Nonnull
private String getColumnsField() {
if (!prefix.isBlank()) {
return prefix + "_constraint_columns";
}
return "columns";
}

/**
* Creates default {@code ForeignKeyExtractor} instance.
*
* @return {@code ForeignKeyExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<ForeignKey> ofDefault() {
return new ForeignKeyExtractor("");
}

/**
* Creates {@code ForeignKeyExtractor} instance for duplicated/intersected constraint fields with given prefix.
*
* @param prefix given prefix; must be non-null
* @return {@code ForeignKeyExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<ForeignKey> withPrefix(@Nonnull final String prefix) {
return new ForeignKeyExtractor(prefix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public IndexWithColumns extractData(@Nonnull final ResultSet resultSet) throws S
return IndexWithColumns.ofSingle(tableName, indexName, indexSize, column);
}

/**
* Creates {@code IndexWithSingleColumnExtractor} instance.
*
* @return {@code IndexWithSingleColumnExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<IndexWithColumns> of() {
return new IndexWithSingleColumnExtractor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public Table extractData(@Nonnull final ResultSet resultSet) throws SQLException
return Table.of(tableName, tableSize);
}

/**
* Creates {@code TableExtractor} instance.
*
* @return {@code TableExtractor} instance
*/
@Nonnull
public static ResultSetExtractor<Table> of() {
return new TableExtractor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ abstract class AbstractCheckOnHost<T extends DbObject> implements DatabaseCheckO
protected static final String INDEX_SIZE = IndexWithSingleColumnExtractor.INDEX_SIZE;
protected static final String BLOAT_SIZE = "bloat_size";
protected static final String BLOAT_PERCENTAGE = "bloat_percentage";
protected static final String CONSTRAINT_NAME = "constraint_name";

/**
* An original java type representing database object.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2024. Ivan Vakhrushev and others.
* https://github.com/mfvanek/pg-index-health
*
* This file is a part of "pg-index-health" - a Java library for
* analyzing and maintaining indexes health in PostgreSQL databases.
*
* Licensed under the Apache License 2.0
*/

package io.github.mfvanek.pg.checks.host;

import io.github.mfvanek.pg.checks.extractors.DuplicatedForeignKeysExtractor;
import io.github.mfvanek.pg.common.maintenance.Diagnostic;
import io.github.mfvanek.pg.connection.PgConnection;
import io.github.mfvanek.pg.model.PgContext;
import io.github.mfvanek.pg.model.constraint.DuplicatedForeignKeys;

import java.util.List;
import javax.annotation.Nonnull;

/**
* Check for duplicated (completely identical) foreign keys on a specific host.
*
* @author Ivan Vahrushev
* @since 0.13.1
*/
public class DuplicatedForeignKeysCheckOnHost extends AbstractCheckOnHost<DuplicatedForeignKeys> {

/**
* Creates a new {@code DuplicatedForeignKeysCheckOnHost} object.
*
* @param pgConnection connection to the PostgreSQL database, must not be null
*/
public DuplicatedForeignKeysCheckOnHost(@Nonnull final PgConnection pgConnection) {
super(DuplicatedForeignKeys.class, pgConnection, Diagnostic.DUPLICATED_FOREIGN_KEYS);
}

/**
* Returns duplicated (completely identical) foreign keys in the specified schema.
*
* @param pgContext check's context with the specified schema
* @return list of duplicated foreign keys
*/
@Nonnull
@Override
public List<DuplicatedForeignKeys> check(@Nonnull final PgContext pgContext) {
return executeQuery(pgContext, DuplicatedForeignKeysExtractor.of("duplicate"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@

package io.github.mfvanek.pg.checks.host;

import io.github.mfvanek.pg.checks.extractors.ForeignKeyExtractor;
import io.github.mfvanek.pg.common.maintenance.Diagnostic;
import io.github.mfvanek.pg.connection.PgConnection;
import io.github.mfvanek.pg.model.PgContext;
import io.github.mfvanek.pg.model.column.Column;
import io.github.mfvanek.pg.model.constraint.ForeignKey;
import io.github.mfvanek.pg.utils.ColumnsInForeignKeyParser;

import java.sql.Array;
import java.util.List;
import javax.annotation.Nonnull;

Expand All @@ -42,13 +40,6 @@ public ForeignKeysNotCoveredWithIndexCheckOnHost(@Nonnull final PgConnection pgC
@Nonnull
@Override
public List<ForeignKey> check(@Nonnull final PgContext pgContext) {
return executeQuery(pgContext, rs -> {
final String tableName = rs.getString(TABLE_NAME);
final String constraintName = rs.getString(CONSTRAINT_NAME);
final Array columnsArray = rs.getArray("columns");
final String[] rawColumns = (String[]) columnsArray.getArray();
final List<Column> columns = ColumnsInForeignKeyParser.parseRawColumnData(tableName, rawColumns);
return ForeignKey.of(tableName, constraintName, columns);
});
return executeQuery(pgContext, ForeignKeyExtractor.ofDefault());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2024. Ivan Vakhrushev and others.
* https://github.com/mfvanek/pg-index-health
*
* This file is a part of "pg-index-health" - a Java library for
* analyzing and maintaining indexes health in PostgreSQL databases.
*
* Licensed under the Apache License 2.0
*/

package io.github.mfvanek.pg.checks.host;

import io.github.mfvanek.pg.checks.extractors.DuplicatedForeignKeysExtractor;
import io.github.mfvanek.pg.common.maintenance.Diagnostic;
import io.github.mfvanek.pg.connection.PgConnection;
import io.github.mfvanek.pg.model.PgContext;
import io.github.mfvanek.pg.model.constraint.DuplicatedForeignKeys;

import java.util.List;
import javax.annotation.Nonnull;

/**
* Check for intersected (partially identical) foreign keys on a specific host.
*
* @author Ivan Vahrushev
* @see DuplicatedForeignKeysCheckOnHost
* @since 0.13.1
*/
public class IntersectedForeignKeysCheckOnHost extends AbstractCheckOnHost<DuplicatedForeignKeys> {

/**
* Creates a new {@code IntersectedForeignKeysCheckOnHost} object.
*
* @param pgConnection connection to the PostgreSQL database, must not be null
*/
public IntersectedForeignKeysCheckOnHost(@Nonnull final PgConnection pgConnection) {
super(DuplicatedForeignKeys.class, pgConnection, Diagnostic.INTERSECTED_FOREIGN_KEYS);
}

/**
* Returns intersected (partially identical) foreign keys in the specified schema (except completely identical).
*
* @param pgContext check's context with the specified schema
* @return list of intersected foreign keys
* @see DuplicatedForeignKeysCheckOnHost
*/
@Nonnull
@Override
public List<DuplicatedForeignKeys> check(@Nonnull final PgContext pgContext) {
return executeQuery(pgContext, DuplicatedForeignKeysExtractor.of("intersected"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.List;
import javax.annotation.Nonnull;

import static io.github.mfvanek.pg.checks.extractors.ForeignKeyExtractor.CONSTRAINT_NAME;

/**
* Check for not valid constraints on a specific host.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public enum Diagnostic {
NOT_VALID_CONSTRAINTS(ExecutionTopology.ON_PRIMARY, "check_not_valid_constraints.sql", QueryExecutors::executeQueryWithSchema),
BTREE_INDEXES_ON_ARRAY_COLUMNS(ExecutionTopology.ON_PRIMARY, "btree_indexes_on_array_columns.sql", QueryExecutors::executeQueryWithSchema),
SEQUENCE_OVERFLOW(ExecutionTopology.ON_PRIMARY, "sequence_overflow.sql", QueryExecutors::executeQueryWithRemainingPercentageThreshold),
PRIMARY_KEYS_WITH_SERIAL_TYPES(ExecutionTopology.ON_PRIMARY, "primary_keys_with_serial_types.sql", QueryExecutors::executeQueryWithSchema);
PRIMARY_KEYS_WITH_SERIAL_TYPES(ExecutionTopology.ON_PRIMARY, "primary_keys_with_serial_types.sql", QueryExecutors::executeQueryWithSchema),
DUPLICATED_FOREIGN_KEYS(ExecutionTopology.ON_PRIMARY, "duplicated_foreign_keys.sql", QueryExecutors::executeQueryWithSchema),
INTERSECTED_FOREIGN_KEYS(ExecutionTopology.ON_PRIMARY, "intersected_foreign_keys.sql", QueryExecutors::executeQueryWithSchema);

private final ExecutionTopology executionTopology;
private final String sqlQueryFileName;
Expand Down
Loading

0 comments on commit 2a35da2

Please sign in to comment.