diff --git a/README.md b/README.md index 31f3f2f4..1b5c8a8c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ For **Java 8** compatible version take a look at release [0.7.0](https://github. 14. Columns of [serial types](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL) that are not primary keys ([sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/non_primary_key_columns_with_serial_types.sql)). 15. Functions without [description](https://www.postgresql.org/docs/current/sql-comment.html) ([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/) ([sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/indexes_with_boolean.sql)). -17. B-tree indexes [on array columns](https://habr.com/ru/articles/800121/) ([sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/btree_indexes_on_array_columns.sql)). +17. Tables with [not valid constraints](https://habr.com/ru/articles/800121/) ([sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/check_not_valid_constraints.sql)). +18. B-tree indexes [on array columns](https://habr.com/ru/articles/800121/) ([sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/btree_indexes_on_array_columns.sql)). For raw sql queries see [pg-index-health-sql](https://github.com/mfvanek/pg-index-health-sql) project. diff --git a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/column/SerialType.java b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/column/SerialType.java index 23e17401..6a63c77a 100644 --- a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/column/SerialType.java +++ b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/column/SerialType.java @@ -25,8 +25,17 @@ */ public enum SerialType { + /** + * Constant for smallserial. + */ SMALL_SERIAL("smallserial"), + /** + * Constant for serial. + */ SERIAL("serial"), + /** + * Constant for bigserial. + */ BIG_SERIAL("bigserial"); private static final Map VALUES = new HashMap<>(); @@ -59,6 +68,12 @@ public String toString() { '}'; } + /** + * Gets {@code SerialType} from PostgreSQL serial column type. + * + * @param pgColumnType PostgreSQL serial column type; should be non-null. + * @return {@code SerialType} + */ @Nonnull public static SerialType valueFrom(@Nonnull final String pgColumnType) { Objects.requireNonNull(pgColumnType, "pgColumnType cannot be null"); diff --git a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/Constraint.java b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/Constraint.java new file mode 100644 index 00000000..89371489 --- /dev/null +++ b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/Constraint.java @@ -0,0 +1,152 @@ +/* + * 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.model.constraint; + +import io.github.mfvanek.pg.model.DbObject; +import io.github.mfvanek.pg.model.table.TableNameAware; +import io.github.mfvanek.pg.model.validation.Validators; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; + +/** + * A representation of constraint in a database. + * + * @author Blohny + * @see TableNameAware + * @since 0.10.4 + */ +@Immutable +public class Constraint implements DbObject, TableNameAware { + + private final String tableName; + private final String constraintName; + private final ConstraintType constraintType; + + /** + * Constructs a {@code Constraint} object with given {@code ConstraintType}. + * + * @param tableName table name; should be non-blank. + * @param constraintName constraint name; should be non-blank. + * @param constraintType constraint type; should be non-null. + */ + protected Constraint( + @Nonnull final String tableName, + @Nonnull final String constraintName, + @Nonnull final ConstraintType constraintType) { + this.tableName = Validators.tableNameNotBlank(tableName); + this.constraintName = Validators.notBlank(constraintName, "constraintName"); + this.constraintType = Objects.requireNonNull(constraintType, "constraintType cannot be null"); + } + + /** + * {@inheritDoc} + */ + @Nonnull + @Override + public final String getName() { + return getConstraintName(); + } + + /** + * {@inheritDoc} + */ + @Nonnull + @Override + public String getTableName() { + return tableName; + } + + /** + * Gets the name of constraint. + * + * @return the name of constraint + */ + @Nonnull + public String getConstraintName() { + return constraintName; + } + + /** + * Gets type of constraint. + * + * @return type of constraint + * @see ConstraintType + */ + @Nonnull + public ConstraintType getConstraintType() { + return constraintType; + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean equals(final Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof Constraint)) { + return false; + } + + final Constraint that = (Constraint) other; + return Objects.equals(tableName, that.tableName) && + Objects.equals(constraintName, that.constraintName); + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return Objects.hash(tableName, constraintName); + } + + /** + * An auxiliary utility method for implementing {@code toString()} in child classes. + * + * @return string representation of the internal fields of this class + */ + @Nonnull + final String innerToString() { + return "tableName='" + tableName + '\'' + + ", constraintName='" + constraintName + '\''; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Constraint.class.getSimpleName() + '{' + + innerToString() + + ", constraintType=" + constraintType + + '}'; + } + + /** + * Constructs a {@code Constraint} object with given {@code ConstraintType}. + * + * @param tableName table name; should be non-blank. + * @param constraintName constraint name; should be non-blank. + * @param constraintType constraint type; should be non-null. + * @return {@code Constraint} + */ + @Nonnull + public static Constraint ofType(@Nonnull final String tableName, + @Nonnull final String constraintName, + @Nonnull final ConstraintType constraintType) { + return new Constraint(tableName, constraintName, constraintType); + } +} diff --git a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ConstraintType.java b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ConstraintType.java new file mode 100644 index 00000000..84651364 --- /dev/null +++ b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ConstraintType.java @@ -0,0 +1,66 @@ +/* + * 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.model.constraint; + +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * A mapping to PostgreSQL constraint types. + * + * @author Blohny + * @since 0.10.4 + * @see pg_constraint + */ +public enum ConstraintType { + + /** + * Check constraint. + */ + CHECK("c"), + /** + * Foreign key constraint. + */ + FOREIGN_KEY("f"); + + private final String pgConType; + + ConstraintType(@Nonnull final String pgConType) { + this.pgConType = Objects.requireNonNull(pgConType, "pgConType"); + } + + /** + * Gets internal PostgreSQL constraint type. + * + * @return pgConType + */ + @Nonnull + public String getPgConType() { + return pgConType; + } + + /** + * Gets {@code ConstraintType} from internal PostgreSQL constraint type. + * + * @param pgConType internal PostgreSQL constraint type; should be non-null. + * @return {@code ConstraintType} + */ + @Nonnull + public static ConstraintType valueFrom(@Nonnull final String pgConType) { + Objects.requireNonNull(pgConType, "pgConType cannot be null"); + for (final ConstraintType ct : values()) { + if (ct.getPgConType().equals(pgConType)) { + return ct; + } + } + throw new IllegalArgumentException("Unknown pgConType: " + pgConType); + } +} diff --git a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ForeignKey.java b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ForeignKey.java index 715d9408..6291f033 100644 --- a/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ForeignKey.java +++ b/pg-index-health-model/src/main/java/io/github/mfvanek/pg/model/constraint/ForeignKey.java @@ -10,7 +10,6 @@ package io.github.mfvanek.pg.model.constraint; -import io.github.mfvanek.pg.model.DbObject; import io.github.mfvanek.pg.model.column.Column; import io.github.mfvanek.pg.model.table.TableNameAware; import io.github.mfvanek.pg.model.validation.Validators; @@ -25,53 +24,23 @@ * * @author Ivan Vakhrushev * @see TableNameAware + * @see Constraint */ @Immutable -public class ForeignKey implements DbObject, TableNameAware { +public class ForeignKey extends Constraint { - private final String tableName; - private final String constraintName; private final List columnsInConstraint; private ForeignKey(@Nonnull final String tableName, @Nonnull final String constraintName, @Nonnull final List columnsInConstraint) { - this.tableName = Validators.tableNameNotBlank(tableName); - this.constraintName = Validators.notBlank(constraintName, "constraintName"); + super(tableName, constraintName, ConstraintType.FOREIGN_KEY); final List defensiveCopy = List.copyOf(Objects.requireNonNull(columnsInConstraint, "columnsInConstraint cannot be null")); Validators.validateThatNotEmpty(defensiveCopy); Validators.validateThatTableIsTheSame(tableName, defensiveCopy); this.columnsInConstraint = defensiveCopy; } - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public final String getName() { - return getConstraintName(); - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public String getTableName() { - return tableName; - } - - /** - * Gets the name of foreign key constraint. - * - * @return the name of foreign key - */ - @Nonnull - public String getConstraintName() { - return constraintName; - } - /** * Gets columns of foreign key constraint. * @@ -83,33 +52,6 @@ public List getColumnsInConstraint() { return columnsInConstraint; } - /** - * {@inheritDoc} - */ - @Override - public final boolean equals(final Object other) { - if (this == other) { - return true; - } - - if (!(other instanceof ForeignKey)) { - return false; - } - - final ForeignKey that = (ForeignKey) other; - return Objects.equals(tableName, that.tableName) && - Objects.equals(constraintName, that.constraintName) && - Objects.equals(columnsInConstraint, that.columnsInConstraint); - } - - /** - * {@inheritDoc} - */ - @Override - public final int hashCode() { - return Objects.hash(tableName, constraintName, columnsInConstraint); - } - /** * {@inheritDoc} */ @@ -117,8 +59,7 @@ public final int hashCode() { @Override public String toString() { return ForeignKey.class.getSimpleName() + '{' + - "tableName='" + tableName + '\'' + - ", constraintName='" + constraintName + '\'' + + innerToString() + ", columnsInConstraint=" + columnsInConstraint + '}'; } 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 new file mode 100644 index 00000000..1a90fdfd --- /dev/null +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTest.java @@ -0,0 +1,116 @@ +/* + * 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.model.constraint; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ConstraintTest { + + @Test + void testToString() { + final Constraint constraintWithCheck = Constraint.ofType("t", "not_valid_id", ConstraintType.CHECK); + assertThat(constraintWithCheck) + .hasToString("Constraint{tableName='t', constraintName='not_valid_id', constraintType=CHECK}"); + + final Constraint constraintWithForeignKey = Constraint.ofType("t", "not_valid_id", ConstraintType.FOREIGN_KEY); + assertThat(constraintWithForeignKey) + .hasToString("Constraint{tableName='t', constraintName='not_valid_id', constraintType=FOREIGN_KEY}"); + } + + @Test + void constraint() { + final Constraint constraintWithCheck = Constraint.ofType("t", "not_valid_id", ConstraintType.CHECK); + assertThat(constraintWithCheck.getTableName()) + .isNotBlank() + .isEqualTo("t"); + assertThat(constraintWithCheck.getConstraintName()) + .isNotBlank() + .isEqualTo("not_valid_id") + .isEqualTo(constraintWithCheck.getName()); + assertThat(constraintWithCheck.getConstraintType()) + .isNotNull() + .isEqualTo(ConstraintType.CHECK); + + final Constraint constraintWithForeignKey = Constraint.ofType("t", "not_valid_id", ConstraintType.FOREIGN_KEY); + assertThat(constraintWithForeignKey.getTableName()) + .isNotBlank() + .isEqualTo("t"); + assertThat(constraintWithForeignKey.getConstraintName()) + .isNotBlank() + .isEqualTo("not_valid_id") + .isEqualTo(constraintWithForeignKey.getName()); + assertThat(constraintWithForeignKey.getConstraintType()) + .isNotNull() + .isEqualTo(ConstraintType.FOREIGN_KEY); + } + + @SuppressWarnings("ConstantConditions") + @Test + void withInvalidArguments() { + assertThatThrownBy(() -> Constraint.ofType(null, null, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("tableName cannot be null"); + assertThatThrownBy(() -> Constraint.ofType("t", null, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("constraintName cannot be null"); + assertThatThrownBy(() -> Constraint.ofType("t", "c_t_order_id", null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("constraintType cannot be null"); + } + + @SuppressWarnings("ConstantConditions") + @Test + void equalsAndHashCode() { + final Constraint first = new Constraint("t", "not_valid_id", ConstraintType.CHECK); + final Constraint theSame = new Constraint("t", "not_valid_id", ConstraintType.CHECK); + final Constraint different = new Constraint("t", "valid_id", ConstraintType.CHECK); + final Constraint constraintTypeDoesntMatter = new Constraint("t", "not_valid_id", ConstraintType.FOREIGN_KEY); + final Constraint third = new Constraint("t1", "not_valid_id", ConstraintType.FOREIGN_KEY); + + assertThat(first.equals(null)).isFalse(); + //noinspection EqualsBetweenInconvertibleTypes + assertThat(first.equals(Integer.MAX_VALUE)).isFalse(); + + assertThat(first) + .isEqualTo(first) + .hasSameHashCodeAs(first); + + assertThat(theSame) + .isEqualTo(first) + .hasSameHashCodeAs(first); + + assertThat(different) + .isNotEqualTo(first) + .doesNotHaveSameHashCodeAs(first); + + assertThat(constraintTypeDoesntMatter) + .isEqualTo(first) + .hasSameHashCodeAs(first); + + assertThat(third) + .isNotEqualTo(first) + .doesNotHaveSameHashCodeAs(first) + .isNotEqualTo(constraintTypeDoesntMatter) + .doesNotHaveSameHashCodeAs(constraintTypeDoesntMatter); + } + + @Test + @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") + void equalsHashCodeShouldAdhereContracts() { + EqualsVerifier.forClass(Constraint.class) + .withIgnoredFields("constraintType") + .verify(); + } +} diff --git a/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTypeTest.java b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTypeTest.java new file mode 100644 index 00000000..01dc2293 --- /dev/null +++ b/pg-index-health-model/src/test/java/io/github/mfvanek/pg/model/constraint/ConstraintTypeTest.java @@ -0,0 +1,38 @@ +/* + * 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.model.constraint; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ConstraintTypeTest { + + @Test + void valueFrom() { + assertThat(ConstraintType.valueFrom("c")) + .isEqualTo(ConstraintType.CHECK); + assertThat(ConstraintType.valueFrom("f")) + .isEqualTo(ConstraintType.FOREIGN_KEY); + } + + @SuppressWarnings("ConstantConditions") + @Test + void creationFromStringShouldThrowExceptionWhenNotFound() { + assertThatThrownBy(() -> ConstraintType.valueFrom(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("pgConType cannot be null"); + assertThatThrownBy(() -> ConstraintType.valueFrom("hi")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unknown pgConType: hi"); + } +} 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 a0705e73..a69311bf 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 @@ -133,7 +133,7 @@ void equalsAndHashCode() { List.of(Column.ofNotNull("t", "order_id"), Column.ofNotNull("t", "limit"))); final ForeignKey withDifferentOrderOfColumns = ForeignKey.of("t", "c_t_order_id", List.of(Column.ofNotNull("t", "limit"), Column.ofNotNull("t", "order_id"))); - final ForeignKey second = ForeignKey.ofNullableColumn("t", "c_t_order_id", "no_matter_what"); + final ForeignKey withDifferentColumnName = ForeignKey.ofNullableColumn("t", "c_t_order_id", "no_matter_what"); assertThat(first.equals(null)).isFalse(); //noinspection EqualsBetweenInconvertibleTypes @@ -149,14 +149,15 @@ void equalsAndHashCode() { .isEqualTo(first) .hasSameHashCodeAs(first); - // column order matters + // column order doesn't matter assertThat(withDifferentOrderOfColumns) - .isNotEqualTo(first) - .doesNotHaveSameHashCodeAs(first); + .isEqualTo(first) + .hasSameHashCodeAs(first); - assertThat(second) - .isNotEqualTo(first) - .doesNotHaveSameHashCodeAs(first); + // column name doesn't matter + assertThat(withDifferentColumnName) + .isEqualTo(first) + .hasSameHashCodeAs(first); final ForeignKey third = ForeignKey.of("table", "c_t_order_id", List.of(Column.ofNotNull("table", "order_id"), Column.ofNotNull("table", "limit"))); @@ -175,6 +176,7 @@ void equalsAndHashCode() { @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") void equalsHashCodeShouldAdhereContracts() { EqualsVerifier.forClass(ForeignKey.class) + .withIgnoredFields("constraintType", "columnsInConstraint") .verify(); } } diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnCluster.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnCluster.java new file mode 100644 index 00000000..6de35ffe --- /dev/null +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnCluster.java @@ -0,0 +1,30 @@ +/* + * 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.NotValidConstraintsCheckOnHost; +import io.github.mfvanek.pg.connection.HighAvailabilityPgConnection; +import io.github.mfvanek.pg.model.constraint.Constraint; + +import javax.annotation.Nonnull; + +/** + * Check for not valid constraint on all hosts in the cluster. + * + * @author Blohny + * @since 0.10.4 + */ +public class NotValidConstraintsCheckOnCluster extends AbstractCheckOnCluster { + + public NotValidConstraintsCheckOnCluster(@Nonnull final HighAvailabilityPgConnection haPgConnection) { + super(haPgConnection, NotValidConstraintsCheckOnHost::new); + } +} diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/ColumnExtractor.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/ColumnExtractor.java index 4401fc63..1bd03119 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/ColumnExtractor.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/ColumnExtractor.java @@ -17,6 +17,8 @@ import java.sql.SQLException; import javax.annotation.Nonnull; +import static io.github.mfvanek.pg.checks.extractors.TableExtractor.TABLE_NAME; + /** * A mapper from raw data to {@link Column} model. * @@ -34,7 +36,7 @@ private ColumnExtractor() { @Nonnull @Override public Column extractData(@Nonnull final ResultSet resultSet) throws SQLException { - final String tableName = resultSet.getString("table_name"); + final String tableName = resultSet.getString(TABLE_NAME); final String columnName = resultSet.getString("column_name"); final boolean columnNotNull = resultSet.getBoolean("column_not_null"); if (columnNotNull) { diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/IndexWithSingleColumnExtractor.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/IndexWithSingleColumnExtractor.java new file mode 100644 index 00000000..cbca81d5 --- /dev/null +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/IndexWithSingleColumnExtractor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019-2024. Ivan Vakhrushev and others. + * https://github.com/mfvanek/pg-index-health + * + * This file is a part of "pg-index-health" - a Java library for + * analyzing and maintaining indexes health in PostgreSQL databases. + * + * Licensed under the Apache License 2.0 + */ + +package io.github.mfvanek.pg.checks.extractors; + +import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor; +import io.github.mfvanek.pg.model.column.Column; +import io.github.mfvanek.pg.model.index.IndexWithColumns; + +import java.sql.ResultSet; +import java.sql.SQLException; +import javax.annotation.Nonnull; + +import static io.github.mfvanek.pg.checks.extractors.TableExtractor.TABLE_NAME; + +/** + * A mapper from raw data to {@link IndexWithColumns} model. + * + * @author Ivan Vahrushev + * @since 0.10.4 + */ +public class IndexWithSingleColumnExtractor implements ResultSetExtractor { + + public static final String INDEX_NAME = "index_name"; + public static final String INDEX_SIZE = "index_size"; + + private final ResultSetExtractor columnExtractor; + + private IndexWithSingleColumnExtractor() { + this.columnExtractor = ColumnExtractor.of(); + } + + /** + * {@inheritDoc} + */ + @Nonnull + @Override + public IndexWithColumns extractData(@Nonnull final ResultSet resultSet) throws SQLException { + final String tableName = resultSet.getString(TABLE_NAME); + final String indexName = resultSet.getString(INDEX_NAME); + final long indexSize = resultSet.getLong(INDEX_SIZE); + final Column column = columnExtractor.extractData(resultSet); + return IndexWithColumns.ofSingle(tableName, indexName, indexSize, column); + } + + @Nonnull + public static ResultSetExtractor of() { + return new IndexWithSingleColumnExtractor(); + } +} diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/TableExtractor.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/TableExtractor.java index fb3f66d2..e4eb0ebf 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/TableExtractor.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/extractors/TableExtractor.java @@ -25,6 +25,9 @@ */ public class TableExtractor implements ResultSetExtractor { + public static final String TABLE_NAME = "table_name"; + public static final String TABLE_SIZE = "table_size"; + private TableExtractor() { } @@ -34,8 +37,8 @@ private TableExtractor() { @Nonnull @Override public Table extractData(@Nonnull final ResultSet resultSet) throws SQLException { - final String tableName = resultSet.getString("table_name"); - final long tableSize = resultSet.getLong("table_size"); + final String tableName = resultSet.getString(TABLE_NAME); + final long tableSize = resultSet.getLong(TABLE_SIZE); return Table.of(tableName, tableSize); } diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/AbstractCheckOnHost.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/AbstractCheckOnHost.java index 53eeb8dc..bb07eefe 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/AbstractCheckOnHost.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/AbstractCheckOnHost.java @@ -10,6 +10,8 @@ package io.github.mfvanek.pg.checks.host; +import io.github.mfvanek.pg.checks.extractors.IndexWithSingleColumnExtractor; +import io.github.mfvanek.pg.checks.extractors.TableExtractor; import io.github.mfvanek.pg.common.maintenance.DatabaseCheckOnHost; import io.github.mfvanek.pg.common.maintenance.Diagnostic; import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor; @@ -33,12 +35,13 @@ */ abstract class AbstractCheckOnHost implements DatabaseCheckOnHost { - protected static final String TABLE_NAME = "table_name"; - protected static final String INDEX_NAME = "index_name"; - protected static final String TABLE_SIZE = "table_size"; - protected static final String INDEX_SIZE = "index_size"; + protected static final String TABLE_NAME = TableExtractor.TABLE_NAME; + protected static final String INDEX_NAME = IndexWithSingleColumnExtractor.INDEX_NAME; + protected static final String TABLE_SIZE = TableExtractor.TABLE_SIZE; + protected static final String INDEX_SIZE = IndexWithSingleColumnExtractor.INDEX_SIZE; protected static final String BLOAT_SIZE = "bloat_size"; protected static final String BLOAT_PERCENTAGE = "bloat_percentage"; + protected static final String CONSTRAINT_NAME = "constraint_name"; /** * An original java type representing database object. diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/BtreeIndexesOnArrayColumnsCheckOnHost.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/BtreeIndexesOnArrayColumnsCheckOnHost.java index 4906f71d..4c61d447 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/BtreeIndexesOnArrayColumnsCheckOnHost.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/BtreeIndexesOnArrayColumnsCheckOnHost.java @@ -10,12 +10,10 @@ package io.github.mfvanek.pg.checks.host; -import io.github.mfvanek.pg.checks.extractors.ColumnExtractor; +import io.github.mfvanek.pg.checks.extractors.IndexWithSingleColumnExtractor; import io.github.mfvanek.pg.common.maintenance.Diagnostic; -import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor; import io.github.mfvanek.pg.connection.PgConnection; import io.github.mfvanek.pg.model.PgContext; -import io.github.mfvanek.pg.model.column.Column; import io.github.mfvanek.pg.model.index.IndexWithColumns; import java.util.List; @@ -34,16 +32,15 @@ public BtreeIndexesOnArrayColumnsCheckOnHost(@Nonnull final PgConnection pgConne super(IndexWithColumns.class, pgConnection, Diagnostic.BTREE_INDEXES_ON_ARRAY_COLUMNS); } + /** + * Returns B-tree indexes on array columns in the specified schema. + * + * @param pgContext check's context with the specified schema + * @return list of B-tree indexes on array columns + */ @Nonnull @Override public List check(@Nonnull final PgContext pgContext) { - final ResultSetExtractor columnExtractor = ColumnExtractor.of(); - return executeQuery(pgContext, rs -> { - final String tableName = rs.getString(TABLE_NAME); - final String indexName = rs.getString(INDEX_NAME); - final long indexSize = rs.getLong(INDEX_SIZE); - final Column column = columnExtractor.extractData(rs); - return IndexWithColumns.ofSingle(tableName, indexName, indexSize, column); - }); + return executeQuery(pgContext, IndexWithSingleColumnExtractor.of()); } } diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java index 7272f912..0422a26f 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/ForeignKeysNotCoveredWithIndexCheckOnHost.java @@ -44,7 +44,7 @@ public ForeignKeysNotCoveredWithIndexCheckOnHost(@Nonnull final PgConnection pgC public List check(@Nonnull final PgContext pgContext) { return executeQuery(pgContext, rs -> { final String tableName = rs.getString(TABLE_NAME); - final String constraintName = rs.getString("constraint_name"); + final String constraintName = rs.getString(CONSTRAINT_NAME); final Array columnsArray = rs.getArray("columns"); final String[] rawColumns = (String[]) columnsArray.getArray(); final List columns = ColumnsInForeignKeyParser.parseRawColumnData(tableName, rawColumns); diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/IndexesWithBooleanCheckOnHost.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/IndexesWithBooleanCheckOnHost.java index 6235cc59..bfa9dfdd 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/IndexesWithBooleanCheckOnHost.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/IndexesWithBooleanCheckOnHost.java @@ -10,12 +10,10 @@ package io.github.mfvanek.pg.checks.host; -import io.github.mfvanek.pg.checks.extractors.ColumnExtractor; +import io.github.mfvanek.pg.checks.extractors.IndexWithSingleColumnExtractor; import io.github.mfvanek.pg.common.maintenance.Diagnostic; -import io.github.mfvanek.pg.common.maintenance.ResultSetExtractor; import io.github.mfvanek.pg.connection.PgConnection; import io.github.mfvanek.pg.model.PgContext; -import io.github.mfvanek.pg.model.column.Column; import io.github.mfvanek.pg.model.index.IndexWithColumns; import java.util.List; @@ -42,13 +40,6 @@ public IndexesWithBooleanCheckOnHost(@Nonnull final PgConnection pgConnection) { @Nonnull @Override public List check(@Nonnull final PgContext pgContext) { - final ResultSetExtractor columnExtractor = ColumnExtractor.of(); - return executeQuery(pgContext, rs -> { - final String tableName = rs.getString(TABLE_NAME); - final String indexName = rs.getString(INDEX_NAME); - final long indexSize = rs.getLong(INDEX_SIZE); - final Column column = columnExtractor.extractData(rs); - return IndexWithColumns.ofSingle(tableName, indexName, indexSize, column); - }); + return executeQuery(pgContext, IndexWithSingleColumnExtractor.of()); } } diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHost.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHost.java new file mode 100644 index 00000000..f1ff44b7 --- /dev/null +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHost.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2024. Ivan Vakhrushev and others. + * https://github.com/mfvanek/pg-index-health + * + * This file is a part of "pg-index-health" - a Java library for + * analyzing and maintaining indexes health in PostgreSQL databases. + * + * Licensed under the Apache License 2.0 + */ + +package io.github.mfvanek.pg.checks.host; + +import io.github.mfvanek.pg.common.maintenance.Diagnostic; +import io.github.mfvanek.pg.connection.PgConnection; +import io.github.mfvanek.pg.model.PgContext; +import io.github.mfvanek.pg.model.constraint.Constraint; +import io.github.mfvanek.pg.model.constraint.ConstraintType; + +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Check for not valid constraints on a specific host. + * + * @author Blohny + * @since 0.10.4 + */ +public class NotValidConstraintsCheckOnHost extends AbstractCheckOnHost { + + public NotValidConstraintsCheckOnHost(@Nonnull final PgConnection pgConnection) { + super(Constraint.class, pgConnection, Diagnostic.NOT_VALID_CONSTRAINTS); + } + + /** + * Returns not valid constraints in the specified schema. + * + * @param pgContext check's context with the specified schema + * @return list of not valid constraints + * @see Constraint + */ + @Nonnull + @Override + public List check(@Nonnull final PgContext pgContext) { + return executeQuery(pgContext, rs -> { + final String tableName = rs.getString(TABLE_NAME); + final String constraintName = rs.getString(CONSTRAINT_NAME); + final String constraintType = rs.getString("constraint_type"); + final ConstraintType ct = ConstraintType.valueFrom(constraintType); + return Constraint.ofType(tableName, constraintName, ct); + }); + } +} diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java index 861767c0..467b2fe7 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/AbstractHealthLogger.java @@ -27,6 +27,7 @@ import io.github.mfvanek.pg.model.PgContext; import io.github.mfvanek.pg.model.column.Column; import io.github.mfvanek.pg.model.column.ColumnWithSerialType; +import io.github.mfvanek.pg.model.constraint.Constraint; import io.github.mfvanek.pg.model.constraint.ForeignKey; import io.github.mfvanek.pg.model.function.StoredFunction; import io.github.mfvanek.pg.model.index.DuplicatedIndexes; @@ -96,6 +97,7 @@ public final List logAll(@Nonnull final Exclusions exclusions, logResult.add(logColumnsWithSerialTypes(databaseChecks, pgContext)); logResult.add(logFunctionsWithoutDescription(databaseChecks, pgContext)); logResult.add(logIndexesWithBoolean(databaseChecks, pgContext)); + logResult.add(logNotValidConstraints(databaseChecks, pgContext)); logResult.add(logBtreeIndexesOnArrayColumns(databaseChecks, exclusions, pgContext)); return logResult; } @@ -239,6 +241,13 @@ private String logBtreeIndexesOnArrayColumns(@Nonnull final DatabaseChecks datab FilterIndexesByNamePredicate.of(exclusions.getBtreeIndexesOnArrayColumnsExclusions()), pgContext, SimpleLoggingKey.BTREE_INDEXES_ON_ARRAY_COLUMNS); } + @Nonnull + private String logNotValidConstraints(@Nonnull final DatabaseChecks databaseChecks, + @Nonnull final PgContext pgContext) { + return logCheckResult(databaseChecks.getCheck(Diagnostic.NOT_VALID_CONSTRAINTS, Constraint.class), + c -> true, pgContext, SimpleLoggingKey.NOT_VALID_CONSTRAINTS); + } + @Nonnull private String logCheckResult(@Nonnull final DatabaseCheckOnCluster check, @Nonnull final Predicate exclusionsFilter, diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java index bebcd07a..d5cc4574 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/health/logger/SimpleLoggingKey.java @@ -31,6 +31,7 @@ public enum SimpleLoggingKey implements LoggingKey { COLUMNS_WITH_SERIAL_TYPES("columns_with_serial_types"), FUNCTIONS_WITHOUT_DESCRIPTION("functions_without_description"), INDEXES_WITH_BOOLEAN("indexes_with_boolean"), + NOT_VALID_CONSTRAINTS("not_valid_constraints"), BTREE_INDEXES_ON_ARRAY_COLUMNS("btree_indexes_on_array_columns"); private final String subKeyName; diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java index 5e6b8056..ed828261 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/DatabaseChecks.java @@ -22,6 +22,7 @@ import io.github.mfvanek.pg.checks.cluster.IndexesWithNullValuesCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.IntersectedIndexesCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.InvalidIndexesCheckOnCluster; +import io.github.mfvanek.pg.checks.cluster.NotValidConstraintsCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.TablesWithBloatCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.TablesWithMissingIndexesCheckOnCluster; import io.github.mfvanek.pg.checks.cluster.TablesWithoutDescriptionCheckOnCluster; @@ -60,6 +61,7 @@ public DatabaseChecks(@Nonnull final HighAvailabilityPgConnection haPgConnection new ColumnsWithSerialTypesCheckOnCluster(haPgConnection), new FunctionsWithoutDescriptionCheckOnCluster(haPgConnection), new IndexesWithBooleanCheckOnCluster(haPgConnection), + new NotValidConstraintsCheckOnCluster(haPgConnection), new BtreeIndexesOnArrayColumnsCheckOnCluster(haPgConnection) ); allChecks.forEach(check -> this.checks.putIfAbsent(check.getDiagnostic(), check)); diff --git a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java index 68088583..bfdef3f1 100644 --- a/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java +++ b/pg-index-health/src/main/java/io/github/mfvanek/pg/common/maintenance/Diagnostic.java @@ -40,6 +40,7 @@ public enum Diagnostic { COLUMNS_WITH_SERIAL_TYPES(ExecutionTopology.ON_PRIMARY, "non_primary_key_columns_with_serial_types.sql", QueryExecutors::executeQueryWithSchema), FUNCTIONS_WITHOUT_DESCRIPTION(ExecutionTopology.ON_PRIMARY, "functions_without_description.sql", QueryExecutors::executeQueryWithSchema), INDEXES_WITH_BOOLEAN(ExecutionTopology.ON_PRIMARY, "indexes_with_boolean.sql", QueryExecutors::executeQueryWithSchema), + NOT_VALID_CONSTRAINTS(ExecutionTopology.ON_PRIMARY, "check_not_valid_constraints.sql", QueryExecutors::executeQueryWithSchema), BTREE_INDEXES_ON_ARRAY_COLUMNS(ExecutionTopology.ON_PRIMARY, "btree_indexes_on_array_columns.sql", QueryExecutors::executeQueryWithSchema); private final ExecutionTopology executionTopology; diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnClusterTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnClusterTest.java new file mode 100644 index 00000000..3bb04102 --- /dev/null +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/cluster/NotValidConstraintsCheckOnClusterTest.java @@ -0,0 +1,64 @@ +/* + * 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.predicates.FilterTablesByNamePredicate; +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.constraint.Constraint; +import io.github.mfvanek.pg.model.constraint.ConstraintType; +import io.github.mfvanek.pg.support.DatabaseAwareTestBase; +import io.github.mfvanek.pg.support.ExecuteUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class NotValidConstraintsCheckOnClusterTest extends DatabaseAwareTestBase { + + private final DatabaseCheckOnCluster check = new NotValidConstraintsCheckOnCluster(getHaPgConnection()); + + @Test + void shouldSatisfyContract() { + assertThat(check.getType()).isEqualTo(Constraint.class); + assertThat(check.getDiagnostic()).isEqualTo(Diagnostic.NOT_VALID_CONSTRAINTS); + } + + @ParameterizedTest + @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) + void onDatabaseWithThem(final String schemaName) { + executeTestOnDatabase(schemaName, dbp -> dbp.withNotValidConstraints().withUniqueConstraintOnSerialColumn(), ctx -> { + final List notValidConstraints = check.check(ctx); + assertThat(notValidConstraints) + .hasSize(2) + .containsExactly( + Constraint.ofType(ctx.enrichWithSchema("accounts"), "c_accounts_chk_client_id_not_validated_yet", ConstraintType.CHECK), + Constraint.ofType(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id_not_validated_yet", ConstraintType.FOREIGN_KEY)); + + assertThat(check.check(ctx, FilterTablesByNamePredicate.of(ctx.enrichWithSchema("accounts")))) + .isEmpty(); + + ExecuteUtils.executeOnDatabase(getDataSource(), statement -> { + for (final Constraint constraint : notValidConstraints) { + statement.execute(String.format("alter table %s validate constraint %s;", + constraint.getTableName(), constraint.getConstraintName())); + } + }); + + assertThat(check.check(ctx)) + .isEmpty(); + }); + } +} diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHostTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHostTest.java new file mode 100644 index 00000000..d90780f7 --- /dev/null +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/checks/host/NotValidConstraintsCheckOnHostTest.java @@ -0,0 +1,64 @@ +/* + * 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.constraint.Constraint; +import io.github.mfvanek.pg.model.constraint.ConstraintType; +import io.github.mfvanek.pg.support.DatabaseAwareTestBase; +import io.github.mfvanek.pg.support.ExecuteUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +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 NotValidConstraintsCheckOnHostTest extends DatabaseAwareTestBase { + + private final DatabaseCheckOnHost check = new NotValidConstraintsCheckOnHost(getPgConnection()); + + @Test + void shouldSatisfyContract() { + assertThat(check) + .hasType(Constraint.class) + .hasDiagnostic(Diagnostic.NOT_VALID_CONSTRAINTS) + .hasHost(getHost()); + } + + @ParameterizedTest + @ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"}) + void onDatabaseWithThem(final String schemaName) { + executeTestOnDatabase(schemaName, dbp -> dbp.withNotValidConstraints().withUniqueConstraintOnSerialColumn(), ctx -> { + final List notValidConstraints = check.check(ctx); + Assertions.assertThat(notValidConstraints) + .hasSize(2) + .containsExactly( + Constraint.ofType(ctx.enrichWithSchema("accounts"), "c_accounts_chk_client_id_not_validated_yet", ConstraintType.CHECK), + Constraint.ofType(ctx.enrichWithSchema("accounts"), "c_accounts_fk_client_id_not_validated_yet", ConstraintType.FOREIGN_KEY)); + + ExecuteUtils.executeOnDatabase(getDataSource(), statement -> { + for (final Constraint constraint : notValidConstraints) { + statement.execute(String.format("alter table %s validate constraint %s;", + constraint.getTableName(), constraint.getConstraintName())); + } + }); + + assertThat(check) + .executing(ctx) + .isEmpty(); + }); + } +} diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java index d04e0a7b..0b21a843 100644 --- a/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/HealthLoggerTest.java @@ -69,6 +69,7 @@ void logAll(final String schemaName) { .withJsonType() .withSerialType() .withFunctions() + .withNotValidConstraints() .withBtreeIndexesOnArrayColumn(), ctx -> { collectStatistics(schemaName); @@ -77,7 +78,7 @@ void logAll(final String schemaName) { .containsExactlyInAnyOrder( "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\t1", + "1999-12-31T23:59:59Z\tdb_indexes_health\tforeign_keys_without_index\t2", "1999-12-31T23:59:59Z\tdb_indexes_health\ttables_without_primary_key\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tindexes_with_null_values\t1", "1999-12-31T23:59:59Z\tdb_indexes_health\tindexes_with_bloat\t16", @@ -91,6 +92,7 @@ void logAll(final String schemaName) { "1999-12-31T23:59:59Z\tdb_indexes_health\tcolumns_with_serial_types\t2", "1999-12-31T23:59:59Z\tdb_indexes_health\tfunctions_without_description\t2", "1999-12-31T23:59:59Z\tdb_indexes_health\tindexes_with_boolean\t1", + "1999-12-31T23:59:59Z\tdb_indexes_health\tnot_valid_constraints\t2", "1999-12-31T23:59:59Z\tdb_indexes_health\tbtree_indexes_on_array_columns\t2"); }); } diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java index 4947d44d..950912c5 100644 --- a/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/common/health/logger/StandardHealthLoggerTest.java @@ -46,7 +46,8 @@ void logAll(final String schemaName) { .withNonSuitableIndex() .withJsonType() .withSerialType() - .withFunctions(), + .withFunctions() + .withNotValidConstraints(), ctx -> { collectStatistics(schemaName); final List logs = logger.logAll(Exclusions.empty(), ctx); @@ -55,7 +56,7 @@ void logAll(final String schemaName) { .containsExactlyInAnyOrder( "invalid_indexes:1", "duplicated_indexes:2", - "foreign_keys_without_index:1", + "foreign_keys_without_index:2", "tables_without_primary_key:1", "indexes_with_null_values:1", "indexes_with_bloat:16", @@ -69,6 +70,7 @@ void logAll(final String schemaName) { "columns_with_serial_types:2", "functions_without_description:2", "indexes_with_boolean:1", + "not_valid_constraints:2", "btree_indexes_on_array_columns:2"); }); } diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/support/DatabasePopulator.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/support/DatabasePopulator.java index be2b3e4f..9bdf2129 100644 --- a/pg-index-health/src/test/java/io/github/mfvanek/pg/support/DatabasePopulator.java +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/support/DatabasePopulator.java @@ -18,6 +18,7 @@ import io.github.mfvanek.pg.support.statements.AddCommentOnFunctionsStatement; import io.github.mfvanek.pg.support.statements.AddCommentOnProceduresStatement; import io.github.mfvanek.pg.support.statements.AddCommentOnTablesStatement; +import io.github.mfvanek.pg.support.statements.AddInvalidForeignKeyStatement; import io.github.mfvanek.pg.support.statements.AddLinksBetweenAccountsAndClientsStatement; import io.github.mfvanek.pg.support.statements.ConvertColumnToJsonTypeStatement; import io.github.mfvanek.pg.support.statements.CreateAccountsTableStatement; @@ -269,8 +270,13 @@ public DatabasePopulator withCommentOnProcedures() { } @Nonnull + public DatabasePopulator withNotValidConstraints() { + statementsToExecuteInSameTransaction.putIfAbsent(95, new AddInvalidForeignKeyStatement(schemaName)); + return this; + } + public DatabasePopulator withBtreeIndexesOnArrayColumn() { - statementsToExecuteInSameTransaction.putIfAbsent(95, new CreateIndexesOnArrayColumn(schemaName)); + statementsToExecuteInSameTransaction.putIfAbsent(96, new CreateIndexesOnArrayColumn(schemaName)); return this; } diff --git a/pg-index-health/src/test/java/io/github/mfvanek/pg/support/statements/AddInvalidForeignKeyStatement.java b/pg-index-health/src/test/java/io/github/mfvanek/pg/support/statements/AddInvalidForeignKeyStatement.java new file mode 100644 index 00000000..7504422a --- /dev/null +++ b/pg-index-health/src/test/java/io/github/mfvanek/pg/support/statements/AddInvalidForeignKeyStatement.java @@ -0,0 +1,32 @@ +/* + * 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.support.statements; + +import java.sql.SQLException; +import java.sql.Statement; +import javax.annotation.Nonnull; + +public class AddInvalidForeignKeyStatement extends AbstractDbStatement { + + public AddInvalidForeignKeyStatement(@Nonnull final String schemaName) { + super(schemaName); + } + + @Override + public void execute(@Nonnull final Statement statement) throws SQLException { + statement.execute(String.format("alter table if exists %1$s.accounts " + + "add constraint c_accounts_fk_client_id_not_validated_yet foreign key (client_id) references %1$s.clients (id) not valid;", + schemaName)); + statement.execute(String.format("alter table if exists %1$s.accounts " + + "add constraint c_accounts_chk_client_id_not_validated_yet check (client_id > 0) not valid;", + schemaName)); + } +}