Skip to content

Commit

Permalink
[POSSIBLE_OBJECT_NAME_OVERFLOW] Add support for partitioned tables. […
Browse files Browse the repository at this point in the history
…PRIMARY_KEYS_WITH_SERIAL_TYPES] Add support for partitioned tables. [PRIMARY_KEYS_WITH_SERIAL_TYPES] Fix insufficient permissions error (#544)

* Update queries

* [POSSIBLE_OBJECT_NAME_OVERFLOW] Add support for partitioned tables

* [PRIMARY_KEYS_WITH_SERIAL_TYPES] Add support for partitioned tables

* Fix test
  • Loading branch information
mfvanek authored Dec 20, 2024
1 parent 555b48b commit 3e9a63c
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 19 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ All checks can be divided into 2 groups:
| 17 | Tables with [not valid constraints](https://habr.com/ru/articles/800121/) | **runtime**/static | no | [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 | no | [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** | not applicable | [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 | no | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/primary_keys_with_serial_types.sql) |
| 20 | Primary keys with [serial types](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_serial) | static | yes | [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 | no | [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 | no | [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 | no | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/possible_object_name_overflow.sql) |
| 23 | Possible object name overflow (identifiers with maximum length) | static | yes | [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 | yes | [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 | no | [sql](https://github.com/mfvanek/pg-index-health-sql/blob/master/sql/foreign_keys_with_unmatched_column_type.sql) |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.github.mfvanek.pg.core.checks.common.DatabaseCheckOnHost;
import io.github.mfvanek.pg.core.checks.common.Diagnostic;
import io.github.mfvanek.pg.core.fixtures.support.DatabaseAwareTestBase;
import io.github.mfvanek.pg.core.fixtures.support.DatabasePopulator;
import io.github.mfvanek.pg.model.context.PgContext;
import io.github.mfvanek.pg.model.dbobject.AnyObject;
import io.github.mfvanek.pg.model.dbobject.PgObjectType;
Expand Down Expand Up @@ -42,18 +43,38 @@ void shouldSatisfyContract() {
@ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"})
void onDatabaseWithThem(final String schemaName) {
executeTestOnDatabase(schemaName, dbp -> dbp.withReferences().withMaterializedView().withIdentityPrimaryKey(), ctx -> {
final String matViewName = ctx.enrichWithSchema("accounts_materialized_view_with_length_63_1234567890_1234567890");
final String constraintName = ctx.enrichWithSchema("num_less_than_million_constraint_with_length_63_1234567890_1234");
final String matViewName = "accounts_materialized_view_with_length_63_1234567890_1234567890";
final String constraintName = "num_less_than_million_constraint_with_length_63_1234567890_1234";
assertThat(check)
.executing(ctx)
.hasSize(2)
.containsExactlyInAnyOrder(
AnyObject.ofType(matViewName, PgObjectType.MATERIALIZED_VIEW),
AnyObject.ofType(constraintName, PgObjectType.CONSTRAINT));
AnyObject.ofType(ctx, matViewName, PgObjectType.MATERIALIZED_VIEW),
AnyObject.ofType(ctx, constraintName, PgObjectType.CONSTRAINT));

assertThat(check)
.executing(ctx, SkipDbObjectsByNamePredicate.of(List.of(matViewName, constraintName)))
.executing(ctx, SkipDbObjectsByNamePredicate.of(ctx, List.of(matViewName, constraintName)))
.isEmpty();
});
}

@ParameterizedTest
@ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"})
void shouldWorkWithPartitionedTables(final String schemaName) {
executeTestOnDatabase(schemaName, DatabasePopulator::withVeryLongNamesInPartitionedTable, ctx ->
assertThat(check)
.executing(ctx)
.hasSize(9)
.containsExactly(
AnyObject.ofType(ctx, "entity_default_long_1234567890_1234567890_1234567890_12345_pkey", PgObjectType.CONSTRAINT),
AnyObject.ofType(ctx, "entity_long_1234567890_1234567890_1234567890_1234567890_12_pkey", PgObjectType.CONSTRAINT),
AnyObject.ofType(ctx, "entity_default_long_1234567890_1234567890_1234567890_12345_pkey", PgObjectType.INDEX),
AnyObject.ofType(ctx, "entity_default_long_1234567890_123456789_ref_type_ref_value_idx", PgObjectType.INDEX),
AnyObject.ofType(ctx, "entity_long_1234567890_1234567890_1234567890_1234567890_12_pkey", PgObjectType.PARTITIONED_INDEX),
AnyObject.ofType(ctx, "idx_entity_long_1234567890_1234567890_1234567890_1234567890_123", PgObjectType.PARTITIONED_INDEX),
AnyObject.ofType(ctx, "entity_long_1234567890_1234567890_1234567890_1234567890_1234567", PgObjectType.PARTITIONED_TABLE),
AnyObject.ofType(ctx, "entity_long_1234567890_1234567890_1234567890_1234_entity_id_seq", PgObjectType.SEQUENCE),
AnyObject.ofType(ctx, "entity_default_long_1234567890_1234567890_1234567890_1234567890", PgObjectType.TABLE)
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.github.mfvanek.pg.core.checks.common.DatabaseCheckOnHost;
import io.github.mfvanek.pg.core.checks.common.Diagnostic;
import io.github.mfvanek.pg.core.fixtures.support.DatabaseAwareTestBase;
import io.github.mfvanek.pg.core.fixtures.support.DatabasePopulator;
import io.github.mfvanek.pg.model.column.Column;
import io.github.mfvanek.pg.model.column.ColumnWithSerialType;
import io.github.mfvanek.pg.model.column.SerialType;
Expand Down Expand Up @@ -69,4 +70,19 @@ void onDatabaseWithoutThem(final String schemaName) {
.isEmpty()
);
}

@ParameterizedTest
@ValueSource(strings = {PgContext.DEFAULT_SCHEMA_NAME, "custom"})
void shouldWorkWithPartitionedTables(final String schemaName) {
executeTestOnDatabase(schemaName, DatabasePopulator::withVeryLongNamesInPartitionedTable, ctx -> {
final String tableName = "entity_long_1234567890_1234567890_1234567890_1234567890_1234567";
final String sequenceName = ctx.enrichWithSchema("entity_long_1234567890_1234567890_1234567890_1234_entity_id_seq");
assertThat(check)
.executing(ctx)
.hasSize(1)
.containsExactly(
ColumnWithSerialType.of(Column.ofNotNull(ctx, tableName, "entity_id"), SerialType.BIG_SERIAL, sequenceName)
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import io.github.mfvanek.pg.core.fixtures.support.statements.CreateMaterializedViewStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreateNotSuitableIndexForForeignKeyStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreatePartitionedTableWithNullableFieldsStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreatePartitionedTableWithVeryLongNamesStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreatePartitionedTableWithoutCommentsStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreatePartitionedTableWithoutPrimaryKeyStatement;
import io.github.mfvanek.pg.core.fixtures.support.statements.CreateProceduresStatement;
Expand Down Expand Up @@ -337,6 +338,12 @@ public DatabasePopulator withNullableIndexesInPartitionedTable() {
return this;
}

@Nonnull
public DatabasePopulator withVeryLongNamesInPartitionedTable() {
statementsToExecuteInSameTransaction.putIfAbsent(114, new CreatePartitionedTableWithVeryLongNamesStatement());
return this;
}

public void populate() {
try (SchemaNameHolder ignored = SchemaNameHolder.with(schemaName)) {
ExecuteUtils.executeInTransaction(dataSource, statementsToExecuteInSameTransaction.values());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.core.fixtures.support.statements;

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

public class CreatePartitionedTableWithVeryLongNamesStatement extends AbstractDbStatement {

@Nonnull
@Override
protected List<String> getSqlToExecute() {
return List.of(
"create table if not exists {schemaName}.entity_long_1234567890_1234567890_1234567890_1234567890_1234567(" +
"ref_type varchar(32)," +
"ref_value varchar(64)," +
"entity_id bigserial primary key" +
") partition by range (entity_id);",
"create index if not exists idx_entity_long_1234567890_1234567890_1234567890_1234567890_123 " +
"on {schemaName}.entity_long_1234567890_1234567890_1234567890_1234567890_1234567 (ref_type, ref_value);",
"create table if not exists {schemaName}.entity_default_long_1234567890_1234567890_1234567890_1234567890 " +
"partition of {schemaName}.entity_long_1234567890_1234567890_1234567890_1234567890_1234567 default;"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package io.github.mfvanek.pg.model.dbobject;

import io.github.mfvanek.pg.model.context.PgContext;
import io.github.mfvanek.pg.model.validation.Validators;

import java.util.Objects;
Expand Down Expand Up @@ -108,18 +109,35 @@ public int compareTo(@Nonnull final AnyObject other) {
* @param objectType type of object in a database; should be non-null.
* @return {@code AnyObject} instance
*/
public static AnyObject ofType(@Nonnull final String objectName, @Nonnull final PgObjectType objectType) {
public static AnyObject ofType(@Nonnull final String objectName,
@Nonnull final PgObjectType objectType) {
return new AnyObject(objectName, objectType);
}

/**
* Constructs an {@code AnyObject} instance with given context.
*
* @param pgContext the schema context to enrich object name; must be non-null.
* @param objectName name of object in a database; should be non-blank.
* @param objectType type of object in a database; should be non-null.
* @return {@code AnyObject} instance
* @since 0.14.4
*/
public static AnyObject ofType(@Nonnull final PgContext pgContext,
@Nonnull final String objectName,
@Nonnull final PgObjectType objectType) {
return ofType(PgContext.enrichWith(objectName, pgContext), objectType);
}

/**
* Constructs an {@code AnyObject} instance.
*
* @param objectName name of object in a database; should be non-blank.
* @param objectType literal type of object in a database; should be non-null.
* @return {@code AnyObject} instance
*/
public static AnyObject ofRaw(@Nonnull final String objectName, @Nonnull final String objectType) {
public static AnyObject ofRaw(@Nonnull final String objectName,
@Nonnull final String objectType) {
return new AnyObject(objectName, PgObjectType.valueFrom(objectType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,26 @@ public enum PgObjectType {
* @see <a href="https://www.postgresql.org/docs/current/sql-createtable.html">CREATE TABLE</a>
*/
TABLE("table"),
/**
* A partitioned table in a database.
*
* @see <a href="https://www.postgresql.org/docs/current/ddl-partitioning.html">Table Partitioning</a>
* @since 0.14.4
*/
PARTITIONED_TABLE("partitioned table"),
/**
* An index in a database.
*
* @see <a href="https://www.postgresql.org/docs/current/indexes-types.html">Index Types</a>
*/
INDEX("index"),
/**
* A partitioned index in a database.
*
* @see <a href="https://www.postgresql.org/docs/current/ddl-partitioning.html">Table Partitioning</a>
* @since 0.14.4
*/
PARTITIONED_INDEX("partitioned index"),
/**
* A sequence in a database.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package io.github.mfvanek.pg.model.predicates;

import io.github.mfvanek.pg.model.context.PgContext;
import io.github.mfvanek.pg.model.dbobject.DbObject;

import java.util.Collection;
Expand All @@ -22,6 +23,8 @@
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

import static io.github.mfvanek.pg.model.predicates.AbstractSkipTablesPredicate.prepareFullyQualifiedNamesToSkip;

/**
* A predicate for filtering database objects by their fully qualified names.
* <p>
Expand Down Expand Up @@ -86,6 +89,20 @@ public static Predicate<DbObject> ofName(@Nonnull final String fullyQualifiedObj
return new SkipDbObjectsByNamePredicate(fullyQualifiedObjectNameToSkip);
}

/**
* Creates a predicate to skip a specific object name in the given context.
*
* @param pgContext the PostgreSQL context to use; must be non-null
* @param objectNameToSkip the object name to skip, must be non-null and non-blank
* @return a predicate that skips the specified object
* @since 0.14.4
*/
@Nonnull
public static Predicate<DbObject> ofName(@Nonnull final PgContext pgContext,
@Nonnull final String objectNameToSkip) {
return ofName(PgContext.enrichWith(objectNameToSkip, pgContext));
}

/**
* Creates a predicate to skip multiple fully qualified object names.
*
Expand All @@ -97,4 +114,18 @@ public static Predicate<DbObject> ofName(@Nonnull final String fullyQualifiedObj
public static Predicate<DbObject> of(@Nonnull final Collection<String> fullyQualifiedObjectNamesToSkip) {
return new SkipDbObjectsByNamePredicate(fullyQualifiedObjectNamesToSkip);
}

/**
* Creates a predicate to skip multiple object names in the given context.
*
* @param pgContext the PostgreSQL context to use; must be non-null
* @param objectNamesToSkip a collection of object names to skip, must be non-null
* @return a predicate that skips the specified objects
* @since 0.14.4
*/
@Nonnull
public static Predicate<DbObject> of(@Nonnull final PgContext pgContext,
@Nonnull final Collection<String> objectNamesToSkip) {
return of(prepareFullyQualifiedNamesToSkip(pgContext, objectNamesToSkip));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package io.github.mfvanek.pg.model.dbobject;

import io.github.mfvanek.pg.model.context.PgContext;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -58,6 +59,10 @@ void withInvalidValues() {
void testToString() {
assertThat(AnyObject.ofRaw("mv", "Materialized View"))
.hasToString("AnyObject{objectName='mv', objectType=MATERIALIZED_VIEW}");
assertThat(AnyObject.ofType("pi", PgObjectType.PARTITIONED_INDEX))
.hasToString("AnyObject{objectName='pi', objectType=PARTITIONED_INDEX}");
assertThat(AnyObject.ofType(PgContext.of("tst"), "pi", PgObjectType.PARTITIONED_INDEX))
.hasToString("AnyObject{objectName='tst.pi', objectType=PARTITIONED_INDEX}");
}

@SuppressWarnings("ConstantConditions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package io.github.mfvanek.pg.model.predicates;

import io.github.mfvanek.pg.model.context.PgContext;
import io.github.mfvanek.pg.model.table.Table;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
Expand All @@ -21,9 +22,10 @@

class SkipDbObjectsByNamePredicateTest {

private static final Table FIRST = Table.of("custom.TABLE1", 1L);
private static final Table SECOND = Table.of("custom.TABLE2", 2L);
private static final Table THIRD = Table.of("custom.TABLE3", 3L);
private static final PgContext CTX = PgContext.of("CUSTOM");
private static final Table FIRST = Table.of(CTX, "TABLE1", 1L);
private static final Table SECOND = Table.of(CTX, "TABLE2", 2L);
private static final Table THIRD = Table.of(CTX, "TABLE3", 3L);

@SuppressWarnings("DataFlowIssue")
@Test
Expand All @@ -43,6 +45,9 @@ void shouldThrowExceptionWhenInvalidDataPassed() {
assertThatThrownBy(() -> SkipDbObjectsByNamePredicate.of(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("fullyQualifiedObjectNamesToSkip cannot be null");
assertThatThrownBy(() -> SkipDbObjectsByNamePredicate.of(null, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("pgContext cannot be null");
}

@Test
Expand All @@ -51,6 +56,11 @@ void caseShouldNotMatter() {
.accepts(FIRST)
.accepts(SECOND)
.rejects(THIRD);

assertThat(SkipDbObjectsByNamePredicate.ofName(CTX, "table3"))
.accepts(FIRST)
.accepts(SECOND)
.rejects(THIRD);
}

@Test
Expand All @@ -59,6 +69,11 @@ void forList() {
.rejects(FIRST)
.accepts(SECOND)
.rejects(THIRD);

assertThat(SkipDbObjectsByNamePredicate.of(CTX, List.of("table1", "table3")))
.rejects(FIRST)
.accepts(SECOND)
.rejects(THIRD);
}

@Test
Expand Down
Loading

0 comments on commit 3e9a63c

Please sign in to comment.