NAME

DBIO::ClickHouse::Diff::Index - Diff operations for ClickHouse data-skipping indexes

VERSION

version 0.900004

DESCRIPTION

Diff operations for ClickHouse data-skipping indexes -- the data_skipping_indexes section of the model produced by DBIO::ClickHouse::Introspect::Indexes, not the canonical indexes section (which this driver always leaves empty).

Why these get their own identity

A data-skipping index is not a B-tree. It has no is_unique, no ordered column list, and it enforces nothing -- it is a per-granule summary (minmax, set(N), bloom_filter, ngrambf_v1, ...) computed over an expression, used to skip granules during a scan. Core's DBIO::Diff::Compare/changed_index_fields compares is_unique and a columns list; neither field exists here, so it would report every index as unchanged. This module compares what a skipping index actually is:

expr          the indexed expression, verbatim
type_full     the index type WITH its arguments (set(100), not set)
granularity   how many index granules one summary covers

type_full rather than type, because set(100) and set(1000) are both type set and are not the same index.

They are altered, not created

Skipping indexes are declared inline in CREATE TABLE, so there is no CREATE INDEX statement to emit. Adding one is ALTER TABLE ... ADD INDEX, removing one is ALTER TABLE ... DROP INDEX, and there is no ALTER INDEX at all -- a changed definition becomes a drop-then-add pair. For a table that is being created in the same pass the index is emitted inline by DBIO::ClickHouse::Diff::Table, which is why the walk here is scoped to tables present on both sides.

ADD INDEX does not index existing data

ALTER TABLE ... ADD INDEX only affects parts written after it. Existing parts are indexed by

ALTER TABLE `t` MATERIALIZE INDEX `idx`;

which is an asynchronous mutation that rewrites every part of the table. That is a capacity decision, not a schema decision, so this module deliberately does not emit it -- an upgrade must not silently kick off a full-table mutation. Run it yourself when the table is small enough or the window is right.

Every skipping index the target lacks is dropped

The schema is the single owner of the skipping indexes a table keeps. An index the live table has and the target does not is dropped, including the last one: when the target carries no skipping index for a table at all, every index the live table has goes. That is what lets DROP COLUMN take a column whose only index reads it -- the server refuses the column drop while the index stands, so the DROP INDEX has to be there even when the target section for the table is empty.

The walk covers only the skipping indexes DBIO::ClickHouse::Introspect::Indexes reads from system.data_skipping_indices, and only tables present on both sides; a table being created or dropped is not walked (see "diff"). An index created by hand on a table is therefore removed by the next upgrade unless the schema declares it (add_index_declaration).

METHODS

changed_index

my @changed = changed_index($old_info, $new_info);

Returns the list of field names that differ between two skipping-index infos: expr, type_full and granularity. Exported for the same reason DBIO::Diff::Compare's comparators are: so a caller can ask "did this change?" without reconstructing the field set.

Unlike core's comparators this applies no desired-state contract. An index that reached this comparison was declared on both sides, so an undef field on the target side is a real difference and not an "unprescribed" one.

diff

my @ops = DBIO::ClickHouse::Diff::Index->diff(
  $source_indexes, $target_indexes,
  $source_tables,  $target_tables,
  $storage,                                # optional, drives IF EXISTS
);

Compares the data_skipping_indexes sections of two models. The two tables sections scope the walk to tables present on both sides (scope => 'both'): indexes of a brand-new table are emitted inline by DBIO::ClickHouse::Diff::Table, and indexes of a table being dropped go away with the DROP TABLE, so emitting a standalone DROP INDEX for one of them would fail against a real server once the table is gone.

Index identity is the index name. Changed definitions become a drop-then-add pair. Every index missing from the target is dropped, see "Every skipping index the target lacks is dropped". $storage threads through to the constructed ops; see "as_sql".

as_sql

Renders ALTER TABLE ... ADD INDEX ... or ALTER TABLE ... DROP INDEX ... through DBIO::ClickHouse::DDL::Emit, so that an added index is spelled exactly as DBIO::ClickHouse::Diff::Table spells the same index inline in a CREATE TABLE.

When the op carries storage, the IF [NOT] EXISTS flag is taken from "should_emit_if_exists" in DBIO::Diff::Op; otherwise the legacy bare shape is preserved (ALTER TABLE ... ADD INDEX / ALTER TABLE ... DROP INDEX) so the existing t/41 cases stay green.

summary

One indented line naming the skipping index and its table. It says "skipping index" rather than "index" on purpose -- these are not B-tree indexes, and a summary that blurred the two would suggest a lookup guarantee ClickHouse never makes.

SEE ALSO

AUTHOR

DBIO Authors

COPYRIGHT AND LICENSE

Copyright (C) 2026 DBIO Authors

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.