IndexDefinition.java

package io.github.databaseaudits.catalog;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;

import org.jspecify.annotations.Nullable;

/**
 * One index and its key columns in index order — the value record
 * {@link IndexCatalog} reads from the platform's catalog.
 *
 * <p>
 * A {@code columns} entry is {@code null} where the index part is not a plain
 * column (a PostgreSQL/MySQL expression part, or a MySQL prefix part like
 * {@code col(10)}); a {@code null} never matches a column by name, which is
 * exactly the conservative behavior the audits want. {@code partial} marks a
 * PostgreSQL index with a {@code WHERE} predicate, which does not reliably
 * serve other lookups.
 *
 * @param tableName The table the index belongs to.
 * @param indexName The index name.
 * @param unique Whether the index enforces uniqueness.
 * @param primary Whether the index backs the primary key.
 * @param partial Whether the index has a {@code WHERE} predicate (a PostgreSQL
 *            partial index).
 * @param columns The key columns in index order; an entry is {@code null} for a
 *            part that is not a plain column (expression or prefix part).
 */
public record IndexDefinition(String tableName, String indexName,
        boolean unique, boolean primary, boolean partial,
        List<@Nullable String> columns) {
    /**
     * Whether any key part is not a plain column (expression or prefix part —
     * represented as NULL).
     *
     * @return {@code true} if any key part is an expression or prefix part.
     */
    public boolean hasExpressionColumn() {
        return columns.stream().anyMatch(Objects::isNull);
    }

    /**
     * Whether this index's leading key columns cover the given columns in any
     * order — the rule for an index supporting a foreign key. A partial index
     * never covers; an expression part (null column) never matches.
     *
     * @param columns
     *                    The columns to cover.
     * @return {@code true} if the leading key columns cover {@code columns}.
     */
    public boolean leadingColumnsCover(final List<String> columns) {
        return !partial && this.columns.size() >= columns.size()
                && new HashSet<>(this.columns.subList(0, columns.size()))
                        .containsAll(columns);
    }
}