diff --git a/tests/sstable_assertions.hh b/tests/sstable_assertions.hh
new file mode 100644
index 0000000000..4379f2cad1
--- /dev/null
+++ b/tests/sstable_assertions.hh
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 ScyllaDB
+ */
+
+/*
+ * This file is part of Scylla.
+ *
+ * Scylla is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Scylla is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Scylla. If not, see .
+ */
+
+#pragma once
+
+#include "dht/i_partitioner.hh"
+#include "schema.hh"
+#include "sstables/index_reader.hh"
+
+class index_reader_assertions {
+ std::unique_ptr _r;
+public:
+ index_reader_assertions(std::unique_ptr r)
+ : _r(std::move(r))
+ { }
+
+ index_reader_assertions& has_monotonic_positions(const schema& s) {
+ auto pos_cmp = position_in_partition::composite_less_compare(s);
+ auto rp_cmp = dht::ring_position_comparator(s);
+ auto prev = dht::ring_position::min();
+ _r->read_partition_data().get();
+ while (!_r->eof()) {
+ auto k = _r->current_partition_entry().get_decorated_key();
+ auto rp = dht::ring_position(k.token(), k.key().to_partition_key(s));
+
+ if (!rp_cmp(prev, rp)) {
+ BOOST_FAIL(sprint("Partitions have invalid order: %s >= %s", prev, rp));
+ }
+
+ prev = rp;
+
+ auto* pi = _r->current_partition_entry().get_promoted_index(s);
+ if (!pi->entries.empty()) {
+ auto& prev = pi->entries[0];
+ for (size_t i = 1; i < pi->entries.size(); ++i) {
+ auto& cur = pi->entries[i];
+ if (!pos_cmp(prev.end, cur.start)) {
+ std::cout << "promoted index:\n";
+ for (auto& e : pi->entries) {
+ std::cout << " " << e.start << "-" << e.end << ": +" << e.offset << " len=" << e.width << std::endl;
+ }
+ BOOST_FAIL(sprint("Index blocks are not monotonic: %s >= %s", prev.end, cur.start));
+ }
+ cur = prev;
+ }
+ }
+ _r->advance_to_next_partition().get();
+ }
+ return *this;
+ }
+};
+
+inline
+index_reader_assertions assert_that(std::unique_ptr r) {
+ return { std::move(r) };
+}
diff --git a/tests/sstable_mutation_test.cc b/tests/sstable_mutation_test.cc
index 6bdcddd240..c33cd210e3 100644
--- a/tests/sstable_mutation_test.cc
+++ b/tests/sstable_mutation_test.cc
@@ -35,6 +35,7 @@
#include "tmpdir.hh"
#include "memtable-sstable.hh"
#include "disk-error-handler.hh"
+#include "tests/sstable_assertions.hh"
thread_local disk_error_signal_type commit_error;
thread_local disk_error_signal_type general_disk_error;
@@ -801,3 +802,52 @@ SEASTAR_TEST_CASE(test_non_compound_table_row_is_not_marked_as_static) {
BOOST_REQUIRE(bool(mut));
});
}
+
+SEASTAR_TEST_CASE(test_promoted_index_blocks_are_monotonic) {
+ return seastar::async([] {
+ auto dir = make_lw_shared();
+ schema_builder builder("ks", "cf");
+ builder.with_column("p", utf8_type, column_kind::partition_key);
+ builder.with_column("c1", int32_type, column_kind::clustering_key);
+ builder.with_column("c2", int32_type, column_kind::clustering_key);
+ builder.with_column("v", int32_type);
+ auto s = builder.build();
+
+ auto k = partition_key::from_exploded(*s, {to_bytes("key1")});
+ auto cell = atomic_cell::make_live(1, int32_type->decompose(88), { });
+ mutation m(k, s);
+
+ auto ck = clustering_key::from_exploded(*s, {int32_type->decompose(1), int32_type->decompose(2)});
+ m.set_clustered_cell(ck, *s->get_column_definition("v"), cell);
+
+ ck = clustering_key::from_exploded(*s, {int32_type->decompose(1), int32_type->decompose(4)});
+ m.set_clustered_cell(ck, *s->get_column_definition("v"), std::move(cell));
+
+ ck = clustering_key::from_exploded(*s, {int32_type->decompose(1), int32_type->decompose(6)});
+ m.set_clustered_cell(ck, *s->get_column_definition("v"), std::move(cell));
+
+ ck = clustering_key::from_exploded(*s, {int32_type->decompose(3), int32_type->decompose(9)});
+ m.set_clustered_cell(ck, *s->get_column_definition("v"), std::move(cell));
+
+ m.partition().apply_row_tombstone(*s, range_tombstone(
+ clustering_key_prefix::from_exploded(*s, {int32_type->decompose(1)}),
+ bound_kind::excl_start,
+ clustering_key_prefix::from_exploded(*s, {int32_type->decompose(2)}),
+ bound_kind::incl_end,
+ {1, gc_clock::now()}));
+
+ auto mt = make_lw_shared(s);
+ mt->apply(std::move(m));
+
+ auto sst = make_lw_shared(s,
+ dir->path,
+ 1 /* generation */,
+ sstables::sstable::version_types::ka,
+ sstables::sstable::format_types::big);
+ sstable_writer_config cfg;
+ cfg.promoted_index_block_size = 1;
+ sst->write_components(mt->make_reader(s), 1, s, cfg).get();
+ sst->load().get();
+ assert_that(sst->get_index_reader(default_priority_class())).has_monotonic_positions(*s);
+ });
+}