From be5dedf087ab8fe9706a4a39f9efcafe2f0a3335 Mon Sep 17 00:00:00 2001 From: Ivan Vakhrushev Date: Fri, 1 Nov 2024 11:12:16 +0400 Subject: [PATCH] Add check "The type of the foreign key must match the type of column in the target table" (#471) * Update queries * Add new check * Javadoc * Add tests * Add logger and tests * Refactor Spring configuration * Refactor Spring configuration * Refactor Spring configuration #3 * Fix tests * Update PMD #2 --- README.md | 53 ++-- ...g-index-health.java-conventions.gradle.kts | 4 +- config/pmd/pmd.xml | 6 +- gradle/libs.versions.toml | 4 +- ...ignKeysNotCoveredWithIndexCheckOnHost.java | 2 + ...eysWithUnmatchedColumnTypeCheckOnHost.java | 53 ++++ .../pg/common/maintenance/Diagnostic.java | 3 +- .../pg/utils/ColumnsInForeignKeyParser.java | 8 +- pg-index-health-core/src/main/resources | 2 +- ...eysNotCoveredWithIndexCheckOnHostTest.java | 48 ++-- ...ithUnmatchedColumnTypeCheckOnHostTest.java | 54 ++++ ...ablesWithoutPrimaryKeyCheckOnHostTest.java | 4 +- .../utils/ColumnsInForeignKeyParserTest.java | 21 ++ ...teForeignKeyOnNullableColumnStatement.java | 8 +- ...CreateTableWithoutPrimaryKeyStatement.java | 4 +- .../connection/ConnectionCredentialsTest.java | 1 - .../pg/connection/PgConnectionImplTest.java | 1 - .../mfvanek/pg/connection/PgHostImplTest.java | 1 - .../health/logger/AbstractHealthLogger.java | 1 + .../health/logger/SimpleLoggingKey.java | 3 +- .../pg/common/maintenance/DatabaseChecks.java | 4 +- .../health/logger/HealthLoggerTest.java | 7 +- .../health/logger/HealthLoggerTestBase.java | 3 +- .../logger/StandardHealthLoggerTest.java | 7 +- .../mfvanek/pg/model/column/ColumnTest.java | 1 - .../column/ColumnWithSerialTypeTest.java | 1 - .../pg/model/constraint/ConstraintTest.java | 1 - .../constraint/DuplicatedForeignKeysTest.java | 1 - .../pg/model/constraint/ForeignKeyTest.java | 1 - .../pg/model/function/StoredFunctionTest.java | 1 - .../pg/model/index/DuplicatedIndexesTest.java | 1 - .../mfvanek/pg/model/index/IndexTest.java | 1 - .../pg/model/index/IndexWithBloatTest.java | 1 - .../pg/model/index/IndexWithColumnsTest.java | 1 - .../pg/model/index/IndexWithNullsTest.java | 1 - .../pg/model/index/IndexWithSizeTest.java | 1 - .../pg/model/index/UnusedIndexTest.java | 1 - .../pg/model/object/AnyObjectTest.java | 1 - .../mfvanek/pg/model/table/TableTest.java | 1 - .../pg/model/table/TableWithBloatTest.java | 1 - .../table/TableWithMissingIndexTest.java | 1 - .../mfvanek/pg/settings/PgParamImplTest.java | 1 - ...WithUnmatchedColumnTypeCheckOnCluster.java | 36 +++ ...NotCoveredWithIndexCheckOnClusterTest.java | 51 ++-- ...UnmatchedColumnTypeCheckOnClusterTest.java | 53 ++++ ...esWithoutPrimaryKeyCheckOnClusterTest.java | 3 +- ...abaseStructureChecksAutoConfiguration.java | 248 ++++++++++++++++++ ...abaseStructureHealthAutoConfiguration.java | 241 +---------------- .../main/resources/META-INF/spring.factories | 1 - ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../pg/spring/AutoConfigurationTestBase.java | 5 +- ...eHealthAutoConfigurationFilteringTest.java | 4 +- 52 files changed, 596 insertions(+), 367 deletions(-) create mode 100644 pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHost.java create mode 100644 pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHostTest.java create mode 100644 pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnCluster.java create mode 100644 pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnClusterTest.java create mode 100644 spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureChecksAutoConfiguration.java delete mode 100644 spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring.factories diff --git a/README.md b/README.md index 750f9fe8..4e365b00 100644 --- a/README.md +++ b/README.md @@ -46,32 +46,33 @@ All checks can be divided into 2 groups: **pg-index-health** allows you to detect the following problems: -| № | Description | Type | SQL query | -|----|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|-----------------------------------------------------------------------------------------------------------| -| 1 | Invalid (broken) indexes | **runtime**/static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/invalid_indexes.sql) | -| 1 | Duplicated (completely identical) indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/duplicated_indexes.sql) | -| 3 | Intersected (partially identical) indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/intersected_indexes.sql) | -| 4 | Unused indexes | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/unused_indexes.sql) | -| 5 | Foreign keys without associated indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/foreign_keys_without_index.sql) | -| 6 | Indexes with null values | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/indexes_with_null_values.sql) | -| 7 | Tables with missing indexes | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_with_missing_indexes.sql) | -| 8 | Tables without primary key | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_without_primary_key.sql) | -| 9 | Indexes [bloat](https://www.percona.com/blog/2018/08/06/basic-understanding-bloat-vacuum-postgresql-mvcc/) | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/bloated_indexes.sql) | -| 10 | Tables [bloat](https://www.percona.com/blog/2018/08/06/basic-understanding-bloat-vacuum-postgresql-mvcc/) | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/bloated_tables.sql) | -| 11 | Tables without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_without_description.sql) | -| 12 | Columns without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_without_description.sql) | -| 13 | Columns with [json](https://www.postgresql.org/docs/current/datatype-json.html) type | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_with_json_type.sql) | -| 14 | Columns of [serial types](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL) that are not primary keys | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_with_serial_types.sql) | -| 15 | Functions without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/functions_without_description.sql) | -| 16 | Indexes [with boolean](https://habr.com/ru/companies/tensor/articles/488104/) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/indexes_with_boolean.sql) | -| 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/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) | -| 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) | -| 23 | Possible object name overflow (identifiers with maximum length) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/possible_object_name_overflow.sql) | -| 24 | Tables not linked to other tables | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_not_linked_to_others.sql) | +| № | Description | Type | SQL query | +|----|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|-------------------------------------------------------------------------------------------------------------------| +| 1 | Invalid (broken) indexes | **runtime**/static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/invalid_indexes.sql) | +| 1 | Duplicated (completely identical) indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/duplicated_indexes.sql) | +| 3 | Intersected (partially identical) indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/intersected_indexes.sql) | +| 4 | Unused indexes | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/unused_indexes.sql) | +| 5 | Foreign keys without associated indexes | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/foreign_keys_without_index.sql) | +| 6 | Indexes with null values | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/indexes_with_null_values.sql) | +| 7 | Tables with missing indexes | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_with_missing_indexes.sql) | +| 8 | Tables without primary key | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_without_primary_key.sql) | +| 9 | Indexes [bloat](https://www.percona.com/blog/2018/08/06/basic-understanding-bloat-vacuum-postgresql-mvcc/) | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/bloated_indexes.sql) | +| 10 | Tables [bloat](https://www.percona.com/blog/2018/08/06/basic-understanding-bloat-vacuum-postgresql-mvcc/) | **runtime** | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/bloated_tables.sql) | +| 11 | Tables without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_without_description.sql) | +| 12 | Columns without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_without_description.sql) | +| 13 | Columns with [json](https://www.postgresql.org/docs/current/datatype-json.html) type | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_with_json_type.sql) | +| 14 | Columns of [serial types](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL) that are not primary keys | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/columns_with_serial_types.sql) | +| 15 | Functions without [description](https://www.postgresql.org/docs/current/sql-comment.html) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/functions_without_description.sql) | +| 16 | Indexes [with boolean](https://habr.com/ru/companies/tensor/articles/488104/) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/indexes_with_boolean.sql) | +| 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/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) | +| 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) | +| 23 | Possible object name overflow (identifiers with maximum length) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/possible_object_name_overflow.sql) | +| 24 | Tables not linked to other tables | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/tables_not_linked_to_others.sql) | +| 25 | Foreign keys [with unmatched column type](https://habr.com/ru/articles/803841/) | static | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/foreign_keys_with_unmatched_column_type.sql) | For raw sql queries see [pg-index-health-sql](https://github.com/mfvanek/pg-index-health-sql) project. diff --git a/buildSrc/src/main/kotlin/pg-index-health.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/pg-index-health.java-conventions.gradle.kts index 5d1f4d90..65bcc9c9 100644 --- a/buildSrc/src/main/kotlin/pg-index-health.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/pg-index-health.java-conventions.gradle.kts @@ -17,7 +17,7 @@ plugins { } dependencies { - errorprone("com.google.errorprone:error_prone_core:2.33.0") + errorprone("com.google.errorprone:error_prone_core:2.35.1") errorprone("jp.skypencil.errorprone.slf4j:errorprone-slf4j:0.1.28") spotbugsPlugins("jp.skypencil.findbugs.slf4j:bug-pattern:1.5.0") @@ -81,7 +81,7 @@ checkstyle { } pmd { - toolVersion = "7.6.0" + toolVersion = "7.7.0" isConsoleOutput = true ruleSetFiles = files("${rootDir}/config/pmd/pmd.xml") ruleSets = listOf() diff --git a/config/pmd/pmd.xml b/config/pmd/pmd.xml index 4ff73dd3..059b34c1 100644 --- a/config/pmd/pmd.xml +++ b/config/pmd/pmd.xml @@ -6,9 +6,9 @@ PMD configuration - - - + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66c2e51d..7ceb23db 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,8 +5,8 @@ commons-lang3 = "3.17.0" slf4j = "1.7.36" # to be compatible with Spring Boot 2.7.X assertj = "3.26.3" testcontainers = "1.20.3" -junit = "5.11.2" -mockito = "5.14.1" +junit = "5.11.3" +mockito = "5.14.2" forbiddenapis = "3.8" [libraries] diff --git a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java index e603f5af..c34bb50b 100644 --- a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java +++ b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java @@ -33,6 +33,8 @@ public ForeignKeysNotCoveredWithIndexCheckOnHost(@Nonnull final PgConnection pgC /** * Returns foreign keys without associated indexes in the specified schema. + *

+ * For multi-column constraints returns all columns. * * @param pgContext check's context with the specified schema * @return list of foreign keys without associated indexes diff --git a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHost.java b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHost.java new file mode 100644 index 00000000..0ba16eb1 --- /dev/null +++ b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHost.java @@ -0,0 +1,53 @@ +/* + * 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.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.constraint.ForeignKey; + +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Check for foreign keys where the type of the constrained column does not match the type in the referenced table on a specific host. + *

+ * The column types in the referring and target relation must match. + * For example, a column with the {@code integer} type should refer to a column with the {@code integer} type. + * This eliminates unnecessary conversions at the DBMS level and in the application code, + * and reduces the number of errors that may appear due to type inconsistencies in the future. + * + * @author Ivan Vahrushev + * @see pg_constraint + * @since 0.13.2 + */ +public class ForeignKeysWithUnmatchedColumnTypeCheckOnHost extends AbstractCheckOnHost { + + public ForeignKeysWithUnmatchedColumnTypeCheckOnHost(@Nonnull final PgConnection pgConnection) { + super(ForeignKey.class, pgConnection, Diagnostic.FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE); + } + + /** + * Returns foreign keys where the type of the constrained column does not match the type in the referenced table. + *

+ * For multi-column constraints returns only columns with differences. + * + * @param pgContext check's context with the specified schema; must not be null + * @return list of foreign keys where the type of the constrained column does not match the type in the referenced table + */ + @Nonnull + @Override + protected List doCheck(@Nonnull final PgContext pgContext) { + return executeQuery(pgContext, ForeignKeyExtractor.ofDefault()); + } +} diff --git a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java index a6eb9c8b..61fc70cf 100644 --- a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java +++ b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java @@ -47,7 +47,8 @@ public enum Diagnostic implements CheckTypeAware { DUPLICATED_FOREIGN_KEYS(ExecutionTopology.ON_PRIMARY, "duplicated_foreign_keys.sql", QueryExecutors::executeQueryWithSchema, false), INTERSECTED_FOREIGN_KEYS(ExecutionTopology.ON_PRIMARY, "intersected_foreign_keys.sql", QueryExecutors::executeQueryWithSchema, false), POSSIBLE_OBJECT_NAME_OVERFLOW(ExecutionTopology.ON_PRIMARY, "possible_object_name_overflow.sql", QueryExecutors::executeQueryWithSchema, false), - TABLES_NOT_LINKED_TO_OTHERS(ExecutionTopology.ON_PRIMARY, "tables_not_linked_to_others.sql", QueryExecutors::executeQueryWithSchema, false); + TABLES_NOT_LINKED_TO_OTHERS(ExecutionTopology.ON_PRIMARY, "tables_not_linked_to_others.sql", QueryExecutors::executeQueryWithSchema, false), + FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE(ExecutionTopology.ON_PRIMARY, "foreign_keys_with_unmatched_column_type.sql", QueryExecutors::executeQueryWithSchema, false); private final ExecutionTopology executionTopology; private final String sqlQueryFileName; diff --git a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParser.java b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParser.java index 28cd6a08..35dbf573 100644 --- a/pg-index-health-core/src/main/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParser.java +++ b/pg-index-health-core/src/main/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParser.java @@ -39,14 +39,14 @@ public static List parseRawColumnData(@Nonnull final String tableName, @ @Nonnull private static Column toColumn(@Nonnull final String tableName, @Nonnull final String rawColumnInfo) { - final String[] columnInfo = rawColumnInfo.split(", "); + final String[] columnInfo = rawColumnInfo.split(","); if (columnInfo.length != 2) { throw new IllegalArgumentException("Cannot parse column info from " + rawColumnInfo); } - final boolean notNullColumn = Boolean.parseBoolean(columnInfo[1]); + final boolean notNullColumn = Boolean.parseBoolean(columnInfo[1].trim()); if (notNullColumn) { - return Column.ofNotNull(tableName, columnInfo[0]); + return Column.ofNotNull(tableName, columnInfo[0].trim()); } - return Column.ofNullable(tableName, columnInfo[0]); + return Column.ofNullable(tableName, columnInfo[0].trim()); } } diff --git a/pg-index-health-core/src/main/resources b/pg-index-health-core/src/main/resources index 0f42870e..3b0f227f 160000 --- a/pg-index-health-core/src/main/resources +++ b/pg-index-health-core/src/main/resources @@ -1 +1 @@ -Subproject commit 0f42870e034eeb4886d2569bfd4e1226c79f0204 +Subproject commit 3b0f227f8f9f4abb29ae48fca52a4421fe2e4bd6 diff --git a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHostTest.java b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHostTest.java index 7c492199..62c87c12 100644 --- a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHostTest.java +++ b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHostTest.java @@ -20,6 +20,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.util.List; + import static io.github.mfvanek.pg.support.AbstractCheckOnHostAssert.assertThat; class ForeignKeysNotCoveredWithIndexCheckOnHostTest extends DatabaseAwareTestBase { @@ -38,39 +40,43 @@ void shouldSatisfyContract() { @ParameterizedTest @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) void onDatabaseWithThem(final String schemaName) { - executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn(), ctx -> + executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn(), ctx -> { + final String accountsTableName = ctx.enrichWithSchema("accounts"); + final String badClientsTableName = ctx.enrichWithSchema("bad_clients"); assertThat(check) .executing(ctx) - .hasSize(2) + .hasSize(3) .containsExactlyInAnyOrder( - ForeignKey.ofColumn(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id", - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id")), - ForeignKey.ofColumn(ctx.enrichWithSchema("bad_clients"), "c_bad_clients_fk_real_client_id", - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))) + ForeignKey.ofColumn(accountsTableName, "c_accounts_fk_client_id", + Column.ofNotNull(accountsTableName, "client_id")), + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_real_client_id", + Column.ofNullable(badClientsTableName, "real_client_id")), + ForeignKey.of(badClientsTableName, "c_bad_clients_fk_email_phone", + List.of( + Column.ofNullable(badClientsTableName, "email"), + Column.ofNullable(badClientsTableName, "phone")))) .flatExtracting(ForeignKey::getColumnsInConstraint) - .hasSize(2) + .hasSize(4) .containsExactlyInAnyOrder( - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id"), - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))); + Column.ofNotNull(accountsTableName, "client_id"), + Column.ofNullable(badClientsTableName, "real_client_id"), + Column.ofNullable(badClientsTableName, "email"), + Column.ofNullable(badClientsTableName, "phone")); + }); } @ParameterizedTest @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) void onDatabaseWithNotSuitableIndex(final String schemaName) { - executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn().withNonSuitableIndex(), ctx -> + executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withNonSuitableIndex(), ctx -> { + final String accountsTableName = ctx.enrichWithSchema("accounts"); assertThat(check) .executing(ctx) - .hasSize(2) - .containsExactlyInAnyOrder( - ForeignKey.ofColumn(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id", - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id")), - ForeignKey.ofColumn(ctx.enrichWithSchema("bad_clients"), "c_bad_clients_fk_real_client_id", - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))) - .flatExtracting(ForeignKey::getColumnsInConstraint) - .hasSize(2) - .containsExactlyInAnyOrder( - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id"), - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))); + .hasSize(1) + .containsExactly( + ForeignKey.ofColumn(accountsTableName, "c_accounts_fk_client_id", + Column.ofNotNull(accountsTableName, "client_id"))); + }); } @ParameterizedTest diff --git a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHostTest.java b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHostTest.java new file mode 100644 index 00000000..5e4686e2 --- /dev/null +++ b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/ForeignKeysWithUnmatchedColumnTypeCheckOnHostTest.java @@ -0,0 +1,54 @@ +/* + * 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.common.maintenance.DatabaseCheckOnHost; +import io.github.mfvanek.pg.common.maintenance.Diagnostic; +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.support.DatabaseAwareTestBase; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static io.github.mfvanek.pg.support.AbstractCheckOnHostAssert.assertThat; + +class ForeignKeysWithUnmatchedColumnTypeCheckOnHostTest extends DatabaseAwareTestBase { + + private final DatabaseCheckOnHost check = new ForeignKeysWithUnmatchedColumnTypeCheckOnHost(getPgConnection()); + + @Test + void shouldSatisfyContract() { + assertThat(check) + .hasType(ForeignKey.class) + .hasDiagnostic(Diagnostic.FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE) + .hasHost(getHost()) + .isStatic(); + } + + @ParameterizedTest + @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) + void onDatabaseWithThem(final String schemaName) { + executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn(), ctx -> { + final String badClientsTableName = ctx.enrichWithSchema("bad_clients"); + assertThat(check) + .executing(ctx) + .hasSize(2) + .containsExactlyInAnyOrder( + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_real_client_id", + Column.ofNullable(badClientsTableName, "real_client_id")), + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_email_phone", + Column.ofNullable(badClientsTableName, "phone")) + ); + }); + } +} diff --git a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/TablesWithoutPrimaryKeyCheckOnHostTest.java b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/TablesWithoutPrimaryKeyCheckOnHostTest.java index 8a01f634..d98de363 100644 --- a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/TablesWithoutPrimaryKeyCheckOnHostTest.java +++ b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/checks/host/TablesWithoutPrimaryKeyCheckOnHostTest.java @@ -44,9 +44,7 @@ void onDatabaseWithThem(final String schemaName) { assertThat(check) .executing(ctx) .hasSize(1) - .containsExactly( - Table.of(ctx.enrichWithSchema("bad_clients"), 0L)) - .allMatch(t -> t.getTableSizeInBytes() == 0L)); + .containsExactly(Table.of(ctx.enrichWithSchema("bad_clients"), 0L))); } @ParameterizedTest diff --git a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParserTest.java b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParserTest.java index ac7c02b7..a6d920ec 100644 --- a/pg-index-health-core/src/test/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParserTest.java +++ b/pg-index-health-core/src/test/java/io/github/mfvanek/pg/utils/ColumnsInForeignKeyParserTest.java @@ -54,4 +54,25 @@ void shouldWorkWhenValidDataPassed() { Column.ofNullable("t", "c3")) .isUnmodifiable(); } + + @Test + void shouldWorkWithoutSpaces() { + assertThat(ColumnsInForeignKeyParser.parseRawColumnData("t", "c1,true", "c2,false", "c3,abracadabra")) + .hasSize(3) + .containsExactly( + Column.ofNotNull("t", "c1"), + Column.ofNullable("t", "c2"), + Column.ofNullable("t", "c3")) + .isUnmodifiable(); + } + + @Test + void shouldWorkWithExtraSpaces() { + assertThat(ColumnsInForeignKeyParser.parseRawColumnData("t", " c1, true ", " c2, false ")) + .hasSize(2) + .containsExactly( + Column.ofNotNull("t", "c1"), + Column.ofNullable("t", "c2")) + .isUnmodifiable(); + } } diff --git a/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateForeignKeyOnNullableColumnStatement.java b/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateForeignKeyOnNullableColumnStatement.java index 86bc25a9..00cfb3c2 100644 --- a/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateForeignKeyOnNullableColumnStatement.java +++ b/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateForeignKeyOnNullableColumnStatement.java @@ -18,7 +18,11 @@ public class CreateForeignKeyOnNullableColumnStatement extends AbstractDbStateme @Nonnull @Override protected List getSqlToExecute() { - return List.of("alter table if exists {schemaName}.bad_clients " + - "add constraint c_bad_clients_fk_real_client_id foreign key (real_client_id) references {schemaName}.clients (id);"); + return List.of( + "alter table if exists {schemaName}.bad_clients " + + "add constraint c_bad_clients_fk_real_client_id foreign key (real_client_id) references {schemaName}.clients (id);", + "alter table if exists {schemaName}.bad_clients " + + "add constraint c_bad_clients_fk_email_phone foreign key (email, phone) references {schemaName}.clients (email, phone);" + ); } } diff --git a/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateTableWithoutPrimaryKeyStatement.java b/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateTableWithoutPrimaryKeyStatement.java index 3f91a676..9a215cbb 100644 --- a/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateTableWithoutPrimaryKeyStatement.java +++ b/pg-index-health-core/src/testFixtures/java/io/github/mfvanek/pg/support/statements/CreateTableWithoutPrimaryKeyStatement.java @@ -23,7 +23,9 @@ protected List getSqlToExecute() { return List.of("create table if not exists {schemaName}.bad_clients (" + "id bigint not null, " + "name varchar(255) not null," + - "real_client_id bigint)"); + "real_client_id integer," + + "email varchar(200)," + + "phone varchar(51))"); } @Override diff --git a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/ConnectionCredentialsTest.java b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/ConnectionCredentialsTest.java index 4b3b9038..21f135fe 100644 --- a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/ConnectionCredentialsTest.java +++ b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/ConnectionCredentialsTest.java @@ -100,7 +100,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(ConnectionCredentials.class) .verify(); diff --git a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgConnectionImplTest.java b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgConnectionImplTest.java index d49ef41c..201a36ff 100644 --- a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgConnectionImplTest.java +++ b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgConnectionImplTest.java @@ -84,7 +84,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(PgConnectionImpl.class) .withIgnoredFields("dataSource") diff --git a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgHostImplTest.java b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgHostImplTest.java index 5edbaec1..616ab897 100644 --- a/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgHostImplTest.java +++ b/pg-index-health-jdbc-connection/src/test/java/io/github/mfvanek/pg/connection/PgHostImplTest.java @@ -125,7 +125,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(PgHostImpl.class) .withIgnoredFields("pgUrl", "maybePrimary") diff --git a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java index 1109d1b8..97b37297 100644 --- a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java +++ b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java @@ -113,6 +113,7 @@ public final List logAll(@Nonnull final Exclusions exclusions, logResult.add(logCheckResult(Diagnostic.INTERSECTED_FOREIGN_KEYS, DuplicatedForeignKeys.class, SimpleLoggingKey.INTERSECTED_FOREIGN_KEYS)); logResult.add(logCheckResult(Diagnostic.POSSIBLE_OBJECT_NAME_OVERFLOW, AnyObject.class, SimpleLoggingKey.POSSIBLE_OBJECT_NAME_OVERFLOW)); logResult.add(logCheckResult(Diagnostic.TABLES_NOT_LINKED_TO_OTHERS, Table.class, SimpleLoggingKey.TABLES_NOT_LINKED_TO_OTHERS)); + logResult.add(logCheckResult(Diagnostic.FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE, ForeignKey.class, SimpleLoggingKey.FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE)); return logResult; } finally { databaseChecksHolder.set(null); diff --git a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java index 3f8a386b..8addf8fa 100644 --- a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java +++ b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java @@ -38,7 +38,8 @@ public enum SimpleLoggingKey implements LoggingKey { DUPLICATED_FOREIGN_KEYS("duplicated_foreign_keys"), INTERSECTED_FOREIGN_KEYS("intersected_foreign_keys"), POSSIBLE_OBJECT_NAME_OVERFLOW("possible_object_name_overflow"), - TABLES_NOT_LINKED_TO_OTHERS("tables_not_linked_to_others"); + TABLES_NOT_LINKED_TO_OTHERS("tables_not_linked_to_others"), + FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE("foreign_keys_with_unmatched_column_type"); private final String subKeyName; diff --git a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java index 4fd07dbf..6b2277e9 100644 --- a/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java +++ b/pg-index-health-logger/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java @@ -17,6 +17,7 @@ import io.github.mfvanek.pg.checks.cluster.DuplicatedForeignKeysCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.DuplicatedIndexesCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.ForeignKeysNotCoveredWithIndexCheckOnCluster; +import io.github.mfvanek.pg.checks.cluster.ForeignKeysWithUnmatchedColumnTypeCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.FunctionsWithoutDescriptionCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.IndexesWithBloatCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.IndexesWithBooleanCheckOnCluster; @@ -76,7 +77,8 @@ public DatabaseChecks(@Nonnull final HighAvailabilityPgConnection haPgConnection new DuplicatedForeignKeysCheckOnCluster(haPgConnection), new IntersectedForeignKeysCheckOnCluster(haPgConnection), new PossibleObjectNameOverflowCheckOnCluster(haPgConnection), - new TablesNotLinkedToOthersCheckOnCluster(haPgConnection) + new TablesNotLinkedToOthersCheckOnCluster(haPgConnection), + new ForeignKeysWithUnmatchedColumnTypeCheckOnCluster(haPgConnection) ); allChecks.forEach(check -> this.checks.putIfAbsent(check.getDiagnostic(), check)); } diff --git a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java index 565e6b7c..cdf39dca 100644 --- a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java +++ b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java @@ -57,7 +57,7 @@ protected String[] getExpectedValue() { return new String[]{ "1999-12-31T23:59:59Z\tdb_indexes_health\tinvalid_indexes\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tduplicated_indexes\t2", - "1999-12-31T23:59:59Z\tdb_indexes_health\tforeign_keys_without_index\t5", + "1999-12-31T23:59:59Z\tdb_indexes_health\tforeign_keys_without_index\t7", "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_without_primary_key\t2", "1999-12-31T23:59:59Z\tdb_indexes_health\tindexes_with_null_values\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tindexes_with_bloat\t17", @@ -66,7 +66,7 @@ protected String[] getExpectedValue() { "1999-12-31T23:59:59Z\tdb_indexes_health\tunused_indexes\t12", "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_with_missing_indexes\t0", "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_without_description\t6", - "1999-12-31T23:59:59Z\tdb_indexes_health\tcolumns_without_description\t25", + "1999-12-31T23:59:59Z\tdb_indexes_health\tcolumns_without_description\t27", "1999-12-31T23:59:59Z\tdb_indexes_health\tcolumns_with_json_type\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tcolumns_with_serial_types\t3", "1999-12-31T23:59:59Z\tdb_indexes_health\tfunctions_without_description\t2", @@ -78,7 +78,8 @@ protected String[] getExpectedValue() { "1999-12-31T23:59:59Z\tdb_indexes_health\tduplicated_foreign_keys\t3", "1999-12-31T23:59:59Z\tdb_indexes_health\tintersected_foreign_keys\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tpossible_object_name_overflow\t2", - "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_not_linked_to_others\t3" + "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_not_linked_to_others\t2", + "1999-12-31T23:59:59Z\tdb_indexes_health\tforeign_keys_with_unmatched_column_type\t2" }; } diff --git a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTestBase.java b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTestBase.java index 6f80a599..d6e248d1 100644 --- a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTestBase.java +++ b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTestBase.java @@ -48,7 +48,8 @@ abstract class HealthLoggerTestBase extends StatisticsAwareTestBase { .withDuplicatedForeignKeys() .withIntersectedForeignKeys() .withMaterializedView() - .withIdentityPrimaryKey(); + .withIdentityPrimaryKey() + .withForeignKeyOnNullableColumn(); @Nonnull protected static Predicate ofKey(@Nonnull final LoggingKey key) { diff --git a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java index 0ab1f508..37689e1e 100644 --- a/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java +++ b/pg-index-health-logger/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java @@ -28,7 +28,7 @@ protected String[] getExpectedValue() { return new String[]{ "invalid_indexes:1", "duplicated_indexes:2", - "foreign_keys_without_index:5", + "foreign_keys_without_index:7", "tables_without_primary_key:2", "indexes_with_null_values:1", "indexes_with_bloat:17", @@ -37,7 +37,7 @@ protected String[] getExpectedValue() { "unused_indexes:12", "tables_with_missing_indexes:0", "tables_without_description:6", - "columns_without_description:25", + "columns_without_description:27", "columns_with_json_type:1", "columns_with_serial_types:3", "functions_without_description:2", @@ -49,7 +49,8 @@ protected String[] getExpectedValue() { "duplicated_foreign_keys:3", "intersected_foreign_keys:1", "possible_object_name_overflow:2", - "tables_not_linked_to_others:3" + "tables_not_linked_to_others:2", + "foreign_keys_with_unmatched_column_type:2" }; } diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnTest.java index 27869343..6fc5e659 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnTest.java @@ -118,7 +118,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(Column.class) .verify(); diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnWithSerialTypeTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnWithSerialTypeTest.java index 00d04b6f..8b689526 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnWithSerialTypeTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/column/ColumnWithSerialTypeTest.java @@ -139,7 +139,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(ColumnWithSerialType.class) .verify(); diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTest.java index d6392ddd..b0e9639a 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTest.java @@ -115,7 +115,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(Constraint.class) .withIgnoredFields("constraintType") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/DuplicatedForeignKeysTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/DuplicatedForeignKeysTest.java index 784cb55c..652359e6 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/DuplicatedForeignKeysTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/DuplicatedForeignKeysTest.java @@ -101,7 +101,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(DuplicatedForeignKeys.class) .withIgnoredFields("foreignKeysNames") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ForeignKeyTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ForeignKeyTest.java index 3dbe0517..5c0e5bff 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ForeignKeyTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ForeignKeyTest.java @@ -176,7 +176,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(ForeignKey.class) .withIgnoredFields("constraintType", "columnsInConstraint") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/function/StoredFunctionTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/function/StoredFunctionTest.java index 7634ad08..534bdcb6 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/function/StoredFunctionTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/function/StoredFunctionTest.java @@ -123,7 +123,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(StoredFunction.class) .verify(); diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/DuplicatedIndexesTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/DuplicatedIndexesTest.java index adaad947..7b957986 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/DuplicatedIndexesTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/DuplicatedIndexesTest.java @@ -190,7 +190,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(DuplicatedIndexes.class) .withIgnoredFields("totalSize", "indexesNames") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexTest.java index 9aa4c5f6..63db4fce 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexTest.java @@ -96,7 +96,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(Index.class) .verify(); diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithBloatTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithBloatTest.java index 7a915683..530fe8d5 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithBloatTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithBloatTest.java @@ -99,7 +99,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(IndexWithBloat.class) .withIgnoredFields("indexSizeInBytes", "bloatSizeInBytes", "bloatPercentage") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithColumnsTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithColumnsTest.java index fce34a4f..fbbb6bed 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithColumnsTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithColumnsTest.java @@ -125,7 +125,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(IndexWithColumns.class) .withIgnoredFields("indexSizeInBytes", "columns") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithNullsTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithNullsTest.java index c3b70c45..2da757a0 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithNullsTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithNullsTest.java @@ -111,7 +111,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(IndexWithNulls.class) .withIgnoredFields("indexSizeInBytes", "columns") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithSizeTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithSizeTest.java index 808e05cf..4fb9fafa 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithSizeTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/IndexWithSizeTest.java @@ -90,7 +90,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(IndexWithSize.class) .withIgnoredFields("indexSizeInBytes") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/UnusedIndexTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/UnusedIndexTest.java index ba4c57f3..e15b53e0 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/UnusedIndexTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/index/UnusedIndexTest.java @@ -89,7 +89,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(UnusedIndex.class) .withIgnoredFields("indexSizeInBytes", "indexScans") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/object/AnyObjectTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/object/AnyObjectTest.java index 906c750f..2b004444 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/object/AnyObjectTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/object/AnyObjectTest.java @@ -95,7 +95,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(AnyObject.class) .verify(); diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableTest.java index 35ee7e6e..594546d3 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableTest.java @@ -96,7 +96,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(Table.class) .withIgnoredFields("tableSizeInBytes") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithBloatTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithBloatTest.java index d1a92536..ea09de56 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithBloatTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithBloatTest.java @@ -107,7 +107,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(TableWithBloat.class) .withIgnoredFields("bloatSizeInBytes", "bloatPercentage") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithMissingIndexTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithMissingIndexTest.java index 93116b45..cb700585 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithMissingIndexTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/table/TableWithMissingIndexTest.java @@ -104,7 +104,6 @@ void testEqualsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(TableWithMissingIndex.class) .withIgnoredFields("seqScans", "indexScans") diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/settings/PgParamImplTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/settings/PgParamImplTest.java index 31ac7b4d..17f93fee 100644 --- a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/settings/PgParamImplTest.java +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/settings/PgParamImplTest.java @@ -94,7 +94,6 @@ void equalsAndHashCode() { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(PgParamImpl.class) .withIgnoredFields("value") diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnCluster.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnCluster.java new file mode 100644 index 00000000..8f4800f9 --- /dev/null +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnCluster.java @@ -0,0 +1,36 @@ +/* + * 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.cluster; + +import io.github.mfvanek.pg.checks.host.ForeignKeysWithUnmatchedColumnTypeCheckOnHost; +import io.github.mfvanek.pg.connection.HighAvailabilityPgConnection; +import io.github.mfvanek.pg.model.constraint.ForeignKey; + +import javax.annotation.Nonnull; + +/** + * Check for foreign keys where the type of the constrained column does not match the type in the referenced table on all hosts in the cluster. + *

+ * The column types in the referring and target relation must match. + * For example, a column with the {@code integer} type should refer to a column with the {@code integer} type. + * This eliminates unnecessary conversions at the DBMS level and in the application code, + * and reduces the number of errors that may appear due to type inconsistencies in the future. + * + * @author Ivan Vahrushev + * @see pg_constraint + * @since 0.13.2 + */ +public class ForeignKeysWithUnmatchedColumnTypeCheckOnCluster extends AbstractCheckOnCluster { + + public ForeignKeysWithUnmatchedColumnTypeCheckOnCluster(@Nonnull final HighAvailabilityPgConnection haPgConnection) { + super(haPgConnection, ForeignKeysWithUnmatchedColumnTypeCheckOnHost::new); + } +} diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysNotCoveredWithIndexCheckOnClusterTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysNotCoveredWithIndexCheckOnClusterTest.java index a2393064..09dc3cc2 100644 --- a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysNotCoveredWithIndexCheckOnClusterTest.java +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysNotCoveredWithIndexCheckOnClusterTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.util.List; import java.util.function.Predicate; import static io.github.mfvanek.pg.support.AbstractCheckOnClusterAssert.assertThat; @@ -51,22 +52,30 @@ void onDatabaseWithoutThem(final String schemaName) { @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) void onDatabaseWithThem(final String schemaName) { executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn(), ctx -> { + final String accountsTableName = ctx.enrichWithSchema("accounts"); + final String badClientsTableName = ctx.enrichWithSchema("bad_clients"); assertThat(check) .executing(ctx) - .hasSize(2) + .hasSize(3) .containsExactlyInAnyOrder( - ForeignKey.ofColumn(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id", - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id")), - ForeignKey.ofColumn(ctx.enrichWithSchema("bad_clients"), "c_bad_clients_fk_real_client_id", - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))) + ForeignKey.ofColumn(accountsTableName, "c_accounts_fk_client_id", + Column.ofNotNull(accountsTableName, "client_id")), + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_real_client_id", + Column.ofNullable(badClientsTableName, "real_client_id")), + ForeignKey.of(badClientsTableName, "c_bad_clients_fk_email_phone", + List.of( + Column.ofNullable(badClientsTableName, "email"), + Column.ofNullable(badClientsTableName, "phone")))) .flatExtracting(ForeignKey::getColumnsInConstraint) - .hasSize(2) + .hasSize(4) .containsExactlyInAnyOrder( - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id"), - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id")); + Column.ofNotNull(accountsTableName, "client_id"), + Column.ofNullable(badClientsTableName, "real_client_id"), + Column.ofNullable(badClientsTableName, "email"), + Column.ofNullable(badClientsTableName, "phone")); - final Predicate predicate = FilterTablesByNamePredicate.of(ctx.enrichWithSchema("accounts")) - .and(FilterTablesByNamePredicate.of(ctx.enrichWithSchema("bad_clients"))); + final Predicate predicate = FilterTablesByNamePredicate.of(accountsTableName) + .and(FilterTablesByNamePredicate.of(badClientsTableName)); assertThat(check) .executing(ctx, predicate) .isEmpty(); @@ -76,25 +85,17 @@ void onDatabaseWithThem(final String schemaName) { @ParameterizedTest @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) void onDatabaseWithNotSuitableIndex(final String schemaName) { - executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn().withNonSuitableIndex(), ctx -> { + executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withNonSuitableIndex(), ctx -> { + final String accountsTableName = ctx.enrichWithSchema("accounts"); assertThat(check) .executing(ctx) - .hasSize(2) - .containsExactlyInAnyOrder( - ForeignKey.ofColumn(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id", - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id")), - ForeignKey.ofColumn(ctx.enrichWithSchema("bad_clients"), "c_bad_clients_fk_real_client_id", - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id"))) - .flatExtracting(ForeignKey::getColumnsInConstraint) - .hasSize(2) - .containsExactlyInAnyOrder( - Column.ofNotNull(ctx.enrichWithSchema("accounts"), "client_id"), - Column.ofNullable(ctx.enrichWithSchema("bad_clients"), "real_client_id")); + .hasSize(1) + .containsExactly( + ForeignKey.ofColumn(accountsTableName, "c_accounts_fk_client_id", + Column.ofNotNull(accountsTableName, "client_id"))); - final Predicate predicate = FilterTablesByNamePredicate.of(ctx.enrichWithSchema("accounts")) - .and(FilterTablesByNamePredicate.of(ctx.enrichWithSchema("bad_clients"))); assertThat(check) - .executing(ctx, predicate) + .executing(ctx, FilterTablesByNamePredicate.of(accountsTableName)) .isEmpty(); }); } diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnClusterTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnClusterTest.java new file mode 100644 index 00000000..c34da7e4 --- /dev/null +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/ForeignKeysWithUnmatchedColumnTypeCheckOnClusterTest.java @@ -0,0 +1,53 @@ +/* + * 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.cluster; + +import io.github.mfvanek.pg.common.maintenance.DatabaseCheckOnCluster; +import io.github.mfvanek.pg.common.maintenance.Diagnostic; +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.support.DatabaseAwareTestBase; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static io.github.mfvanek.pg.support.AbstractCheckOnClusterAssert.assertThat; + +class ForeignKeysWithUnmatchedColumnTypeCheckOnClusterTest extends DatabaseAwareTestBase { + + private final DatabaseCheckOnCluster check = new ForeignKeysWithUnmatchedColumnTypeCheckOnCluster(getHaPgConnection()); + + @Test + void shouldSatisfyContract() { + assertThat(check) + .hasType(ForeignKey.class) + .hasDiagnostic(Diagnostic.FOREIGN_KEYS_WITH_UNMATCHED_COLUMN_TYPE) + .isStatic(); + } + + @ParameterizedTest + @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) + void onDatabaseWithThem(final String schemaName) { + executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withForeignKeyOnNullableColumn(), ctx -> { + final String badClientsTableName = ctx.enrichWithSchema("bad_clients"); + assertThat(check) + .executing(ctx) + .hasSize(2) + .containsExactlyInAnyOrder( + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_real_client_id", + Column.ofNullable(badClientsTableName, "real_client_id")), + ForeignKey.ofColumn(badClientsTableName, "c_bad_clients_fk_email_phone", + Column.ofNullable(badClientsTableName, "phone")) + ); + }); + } +} diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/TablesWithoutPrimaryKeyCheckOnClusterTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/TablesWithoutPrimaryKeyCheckOnClusterTest.java index be389458..c84d0655 100644 --- a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/TablesWithoutPrimaryKeyCheckOnClusterTest.java +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/TablesWithoutPrimaryKeyCheckOnClusterTest.java @@ -41,8 +41,7 @@ void onDatabaseWithThem(final String schemaName) { assertThat(check) .executing(ctx) .hasSize(1) - .containsExactly(Table.of(ctx.enrichWithSchema("bad_clients"), 0L)) - .allMatch(t -> t.getTableSizeInBytes() == 0L); + .containsExactly(Table.of(ctx.enrichWithSchema("bad_clients"), 0L)); assertThat(check) .executing(ctx, FilterTablesByNamePredicate.of(ctx.enrichWithSchema("bad_clients"))) diff --git a/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureChecksAutoConfiguration.java b/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureChecksAutoConfiguration.java new file mode 100644 index 00000000..def6dc21 --- /dev/null +++ b/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureChecksAutoConfiguration.java @@ -0,0 +1,248 @@ +/* + * 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.spring; + +import io.github.mfvanek.pg.checks.host.BtreeIndexesOnArrayColumnsCheckOnHost; +import io.github.mfvanek.pg.checks.host.ColumnsWithJsonTypeCheckOnHost; +import io.github.mfvanek.pg.checks.host.ColumnsWithSerialTypesCheckOnHost; +import io.github.mfvanek.pg.checks.host.ColumnsWithoutDescriptionCheckOnHost; +import io.github.mfvanek.pg.checks.host.DuplicatedForeignKeysCheckOnHost; +import io.github.mfvanek.pg.checks.host.DuplicatedIndexesCheckOnHost; +import io.github.mfvanek.pg.checks.host.ForeignKeysNotCoveredWithIndexCheckOnHost; +import io.github.mfvanek.pg.checks.host.ForeignKeysWithUnmatchedColumnTypeCheckOnHost; +import io.github.mfvanek.pg.checks.host.FunctionsWithoutDescriptionCheckOnHost; +import io.github.mfvanek.pg.checks.host.IndexesWithBloatCheckOnHost; +import io.github.mfvanek.pg.checks.host.IndexesWithBooleanCheckOnHost; +import io.github.mfvanek.pg.checks.host.IndexesWithNullValuesCheckOnHost; +import io.github.mfvanek.pg.checks.host.IntersectedForeignKeysCheckOnHost; +import io.github.mfvanek.pg.checks.host.IntersectedIndexesCheckOnHost; +import io.github.mfvanek.pg.checks.host.InvalidIndexesCheckOnHost; +import io.github.mfvanek.pg.checks.host.NotValidConstraintsCheckOnHost; +import io.github.mfvanek.pg.checks.host.PossibleObjectNameOverflowCheckOnHost; +import io.github.mfvanek.pg.checks.host.PrimaryKeysWithSerialTypesCheckOnHost; +import io.github.mfvanek.pg.checks.host.SequenceOverflowCheckOnHost; +import io.github.mfvanek.pg.checks.host.TablesNotLinkedToOthersCheckOnHost; +import io.github.mfvanek.pg.checks.host.TablesWithBloatCheckOnHost; +import io.github.mfvanek.pg.checks.host.TablesWithMissingIndexesCheckOnHost; +import io.github.mfvanek.pg.checks.host.TablesWithoutDescriptionCheckOnHost; +import io.github.mfvanek.pg.checks.host.TablesWithoutPrimaryKeyCheckOnHost; +import io.github.mfvanek.pg.checks.host.UnusedIndexesCheckOnHost; +import io.github.mfvanek.pg.connection.PgConnection; +import io.github.mfvanek.pg.settings.maintenance.ConfigurationMaintenanceOnHost; +import io.github.mfvanek.pg.settings.maintenance.ConfigurationMaintenanceOnHostImpl; +import io.github.mfvanek.pg.statistics.maintenance.StatisticsMaintenanceOnHost; +import io.github.mfvanek.pg.statistics.maintenance.StatisticsMaintenanceOnHostImpl; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * Autoconfiguration for database checks. + * + * @author Ivan Vakhrushev + * @since 0.13.2 + */ +@SuppressWarnings({"checkstyle:ClassDataAbstractionCoupling", "checkstyle:ClassFanOutComplexity"}) +@AutoConfiguration(after = DatabaseStructureHealthAutoConfiguration.class) +@ConditionalOnBean(PgConnection.class) +public class DatabaseStructureChecksAutoConfiguration { + + @Bean + @ConditionalOnClass(DuplicatedIndexesCheckOnHost.class) + @ConditionalOnMissingBean + public DuplicatedIndexesCheckOnHost duplicatedIndexesCheckOnHost(final PgConnection pgConnection) { + return new DuplicatedIndexesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(ForeignKeysNotCoveredWithIndexCheckOnHost.class) + @ConditionalOnMissingBean + public ForeignKeysNotCoveredWithIndexCheckOnHost foreignKeysNotCoveredWithIndexCheckOnHost(final PgConnection pgConnection) { + return new ForeignKeysNotCoveredWithIndexCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(IndexesWithBloatCheckOnHost.class) + @ConditionalOnMissingBean + public IndexesWithBloatCheckOnHost indexesWithBloatCheckOnHost(final PgConnection pgConnection) { + return new IndexesWithBloatCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(IndexesWithNullValuesCheckOnHost.class) + @ConditionalOnMissingBean + public IndexesWithNullValuesCheckOnHost indexesWithNullValuesCheckOnHost(final PgConnection pgConnection) { + return new IndexesWithNullValuesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(IntersectedIndexesCheckOnHost.class) + @ConditionalOnMissingBean + public IntersectedIndexesCheckOnHost intersectedIndexesCheckOnHost(final PgConnection pgConnection) { + return new IntersectedIndexesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(InvalidIndexesCheckOnHost.class) + @ConditionalOnMissingBean + public InvalidIndexesCheckOnHost invalidIndexesCheckOnHost(final PgConnection pgConnection) { + return new InvalidIndexesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(TablesWithBloatCheckOnHost.class) + @ConditionalOnMissingBean + public TablesWithBloatCheckOnHost tablesWithBloatCheckOnHost(final PgConnection pgConnection) { + return new TablesWithBloatCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(TablesWithMissingIndexesCheckOnHost.class) + @ConditionalOnMissingBean + public TablesWithMissingIndexesCheckOnHost tablesWithMissingIndexesCheckOnHost(final PgConnection pgConnection) { + return new TablesWithMissingIndexesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(TablesWithoutPrimaryKeyCheckOnHost.class) + @ConditionalOnMissingBean + public TablesWithoutPrimaryKeyCheckOnHost tablesWithoutPrimaryKeyCheckOnHost(final PgConnection pgConnection) { + return new TablesWithoutPrimaryKeyCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(UnusedIndexesCheckOnHost.class) + @ConditionalOnMissingBean + public UnusedIndexesCheckOnHost unusedIndexesCheckOnHost(final PgConnection pgConnection) { + return new UnusedIndexesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(TablesWithoutDescriptionCheckOnHost.class) + @ConditionalOnMissingBean + public TablesWithoutDescriptionCheckOnHost tablesWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { + return new TablesWithoutDescriptionCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(ColumnsWithoutDescriptionCheckOnHost.class) + @ConditionalOnMissingBean + public ColumnsWithoutDescriptionCheckOnHost columnsWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { + return new ColumnsWithoutDescriptionCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(ColumnsWithJsonTypeCheckOnHost.class) + @ConditionalOnMissingBean + public ColumnsWithJsonTypeCheckOnHost columnsWithJsonTypeCheckOnHost(final PgConnection pgConnection) { + return new ColumnsWithJsonTypeCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(ColumnsWithSerialTypesCheckOnHost.class) + @ConditionalOnMissingBean + public ColumnsWithSerialTypesCheckOnHost columnsWithSerialTypesCheckOnHost(final PgConnection pgConnection) { + return new ColumnsWithSerialTypesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(FunctionsWithoutDescriptionCheckOnHost.class) + @ConditionalOnMissingBean + public FunctionsWithoutDescriptionCheckOnHost functionsWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { + return new FunctionsWithoutDescriptionCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(IndexesWithBooleanCheckOnHost.class) + @ConditionalOnMissingBean + public IndexesWithBooleanCheckOnHost indexesWithBooleanCheckOnHost(final PgConnection pgConnection) { + return new IndexesWithBooleanCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(NotValidConstraintsCheckOnHost.class) + @ConditionalOnMissingBean + public NotValidConstraintsCheckOnHost notValidConstraintsCheckOnHost(final PgConnection pgConnection) { + return new NotValidConstraintsCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(BtreeIndexesOnArrayColumnsCheckOnHost.class) + @ConditionalOnMissingBean + public BtreeIndexesOnArrayColumnsCheckOnHost btreeIndexesOnArrayColumnsCheckOnHost(final PgConnection pgConnection) { + return new BtreeIndexesOnArrayColumnsCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(SequenceOverflowCheckOnHost.class) + @ConditionalOnMissingBean + public SequenceOverflowCheckOnHost sequenceOverflowCheckOnHost(final PgConnection pgConnection) { + return new SequenceOverflowCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(PrimaryKeysWithSerialTypesCheckOnHost.class) + @ConditionalOnMissingBean + public PrimaryKeysWithSerialTypesCheckOnHost primaryKeysWithSerialTypesCheckOnHost(final PgConnection pgConnection) { + return new PrimaryKeysWithSerialTypesCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(DuplicatedForeignKeysCheckOnHost.class) + @ConditionalOnMissingBean + public DuplicatedForeignKeysCheckOnHost duplicatedForeignKeysCheckOnHost(final PgConnection pgConnection) { + return new DuplicatedForeignKeysCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(IntersectedForeignKeysCheckOnHost.class) + @ConditionalOnMissingBean + public IntersectedForeignKeysCheckOnHost intersectedForeignKeysCheckOnHost(final PgConnection pgConnection) { + return new IntersectedForeignKeysCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(PossibleObjectNameOverflowCheckOnHost.class) + @ConditionalOnMissingBean + public PossibleObjectNameOverflowCheckOnHost possibleObjectNameOverflowCheckOnHost(final PgConnection pgConnection) { + return new PossibleObjectNameOverflowCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(TablesNotLinkedToOthersCheckOnHost.class) + @ConditionalOnMissingBean + public TablesNotLinkedToOthersCheckOnHost tablesNotLinkedToOthersCheckOnHost(final PgConnection pgConnection) { + return new TablesNotLinkedToOthersCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(ForeignKeysWithUnmatchedColumnTypeCheckOnHost.class) + @ConditionalOnMissingBean + public ForeignKeysWithUnmatchedColumnTypeCheckOnHost foreignKeysWithUnmatchedColumnTypeCheckOnHost(final PgConnection pgConnection) { + return new ForeignKeysWithUnmatchedColumnTypeCheckOnHost(pgConnection); + } + + @Bean + @ConditionalOnClass(StatisticsMaintenanceOnHost.class) + @ConditionalOnMissingBean + public StatisticsMaintenanceOnHost statisticsMaintenanceOnHost(final PgConnection pgConnection) { + return new StatisticsMaintenanceOnHostImpl(pgConnection); + } + + @Bean + @ConditionalOnClass(ConfigurationMaintenanceOnHost.class) + @ConditionalOnMissingBean + public ConfigurationMaintenanceOnHost configurationMaintenanceOnHost(final PgConnection pgConnection) { + return new ConfigurationMaintenanceOnHostImpl(pgConnection); + } +} diff --git a/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfiguration.java b/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfiguration.java index c518f468..ba92f04d 100644 --- a/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfiguration.java +++ b/spring-boot-integration/pg-index-health-test-starter/src/main/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfiguration.java @@ -10,43 +10,14 @@ package io.github.mfvanek.pg.spring; -import io.github.mfvanek.pg.checks.host.BtreeIndexesOnArrayColumnsCheckOnHost; -import io.github.mfvanek.pg.checks.host.ColumnsWithJsonTypeCheckOnHost; -import io.github.mfvanek.pg.checks.host.ColumnsWithSerialTypesCheckOnHost; -import io.github.mfvanek.pg.checks.host.ColumnsWithoutDescriptionCheckOnHost; -import io.github.mfvanek.pg.checks.host.DuplicatedForeignKeysCheckOnHost; -import io.github.mfvanek.pg.checks.host.DuplicatedIndexesCheckOnHost; -import io.github.mfvanek.pg.checks.host.ForeignKeysNotCoveredWithIndexCheckOnHost; -import io.github.mfvanek.pg.checks.host.FunctionsWithoutDescriptionCheckOnHost; -import io.github.mfvanek.pg.checks.host.IndexesWithBloatCheckOnHost; -import io.github.mfvanek.pg.checks.host.IndexesWithBooleanCheckOnHost; -import io.github.mfvanek.pg.checks.host.IndexesWithNullValuesCheckOnHost; -import io.github.mfvanek.pg.checks.host.IntersectedForeignKeysCheckOnHost; -import io.github.mfvanek.pg.checks.host.IntersectedIndexesCheckOnHost; -import io.github.mfvanek.pg.checks.host.InvalidIndexesCheckOnHost; -import io.github.mfvanek.pg.checks.host.NotValidConstraintsCheckOnHost; -import io.github.mfvanek.pg.checks.host.PossibleObjectNameOverflowCheckOnHost; -import io.github.mfvanek.pg.checks.host.PrimaryKeysWithSerialTypesCheckOnHost; -import io.github.mfvanek.pg.checks.host.SequenceOverflowCheckOnHost; -import io.github.mfvanek.pg.checks.host.TablesNotLinkedToOthersCheckOnHost; -import io.github.mfvanek.pg.checks.host.TablesWithBloatCheckOnHost; -import io.github.mfvanek.pg.checks.host.TablesWithMissingIndexesCheckOnHost; -import io.github.mfvanek.pg.checks.host.TablesWithoutDescriptionCheckOnHost; -import io.github.mfvanek.pg.checks.host.TablesWithoutPrimaryKeyCheckOnHost; -import io.github.mfvanek.pg.checks.host.UnusedIndexesCheckOnHost; import io.github.mfvanek.pg.connection.PgConnection; import io.github.mfvanek.pg.connection.PgConnectionImpl; import io.github.mfvanek.pg.connection.PgHost; import io.github.mfvanek.pg.connection.PgHostImpl; import io.github.mfvanek.pg.connection.PgSqlException; -import io.github.mfvanek.pg.settings.maintenance.ConfigurationMaintenanceOnHost; -import io.github.mfvanek.pg.settings.maintenance.ConfigurationMaintenanceOnHostImpl; -import io.github.mfvanek.pg.statistics.maintenance.StatisticsMaintenanceOnHost; -import io.github.mfvanek.pg.statistics.maintenance.StatisticsMaintenanceOnHostImpl; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -67,13 +38,11 @@ * @author Ivan Vakhrushev * @since 0.3.1 */ -@SuppressWarnings({"checkstyle:ClassDataAbstractionCoupling", "checkstyle:ClassFanOutComplexity"}) -@AutoConfiguration +@AutoConfiguration(after = DataSourceAutoConfiguration.class) @EnableConfigurationProperties(DatabaseStructureHealthProperties.class) @ConditionalOnClass(value = DataSource.class, name = "org.postgresql.Driver") @Conditional(DatabaseStructureHealthCondition.class) @ConditionalOnProperty(name = "pg.index.health.test.enabled", matchIfMissing = true, havingValue = "true") -@AutoConfigureAfter(DataSourceAutoConfiguration.class) public class DatabaseStructureHealthAutoConfiguration { /** @@ -100,212 +69,4 @@ public PgConnection pgConnection(@Qualifier("dataSource") final DataSource dataS } return PgConnectionImpl.of(dataSource, host); } - - @Bean - @ConditionalOnClass(DuplicatedIndexesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public DuplicatedIndexesCheckOnHost duplicatedIndexesCheckOnHost(final PgConnection pgConnection) { - return new DuplicatedIndexesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(ForeignKeysNotCoveredWithIndexCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public ForeignKeysNotCoveredWithIndexCheckOnHost foreignKeysNotCoveredWithIndexCheckOnHost(final PgConnection pgConnection) { - return new ForeignKeysNotCoveredWithIndexCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(IndexesWithBloatCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public IndexesWithBloatCheckOnHost indexesWithBloatCheckOnHost(final PgConnection pgConnection) { - return new IndexesWithBloatCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(IndexesWithNullValuesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public IndexesWithNullValuesCheckOnHost indexesWithNullValuesCheckOnHost(final PgConnection pgConnection) { - return new IndexesWithNullValuesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(IntersectedIndexesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public IntersectedIndexesCheckOnHost intersectedIndexesCheckOnHost(final PgConnection pgConnection) { - return new IntersectedIndexesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(InvalidIndexesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public InvalidIndexesCheckOnHost invalidIndexesCheckOnHost(final PgConnection pgConnection) { - return new InvalidIndexesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(TablesWithBloatCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public TablesWithBloatCheckOnHost tablesWithBloatCheckOnHost(final PgConnection pgConnection) { - return new TablesWithBloatCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(TablesWithMissingIndexesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public TablesWithMissingIndexesCheckOnHost tablesWithMissingIndexesCheckOnHost(final PgConnection pgConnection) { - return new TablesWithMissingIndexesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(TablesWithoutPrimaryKeyCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public TablesWithoutPrimaryKeyCheckOnHost tablesWithoutPrimaryKeyCheckOnHost(final PgConnection pgConnection) { - return new TablesWithoutPrimaryKeyCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(UnusedIndexesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public UnusedIndexesCheckOnHost unusedIndexesCheckOnHost(final PgConnection pgConnection) { - return new UnusedIndexesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(TablesWithoutDescriptionCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public TablesWithoutDescriptionCheckOnHost tablesWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { - return new TablesWithoutDescriptionCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(ColumnsWithoutDescriptionCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public ColumnsWithoutDescriptionCheckOnHost columnsWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { - return new ColumnsWithoutDescriptionCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(ColumnsWithJsonTypeCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public ColumnsWithJsonTypeCheckOnHost columnsWithJsonTypeCheckOnHost(final PgConnection pgConnection) { - return new ColumnsWithJsonTypeCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(ColumnsWithSerialTypesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public ColumnsWithSerialTypesCheckOnHost columnsWithSerialTypesCheckOnHost(final PgConnection pgConnection) { - return new ColumnsWithSerialTypesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(FunctionsWithoutDescriptionCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public FunctionsWithoutDescriptionCheckOnHost functionsWithoutDescriptionCheckOnHost(final PgConnection pgConnection) { - return new FunctionsWithoutDescriptionCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(IndexesWithBooleanCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public IndexesWithBooleanCheckOnHost indexesWithBooleanCheckOnHost(final PgConnection pgConnection) { - return new IndexesWithBooleanCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(NotValidConstraintsCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public NotValidConstraintsCheckOnHost notValidConstraintsCheckOnHost(final PgConnection pgConnection) { - return new NotValidConstraintsCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(BtreeIndexesOnArrayColumnsCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public BtreeIndexesOnArrayColumnsCheckOnHost btreeIndexesOnArrayColumnsCheckOnHost(final PgConnection pgConnection) { - return new BtreeIndexesOnArrayColumnsCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(SequenceOverflowCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public SequenceOverflowCheckOnHost sequenceOverflowCheckOnHost(final PgConnection pgConnection) { - return new SequenceOverflowCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(PrimaryKeysWithSerialTypesCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public PrimaryKeysWithSerialTypesCheckOnHost primaryKeysWithSerialTypesCheckOnHost(final PgConnection pgConnection) { - return new PrimaryKeysWithSerialTypesCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(DuplicatedForeignKeysCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public DuplicatedForeignKeysCheckOnHost duplicatedForeignKeysCheckOnHost(final PgConnection pgConnection) { - return new DuplicatedForeignKeysCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(IntersectedForeignKeysCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public IntersectedForeignKeysCheckOnHost intersectedForeignKeysCheckOnHost(final PgConnection pgConnection) { - return new IntersectedForeignKeysCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(PossibleObjectNameOverflowCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public PossibleObjectNameOverflowCheckOnHost possibleObjectNameOverflowCheckOnHost(final PgConnection pgConnection) { - return new PossibleObjectNameOverflowCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(TablesNotLinkedToOthersCheckOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public TablesNotLinkedToOthersCheckOnHost tablesNotLinkedToOthersCheckOnHost(final PgConnection pgConnection) { - return new TablesNotLinkedToOthersCheckOnHost(pgConnection); - } - - @Bean - @ConditionalOnClass(StatisticsMaintenanceOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public StatisticsMaintenanceOnHost statisticsMaintenanceOnHost(final PgConnection pgConnection) { - return new StatisticsMaintenanceOnHostImpl(pgConnection); - } - - @Bean - @ConditionalOnClass(ConfigurationMaintenanceOnHost.class) - @ConditionalOnBean(PgConnection.class) - @ConditionalOnMissingBean - public ConfigurationMaintenanceOnHost configurationMaintenanceOnHost(final PgConnection pgConnection) { - return new ConfigurationMaintenanceOnHostImpl(pgConnection); - } } diff --git a/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring.factories b/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index f44d1052..00000000 --- a/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.mfvanek.pg.spring.DatabaseStructureHealthAutoConfiguration diff --git a/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ba8a654e..7e6abb9c 100644 --- a/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-integration/pg-index-health-test-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ io.github.mfvanek.pg.spring.DatabaseStructureHealthAutoConfiguration +io.github.mfvanek.pg.spring.DatabaseStructureChecksAutoConfiguration diff --git a/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/AutoConfigurationTestBase.java b/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/AutoConfigurationTestBase.java index f9e35839..5a82f8a9 100644 --- a/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/AutoConfigurationTestBase.java +++ b/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/AutoConfigurationTestBase.java @@ -56,7 +56,8 @@ abstract class AutoConfigurationTestBase { "duplicatedForeignKeysCheckOnHost", "intersectedForeignKeysCheckOnHost", "possibleObjectNameOverflowCheckOnHost", - "tablesNotLinkedToOthersCheckOnHost" + "tablesNotLinkedToOthersCheckOnHost", + "foreignKeysWithUnmatchedColumnTypeCheckOnHost" ); protected static final Class[] EXPECTED_TYPES = {PgConnection.class, DatabaseCheckOnHost.class, StatisticsMaintenanceOnHost.class, ConfigurationMaintenanceOnHost.class}; protected static final DataSource DATA_SOURCE_MOCK = Mockito.mock(DataSource.class); @@ -67,7 +68,7 @@ abstract class AutoConfigurationTestBase { @Nonnull protected ApplicationContextRunner assertWithTestConfig() { - return contextRunner.withUserConfiguration(DatabaseStructureHealthAutoConfiguration.class); + return contextRunner.withUserConfiguration(DatabaseStructureHealthAutoConfiguration.class, DatabaseStructureChecksAutoConfiguration.class); } protected static void initialize(@Nonnull final C applicationContext) { diff --git a/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfigurationFilteringTest.java b/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfigurationFilteringTest.java index 51642a18..9e45597f 100644 --- a/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfigurationFilteringTest.java +++ b/spring-boot-integration/pg-index-health-test-starter/src/test/java/io/github/mfvanek/pg/spring/DatabaseStructureHealthAutoConfigurationFilteringTest.java @@ -17,6 +17,7 @@ import io.github.mfvanek.pg.checks.host.DuplicatedForeignKeysCheckOnHost; import io.github.mfvanek.pg.checks.host.DuplicatedIndexesCheckOnHost; import io.github.mfvanek.pg.checks.host.ForeignKeysNotCoveredWithIndexCheckOnHost; +import io.github.mfvanek.pg.checks.host.ForeignKeysWithUnmatchedColumnTypeCheckOnHost; import io.github.mfvanek.pg.checks.host.FunctionsWithoutDescriptionCheckOnHost; import io.github.mfvanek.pg.checks.host.IndexesWithBloatCheckOnHost; import io.github.mfvanek.pg.checks.host.IndexesWithBooleanCheckOnHost; @@ -122,7 +123,8 @@ private static List> getCheckTypes() { DuplicatedForeignKeysCheckOnHost.class, IntersectedForeignKeysCheckOnHost.class, PossibleObjectNameOverflowCheckOnHost.class, - TablesNotLinkedToOthersCheckOnHost.class + TablesNotLinkedToOthersCheckOnHost.class, + ForeignKeysWithUnmatchedColumnTypeCheckOnHost.class ); } }