Skip to content

Commit

Permalink
Merge branch 'master' into feature/postgresql-17-support
Browse files Browse the repository at this point in the history
  • Loading branch information
mfvanek authored Nov 13, 2024
2 parents 0563ea9 + 8029a92 commit ca495d9
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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.predicates.SkipSmallTablesPredicate;
import io.github.mfvanek.pg.model.predicates.SkipTablesByNamePredicate;
import io.github.mfvanek.pg.model.table.TableWithMissingIndex;
import io.github.mfvanek.pg.support.StatisticsAwareTestBase;
Expand Down Expand Up @@ -52,6 +53,10 @@ void onDatabaseWithThem(final String schemaName) {
assertThat(check)
.executing(ctx, SkipTablesByNamePredicate.ofName(ctx, "accounts"))
.isEmpty();

assertThat(check)
.executing(ctx, SkipSmallTablesPredicate.of(1_000_000L))
.isEmpty();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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.predicates.SkipSmallTablesPredicate;
import io.github.mfvanek.pg.model.predicates.SkipTablesByNamePredicate;
import io.github.mfvanek.pg.model.table.Table;
import io.github.mfvanek.pg.support.DatabaseAwareTestBase;
Expand Down Expand Up @@ -58,12 +59,19 @@ void onDatabaseWithThem(final String schemaName) {
@ParameterizedTest
@ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"})
void shouldNotTakingIntoAccountBlankComments(final String schemaName) {
executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withBlankCommentOnTables(), ctx ->
executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withBlankCommentOnTables(), ctx -> {
assertThat(check)
.executing(ctx)
.hasSize(2)
.containsExactly(
Table.of(ctx.enrichWithSchema("accounts"), 0L),
Table.of(ctx.enrichWithSchema("clients"), 0L)));
Table.of(ctx.enrichWithSchema("clients"), 0L));

assertThat(check)
.executing(ctx, SkipSmallTablesPredicate.of(1_234L))
.hasSize(1)
.containsExactly(Table.of(ctx.enrichWithSchema("clients"), 0L))
.allMatch(t -> t.getTableSizeInBytes() > 1_234L);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
import io.github.mfvanek.pg.checks.predicates.FilterIndexesByBloatPredicate;
import io.github.mfvanek.pg.checks.predicates.FilterIndexesByNamePredicate;
import io.github.mfvanek.pg.checks.predicates.FilterIndexesBySizePredicate;
import io.github.mfvanek.pg.checks.predicates.FilterTablesByBloatPredicate;
import io.github.mfvanek.pg.checks.predicates.FilterTablesByNamePredicate;
import io.github.mfvanek.pg.checks.predicates.FilterTablesBySizePredicate;
import io.github.mfvanek.pg.common.maintenance.DatabaseCheckOnCluster;
import io.github.mfvanek.pg.common.maintenance.DatabaseChecks;
import io.github.mfvanek.pg.common.maintenance.Diagnostic;
Expand All @@ -38,6 +35,10 @@
import io.github.mfvanek.pg.model.index.IndexWithNulls;
import io.github.mfvanek.pg.model.index.UnusedIndex;
import io.github.mfvanek.pg.model.object.AnyObject;
import io.github.mfvanek.pg.model.predicates.SkipBloatUnderThresholdPredicate;
import io.github.mfvanek.pg.model.predicates.SkipIndexesByNamePredicate;
import io.github.mfvanek.pg.model.predicates.SkipSmallTablesPredicate;
import io.github.mfvanek.pg.model.predicates.SkipTablesByNamePredicate;
import io.github.mfvanek.pg.model.sequence.SequenceState;
import io.github.mfvanek.pg.model.table.Table;
import io.github.mfvanek.pg.model.table.TableWithBloat;
Expand Down Expand Up @@ -150,21 +151,21 @@ private String logUnusedIndexes(@Nonnull final Exclusions exclusions) {
@Nonnull
private String logTablesWithMissingIndexes(@Nonnull final Exclusions exclusions) {
return logCheckResult(databaseChecksHolder.get().getCheck(Diagnostic.TABLES_WITH_MISSING_INDEXES, TableWithMissingIndex.class),
FilterTablesBySizePredicate.of(exclusions.getTableSizeThresholdInBytes())
.and(FilterTablesByNamePredicate.of(exclusions.getTablesWithMissingIndexesExclusions())), SimpleLoggingKey.TABLES_WITH_MISSING_INDEXES);
SkipSmallTablesPredicate.of(exclusions.getTableSizeThresholdInBytes())
.and(SkipTablesByNamePredicate.of(pgContextHolder.get(), exclusions.getTablesWithMissingIndexesExclusions())), SimpleLoggingKey.TABLES_WITH_MISSING_INDEXES);
}

@Nonnull
private String logTablesWithoutPrimaryKey(@Nonnull final Exclusions exclusions) {
return logCheckResult(databaseChecksHolder.get().getCheck(Diagnostic.TABLES_WITHOUT_PRIMARY_KEY, Table.class),
FilterTablesBySizePredicate.of(exclusions.getTableSizeThresholdInBytes())
.and(FilterTablesByNamePredicate.of(exclusions.getTablesWithoutPrimaryKeyExclusions())), SimpleLoggingKey.TABLES_WITHOUT_PRIMARY_KEY);
SkipSmallTablesPredicate.of(exclusions.getTableSizeThresholdInBytes())
.and(SkipTablesByNamePredicate.of(pgContextHolder.get(), exclusions.getTablesWithoutPrimaryKeyExclusions())), SimpleLoggingKey.TABLES_WITHOUT_PRIMARY_KEY);
}

@Nonnull
private String logIndexesWithNullValues(@Nonnull final Exclusions exclusions) {
return logCheckResult(databaseChecksHolder.get().getCheck(Diagnostic.INDEXES_WITH_NULL_VALUES, IndexWithNulls.class),
FilterIndexesByNamePredicate.of(exclusions.getIndexesWithNullValuesExclusions()), SimpleLoggingKey.INDEXES_WITH_NULL_VALUES);
SkipIndexesByNamePredicate.of(pgContextHolder.get(), exclusions.getIndexesWithNullValuesExclusions()), SimpleLoggingKey.INDEXES_WITH_NULL_VALUES);
}

@Nonnull
Expand All @@ -177,13 +178,13 @@ private String logIndexesBloat(@Nonnull final Exclusions exclusions) {
@Nonnull
private String logTablesBloat(@Nonnull final Exclusions exclusions) {
return logCheckResult(databaseChecksHolder.get().getCheck(Diagnostic.BLOATED_TABLES, TableWithBloat.class),
FilterTablesByBloatPredicate.of(exclusions.getTableBloatSizeThresholdInBytes(), exclusions.getTableBloatPercentageThreshold())
.and(FilterTablesBySizePredicate.of(exclusions.getTableSizeThresholdInBytes())), SimpleLoggingKey.BLOATED_TABLES);
SkipBloatUnderThresholdPredicate.of(exclusions.getTableBloatSizeThresholdInBytes(), exclusions.getTableBloatPercentageThreshold())
.and(SkipSmallTablesPredicate.of(exclusions.getTableSizeThresholdInBytes())), SimpleLoggingKey.BLOATED_TABLES);
}

private String logBtreeIndexesOnArrayColumns(@Nonnull final Exclusions exclusions) {
return logCheckResult(databaseChecksHolder.get().getCheck(Diagnostic.BTREE_INDEXES_ON_ARRAY_COLUMNS, Index.class),
FilterIndexesByNamePredicate.of(exclusions.getBtreeIndexesOnArrayColumnsExclusions()), SimpleLoggingKey.BTREE_INDEXES_ON_ARRAY_COLUMNS);
SkipIndexesByNamePredicate.of(pgContextHolder.get(), exclusions.getBtreeIndexesOnArrayColumnsExclusions()), SimpleLoggingKey.BTREE_INDEXES_ON_ARRAY_COLUMNS);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public class Index implements DbObject, TableNameAware, IndexNameAware, Comparab
private final String tableName;
private final String indexName;

/**
* Constructs an {@code Index} with the specified table and index names.
*
* @param tableName the name of the table associated with this index; must be non-blank.
* @param indexName the name of this index; must be non-blank.
*/
@SuppressWarnings("WeakerAccess")
protected Index(@Nonnull final String tableName, @Nonnull final String indexName) {
this.tableName = Validators.tableNameNotBlank(tableName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.predicates;

import io.github.mfvanek.pg.model.DbObject;
import io.github.mfvanek.pg.model.validation.Validators;

import java.util.function.Predicate;

/**
* An abstract base class for filtering {@link DbObject} instances based on a size threshold.
* This class implements {@link Predicate} and serves as a foundation for classes that
* filter database objects according to their size in bytes.
*
* <p>Subclasses must implement the {@code test} method to specify the filtering logic.</p>
*
* @author Ivan Vakhrushev
* @see DbObject
* @see Predicate
* @since 0.13.3
*/
abstract class AbstractFilterBySize implements Predicate<DbObject> {

/**
* The size threshold in bytes for filtering {@link DbObject} instances.
* Subclasses may use this threshold to include or exclude objects based on their size.
*/
protected final long thresholdInBytes;

/**
* Constructs an {@code AbstractFilterBySize} with the specified size threshold.
*
* @param thresholdInBytes the minimum size in bytes for an object to pass the filter;
* must be non-negative.
* @throws IllegalArgumentException if {@code thresholdInBytes} is negative.
*/
protected AbstractFilterBySize(final long thresholdInBytes) {
this.thresholdInBytes = Validators.sizeNotNegative(thresholdInBytes, "thresholdInBytes");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static Predicate<DbObject> ofName(@Nonnull final String rawSequenceNameTo
* @param rawSequenceNamesToSkip the collection of raw sequence names to skip; must be non-null
* @return a {@code SkipBySequenceNamePredicate} instance for the specified sequence names
*/
public static Predicate<DbObject> of(@Nonnull final Collection<String> rawSequenceNamesToSkip) {
public static Predicate<DbObject> ofPublic(@Nonnull final Collection<String> rawSequenceNamesToSkip) {
return new SkipBySequenceNamePredicate(PgContext.ofPublic(), rawSequenceNamesToSkip);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static Predicate<DbObject> ofName(@Nonnull final String rawIndexNameToSki
* @param rawIndexNamesToSkip a collection of raw index names to skip; must be non-null
* @return a {@link Predicate} to skip the specified index names
*/
public static Predicate<DbObject> of(@Nonnull final Collection<String> rawIndexNamesToSkip) {
public static Predicate<DbObject> ofPublic(@Nonnull final Collection<String> rawIndexNamesToSkip) {
return new SkipIndexesByNamePredicate(PgContext.ofPublic(), rawIndexNamesToSkip);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.predicates;

import io.github.mfvanek.pg.model.DbObject;
import io.github.mfvanek.pg.model.table.TableSizeAware;

import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

/**
* A predicate that filters out small tables based on a specified size threshold.
* This class extends {@link AbstractFilterBySize} and evaluates {@link TableSizeAware}
* instances to determine if they meet or exceed the specified minimum table size.
*
* @author Ivan Vakhrushev
* @see TableSizeAware
* @see DbObject
* @see AbstractFilterBySize
* @since 0.13.3
*/
@Immutable
@ThreadSafe
public final class SkipSmallTablesPredicate extends AbstractFilterBySize {

private SkipSmallTablesPredicate(final long thresholdInBytes) {
super(thresholdInBytes);
}

/**
* Evaluates whether the given {@link DbObject} should pass the filter based on its size.
* If the object is not an instance of {@link TableSizeAware}, or if the threshold is zero,
* it automatically passes the filter. Otherwise, the object's size is compared to the threshold.
*
* @param dbObject the {@code DbObject} to be evaluated; must not be null.
* @return {@code true} if the table size is greater than or equal to the threshold, or if the object is not {@code TableSizeAware}; {@code false} otherwise.
*/
@Override
public boolean test(@Nonnull final DbObject dbObject) {
if (thresholdInBytes == 0L) {
return true;
}
if (!(dbObject instanceof TableSizeAware)) {
return true;
}
final TableSizeAware tableSizeAware = (TableSizeAware) dbObject;
return tableSizeAware.getTableSizeInBytes() >= thresholdInBytes;
}

/**
* Creates a {@code SkipSmallTablesPredicate} with the specified size threshold.
*
* @param thresholdInBytes the minimum table size in bytes required for a table to pass the filter;
* must be non-negative.
* @return a predicate that filters out tables smaller than the specified size threshold.
*/
@Nonnull
public static Predicate<DbObject> of(final long thresholdInBytes) {
return new SkipSmallTablesPredicate(thresholdInBytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static Predicate<DbObject> ofName(@Nonnull final String rawTableNameToSki
* @return a predicate that skips the specified tables in the "public" schema
* @throws NullPointerException if {@code rawTableNamesToSkip} is null
*/
public static Predicate<DbObject> of(@Nonnull final Collection<String> rawTableNamesToSkip) {
public static Predicate<DbObject> ofPublic(@Nonnull final Collection<String> rawTableNamesToSkip) {
return new SkipTablesByNamePredicate(PgContext.ofPublic(), rawTableNamesToSkip);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("rawSequenceNameToSkip cannot be blank");

assertThatThrownBy(() -> SkipBySequenceNamePredicate.of(null))
assertThatThrownBy(() -> SkipBySequenceNamePredicate.ofPublic(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("rawNamesToSkip cannot be null");

Expand Down Expand Up @@ -72,7 +72,7 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
@Test
void shouldNotCastObjectsWhenExclusionsIsEmpty() {
final SequenceState mockSequence = Mockito.mock(SequenceState.class);
assertThat(SkipBySequenceNamePredicate.of(List.of()))
assertThat(SkipBySequenceNamePredicate.ofPublic(List.of()))
.accepts(mockSequence);
Mockito.verify(mockSequence, Mockito.never()).getSequenceName();
}
Expand All @@ -95,7 +95,7 @@ void shouldWorkForSingleSequence() {

@Test
void shouldWorkForMultipleSequences() {
assertThat(SkipBySequenceNamePredicate.of(Set.of("s1", "S2")))
assertThat(SkipBySequenceNamePredicate.ofPublic(Set.of("s1", "S2")))
.accepts(Table.of("t", 0L))
.accepts(SequenceState.of("s11", "int", 80.0))
.rejects(SequenceState.of("s1", "int", 80.0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("rawIndexNameToSkip cannot be blank");

assertThatThrownBy(() -> SkipIndexesByNamePredicate.of(null))
assertThatThrownBy(() -> SkipIndexesByNamePredicate.ofPublic(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("rawNamesToSkip cannot be null");

Expand Down Expand Up @@ -73,7 +73,7 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
@Test
void shouldNotCastObjectsWhenExclusionsIsEmpty() {
final Index mockIndex = Mockito.mock(Index.class);
assertThat(SkipIndexesByNamePredicate.of(List.of()))
assertThat(SkipIndexesByNamePredicate.ofPublic(List.of()))
.accepts(mockIndex);
Mockito.verify(mockIndex, Mockito.never()).getIndexName();
}
Expand All @@ -96,7 +96,7 @@ void shouldWorkForSingleIndex() {

@Test
void shouldWorkForMultipleIndexes() {
assertThat(SkipIndexesByNamePredicate.of(Set.of("i1", "I2")))
assertThat(SkipIndexesByNamePredicate.ofPublic(Set.of("i1", "I2")))
.accepts(Table.of("t", 0L))
.accepts(SequenceState.of("s11", "int", 80.0))
.accepts(ColumnWithSerialType.ofSerial(Column.ofNullable("t", "c"), "s1"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.predicates;

import io.github.mfvanek.pg.model.sequence.SequenceState;
import io.github.mfvanek.pg.model.table.Table;
import io.github.mfvanek.pg.model.table.TableWithBloat;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class SkipSmallTablesPredicateTest {

@Test
void shouldThrowExceptionWhenInvalidDataPassed() {
assertThatThrownBy(() -> SkipSmallTablesPredicate.of(-1L))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("thresholdInBytes cannot be less than zero");
}

@Test
void shouldNotCastObjectsWhenThresholdIsZero() {
final Table mockTable = Mockito.mock(Table.class);
assertThat(SkipSmallTablesPredicate.of(0L))
.accepts(mockTable);
Mockito.verify(mockTable, Mockito.never()).getTableSizeInBytes();
}

@Test
void shouldWork() {
assertThat(SkipSmallTablesPredicate.of(10L))
.accepts(Table.of("t", 10L))
.accepts(Table.of("t", 11L))
.accepts(SequenceState.of("s1", "int", 80.0))
.rejects(TableWithBloat.of(Table.of("t2", 1L), 1L, 10.0))
.rejects(Table.of("t2", 1L));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("rawTableNameToSkip cannot be blank");

assertThatThrownBy(() -> SkipTablesByNamePredicate.of(null))
assertThatThrownBy(() -> SkipTablesByNamePredicate.ofPublic(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("rawNamesToSkip cannot be null");

Expand Down Expand Up @@ -82,7 +82,7 @@ void shouldWorkForSingleTable() {

@Test
void shouldWorkForMultipleTables() {
assertThat(SkipTablesByNamePredicate.of(Set.of("t", "T2")))
assertThat(SkipTablesByNamePredicate.ofPublic(Set.of("t", "T2")))
.accepts(Index.of("t1", "i1"))
.rejects(Index.of("t", "i"))
.rejects(Index.of("T", "I"))
Expand Down
Loading

0 comments on commit ca495d9

Please sign in to comment.