From 584978488861e24d2e400235f0ad257edb9bbcbf Mon Sep 17 00:00:00 2001 From: jkumz Date: Thu, 9 Jul 2026 13:15:33 +0700 Subject: [PATCH 1/2] tests: generate dictionary-bug relationship projection fixtures Add the e2e and storage unit coverage for relationship string-property corruption, and reconstruct A/B/A_TO_B parquet at test time from compact zlib payloads colocated with the fixture directory so the regression signal is preserved without checking in parquet binaries. --- .github/workflows/ci-musl-test-workflow.yml | 8 +- .github/workflows/ci-workflow.yml | 5 +- .gitignore | 3 + scripts/generate-dictionary-bug-fixture.py | 245 ++++++++++++ test/storage/CMakeLists.txt | 1 + test/storage/string_chunk_scan_test.cpp | 378 ++++++++++++++++++ ...83_relationship_projection_obfuscated.test | 80 ++++ .../node_ids.u32le.zlib | Bin 0 -> 91563 bytes .../rels.u32u32u8.zlib | Bin 0 -> 3063 bytes 9 files changed, 716 insertions(+), 4 deletions(-) create mode 100644 scripts/generate-dictionary-bug-fixture.py create mode 100644 test/storage/string_chunk_scan_test.cpp create mode 100644 test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated.test create mode 100644 test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/node_ids.u32le.zlib create mode 100644 test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/rels.u32u32u8.zlib diff --git a/.github/workflows/ci-musl-test-workflow.yml b/.github/workflows/ci-musl-test-workflow.yml index fe30ec393b..d1ee0a6041 100644 --- a/.github/workflows/ci-musl-test-workflow.yml +++ b/.github/workflows/ci-musl-test-workflow.yml @@ -205,12 +205,14 @@ jobs: CMAKE_CXX_COMPILER_LAUNCHER: ccache run: su-exec "$RUN_AS_USER:$RUN_AS_GROUP" env HOME="$RUN_AS_HOME" sh -lc 'make relwithdebinfo' - - name: Generate datasets - run: su-exec "$RUN_AS_USER:$RUN_AS_GROUP" env HOME="$RUN_AS_HOME" sh -lc 'bash scripts/generate_binary_demo.sh --lbug-shell-mode relwithdebinfo' - - name: Install uv run: pip3 install uv + - name: Generate datasets + run: | + su-exec "$RUN_AS_USER:$RUN_AS_GROUP" env HOME="$RUN_AS_HOME" sh -lc 'bash scripts/generate_binary_demo.sh --lbug-shell-mode relwithdebinfo' + su-exec "$RUN_AS_USER:$RUN_AS_GROUP" env HOME="$RUN_AS_HOME" sh -lc 'uv run --with pyarrow python3 scripts/generate-dictionary-bug-fixture.py' + - name: Test env: CMAKE_C_COMPILER_LAUNCHER: ccache diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 58729192aa..94b0b78112 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -256,7 +256,9 @@ jobs: - name: Generate datasets if: matrix.platform != 'windows' - run: bash scripts/generate_binary_demo.sh --lbug-shell-mode relwithdebinfo + run: | + bash scripts/generate_binary_demo.sh --lbug-shell-mode relwithdebinfo + uv run --with pyarrow python3 scripts/generate-dictionary-bug-fixture.py - name: Generate datasets (Windows) if: matrix.platform == 'windows' @@ -265,6 +267,7 @@ jobs: LBUG_SHELL="$(find build -path '*/src/lbug_shell.exe' -print -quit)" test -n "$LBUG_SHELL" bash scripts/generate_binary_demo.sh --lbug-shell "$LBUG_SHELL" + uv run --with pyarrow python3 scripts/generate-dictionary-bug-fixture.py - name: Test shell: bash diff --git a/.gitignore b/.gitignore index 0181ba504d..cc2d4b906c 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ scripts/generate-cpp-docs/c/docs dataset/binary-demo dataset/databases/tinysnb dataset/ldbc-1/csv + +# Generated dictionary-bug e2e parquet fixtures +test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/*.parquet diff --git a/scripts/generate-dictionary-bug-fixture.py b/scripts/generate-dictionary-bug-fixture.py new file mode 100644 index 0000000000..33d696fcdb --- /dev/null +++ b/scripts/generate-dictionary-bug-fixture.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""Generate parquet fixtures for the relationship property corruption e2e. + +The e2e in +``test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated.test`` +needs a specific import layout. Sequential ``id = 1..N`` node tables do not +reproduce the pre-fix failure; the minimized node insertion order and the +exact 953-row relationship table do. + +This script reconstructs that layout from compact zlib payloads next to the +generated parquet files under +``test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/`` +and writes: + +- ``A.parquet`` / ``B.parquet``: 34,747 nodes with the original insertion order +- ``A_TO_B.parquet``: 953 relationships with the original endpoints and types + +Generated parquet files should not be committed (see ``.gitignore``). + +Usage: + uv run --with pyarrow python3 scripts/generate-dictionary-bug-fixture.py +""" + +from __future__ import annotations + +import argparse +import hashlib +import os +import struct +import sys +import zlib + +import pyarrow as pa +import pyarrow.parquet as pq + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +PROJECT_ROOT = os.path.dirname(SCRIPT_DIR) +DEFAULT_FIXTURE_DIR = os.path.join( + PROJECT_ROOT, + "test", + "test_files", + "dictionary_bug", + "orb383_relationship_projection_obfuscated_fixture", +) + +NODE_IDS_PAYLOAD = os.path.join(DEFAULT_FIXTURE_DIR, "node_ids.u32le.zlib") +RELS_PAYLOAD = os.path.join(DEFAULT_FIXTURE_DIR, "rels.u32u32u8.zlib") + +NUM_NODES = 34747 +NUM_RELS = 953 +TYPE_BY_CODE = {0: "REL_A", 1: "REL_B", 2: "REL_C"} +EXPECTED_TYPE_COUNTS = {"REL_A": 739, "REL_B": 202, "REL_C": 12} +EXPECTED_NODE_IDS_SHA256 = ( + "9563bacdbfe3afc53a7c9c0a377e8a9cdb617794171c56ffc1bcd205e65f9ed8" +) +EXPECTED_RELS_SHA256 = ( + "43f42222288bd8c1097396fb787855dba0aa6e75fa35e48b46a62c52ff191b2f" +) + + +def _sha256(data: bytes) -> str: + """Return the hex SHA-256 digest of ``data``.""" + return hashlib.sha256(data).hexdigest() + + +def _read_zlib(path: str) -> bytes: + """Read and decompress a zlib payload file. + + Args: + path: Absolute path to a zlib-compressed file. + + Returns: + The decompressed payload bytes. + """ + with open(path, "rb") as handle: + return zlib.decompress(handle.read()) + + +def load_node_ids(path: str = NODE_IDS_PAYLOAD) -> list[int]: + """Load the minimized node insertion-order IDs. + + Args: + path: Path to the little-endian uint32 zlib payload. + + Returns: + Exactly ``NUM_NODES`` unique IDs forming the set ``1..NUM_NODES``. + """ + raw = _read_zlib(path) + if _sha256(raw) != EXPECTED_NODE_IDS_SHA256: + raise ValueError(f"node id payload checksum mismatch for {path}") + if len(raw) != NUM_NODES * 4: + raise ValueError(f"unexpected node id payload size: {len(raw)}") + ids = list(struct.unpack(f"<{NUM_NODES}I", raw)) + if set(ids) != set(range(1, NUM_NODES + 1)): + raise ValueError("node id payload is not a permutation of 1..NUM_NODES") + return ids + + +def load_relationships( + path: str = RELS_PAYLOAD, +) -> tuple[list[int], list[int], list[str]]: + """Load the minimized relationship endpoints and type labels. + + Args: + path: Path to the packed ``(a_id, b_id, type_code)`` zlib payload. + + Returns: + Parallel lists of source IDs, destination IDs, and type strings. + """ + raw = _read_zlib(path) + if _sha256(raw) != EXPECTED_RELS_SHA256: + raise ValueError(f"relationship payload checksum mismatch for {path}") + record_size = 9 + if len(raw) != NUM_RELS * record_size: + raise ValueError(f"unexpected relationship payload size: {len(raw)}") + + src_ids: list[int] = [] + dst_ids: list[int] = [] + types: list[str] = [] + for offset in range(0, len(raw), record_size): + src, dst, type_code = struct.unpack_from(" None: + """Write a node parquet table with ``id`` / ``key`` columns. + + Args: + output_path: Destination parquet path. + column_prefix: Column name prefix (``a`` or ``b``). + ids: Node IDs in insertion order. + """ + keys = [f"node_{node_id}" for node_id in ids] + table = pa.table( + { + f"{column_prefix}.id": pa.array(ids, type=pa.int64()), + f"{column_prefix}.key": pa.array(keys, type=pa.utf8()), + } + ) + pq.write_table(table, output_path, compression="snappy", use_dictionary=False) + size_kb = os.path.getsize(output_path) // 1024 + print(f"Generated {output_path} ({len(ids)} rows, {size_kb} KB)") + + +def write_relationship_table( + output_path: str, + src_ids: list[int], + dst_ids: list[int], + types: list[str], +) -> None: + """Write the relationship parquet table with dictionary-encoded types. + + Args: + output_path: Destination parquet path. + src_ids: Source node IDs. + dst_ids: Destination node IDs. + types: Relationship ``type`` property values. + """ + table = pa.table( + { + "a.id": pa.array(src_ids, type=pa.int64()), + "b.id": pa.array(dst_ids, type=pa.int64()), + "r.type": pa.array(types, type=pa.utf8()), + } + ) + pq.write_table( + table, + output_path, + compression="snappy", + use_dictionary=["r.type"], + ) + size_kb = os.path.getsize(output_path) // 1024 + print(f"Generated {output_path} ({len(src_ids)} rows, {size_kb} KB)") + + +def generate_fixtures(fixture_dir: str) -> None: + """Generate all three parquet fixtures into ``fixture_dir``. + + Args: + fixture_dir: Directory that receives ``A.parquet``, ``B.parquet``, and + ``A_TO_B.parquet``. + """ + os.makedirs(fixture_dir, exist_ok=True) + node_ids = load_node_ids() + src_ids, dst_ids, types = load_relationships() + + write_node_table(os.path.join(fixture_dir, "A.parquet"), "a", node_ids) + write_node_table(os.path.join(fixture_dir, "B.parquet"), "b", node_ids) + write_relationship_table( + os.path.join(fixture_dir, "A_TO_B.parquet"), + src_ids, + dst_ids, + types, + ) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + """Parse CLI arguments. + + Args: + argv: Argument vector excluding the program name. + + Returns: + Parsed argparse namespace. + """ + parser = argparse.ArgumentParser( + description="Generate dictionary-bug relationship projection parquet fixtures." + ) + parser.add_argument( + "--fixture-dir", + default=DEFAULT_FIXTURE_DIR, + help="Output directory for A.parquet, B.parquet, and A_TO_B.parquet.", + ) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + """Entry point for fixture generation. + + Args: + argv: Optional argument vector excluding the program name. + + Returns: + Process exit code (``0`` on success). + """ + args = parse_args(sys.argv[1:] if argv is None else argv) + generate_fixtures(args.fixture_dir) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test/storage/CMakeLists.txt b/test/storage/CMakeLists.txt index 83efdeabba..b1b9d9a676 100644 --- a/test/storage/CMakeLists.txt +++ b/test/storage/CMakeLists.txt @@ -7,6 +7,7 @@ add_lbug_test(rel_tests rel_scan_test.cpp rel_delete_test.cpp) add_lbug_test(node_update_test node_update_test.cpp) add_lbug_test(detach_delete_test detach_delete_test.cpp) add_lbug_test(storage_utils_test storage_utils_test.cpp) +add_lbug_test(string_chunk_scan_test string_chunk_scan_test.cpp) add_lbug_test(ice_disk_utils_test ice_disk_utils_test.cpp) target_include_directories(compression_test PRIVATE ${PROJECT_SOURCE_DIR}/third_party/alp/include) diff --git a/test/storage/string_chunk_scan_test.cpp b/test/storage/string_chunk_scan_test.cpp new file mode 100644 index 0000000000..285316e80a --- /dev/null +++ b/test/storage/string_chunk_scan_test.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include +#include + +#include "common/vector/value_vector.h" +#include "graph_test/private_graph_test.h" +#include "gtest/gtest.h" +#include "storage/page_manager.h" +#include "storage/storage_manager.h" +#include "storage/table/column_chunk.h" +#include "storage/table/column_chunk_data.h" +#include "storage/table/string_chunk_data.h" +#include "storage/table/string_column.h" +#include + +using namespace lbug::common; +using namespace lbug::storage; + +namespace lbug { +namespace testing { +namespace { + +using MaybeString = std::optional; +using string_index_t = DictionaryChunk::string_index_t; + +constexpr std::string_view NULL_SENTINEL = ""; + +class StringChunkScanTest : public DBTest { +public: + void SetUp() override { + BaseGraphTest::SetUp(); // NOLINT + createDBAndConn(); + } + + std::string getInputDir() override { return TestHelper::appendLbugRootPath("dataset/empty/"); } +}; + +MaybeString value(std::string_view str) { + return MaybeString{str}; +} + +MaybeString nullValue() { + return std::nullopt; +} + +std::string printableValue(const MaybeString& maybeValue) { + if (!maybeValue.has_value()) { + return std::string{NULL_SENTINEL}; + } + return std::string{*maybeValue}; +} + +std::vector printableValues(std::span values) { + std::vector result; + result.reserve(values.size()); + for (const auto& maybeValue : values) { + result.push_back(printableValue(maybeValue)); + } + return result; +} + +std::vector expectedRange(std::span values, offset_t startRow, + offset_t numRows) { + std::vector result; + result.reserve(numRows); + for (auto i = 0u; i < numRows; i++) { + result.push_back(printableValue(values[startRow + i])); + } + return result; +} + +std::vector expectedPrefixedRange(std::span prefix, + std::span values, offset_t startRow, offset_t numRows) { + auto result = printableValues(prefix); + auto range = expectedRange(values, startRow, numRows); + result.insert(result.end(), range.begin(), range.end()); + return result; +} + +LogicalType makeStringLikeType(const StringColumn& column) { + if (column.getDataType().getLogicalTypeID() == LogicalTypeID::JSON) { + return LogicalType::JSON(); + } + return LogicalType::STRING(); +} + +void writeMaybeStringValue(StringChunkData& chunk, ValueVector& vector, offset_t row, + const MaybeString& maybeValue) { + vector.setNull(0, !maybeValue.has_value()); + if (maybeValue.has_value()) { + StringVector::addString(&vector, 0, *maybeValue); + } + chunk.write(&vector, 0, row); +} + +struct PersistedStringChunk { + std::unique_ptr chunk; + ChunkState state; +}; + +PersistedStringChunk buildPersistedStringChunk(MemoryManager& memoryManager, + PageManager& pageManager, StringColumn& column, std::span values) { + auto chunk = ColumnChunkFactory::createColumnChunkData(memoryManager, + makeStringLikeType(column), true /*enableCompression*/, + std::max(values.size(), 1), ResidencyState::IN_MEMORY); + auto& stringChunk = chunk->cast(); + ValueVector valueVector{makeStringLikeType(column), &memoryManager}; + for (auto row = 0u; row < values.size(); row++) { + writeMaybeStringValue(stringChunk, valueVector, row, values[row]); + } + chunk->flush(pageManager); + + ChunkState state; + state.column = &column; + state.segmentStates.resize(1); + chunk->initializeScanState(state.segmentStates[0], &column); + return {std::move(chunk), std::move(state)}; +} + +struct MaterializedStringChunk { + std::vector values; + std::vector> indexes; + uint64_t dictionarySize; +}; + +MaterializedStringChunk materializeStringChunk(const StringChunkData& chunk, offset_t numRows) { + MaterializedStringChunk result; + result.values.reserve(numRows); + result.indexes.reserve(numRows); + result.dictionarySize = chunk.getDictionaryChunk().getOffsetChunk()->getNumValues(); + const auto* indexChunk = chunk.getIndexColumnChunk(); + for (auto row = 0u; row < numRows; row++) { + if (chunk.isNull(row)) { + result.values.push_back(std::string{NULL_SENTINEL}); + result.indexes.push_back(std::nullopt); + continue; + } + result.values.push_back(chunk.getValue(row)); + result.indexes.push_back(indexChunk->getValue(row)); + } + return result; +} + +void expectIndexesInBounds(const MaterializedStringChunk& chunk) { + for (const auto& maybeIndex : chunk.indexes) { + if (maybeIndex.has_value()) { + EXPECT_LT(*maybeIndex, chunk.dictionarySize); + } + } +} + +std::vector materializeValueVector(const ValueVector& vector, offset_t numRows) { + std::vector values; + values.reserve(numRows); + for (auto row = 0u; row < numRows; row++) { + if (vector.isNull(row)) { + values.push_back(std::string{NULL_SENTINEL}); + continue; + } + values.push_back(vector.getValue(row).getAsString()); + } + return values; +} + +MaterializedStringChunk scanToStringChunk(MemoryManager& memoryManager, StringColumn& column, + const ChunkState& state, offset_t startRow, offset_t numRows, + std::span prefix = {}) { + auto outputCapacity = std::max(prefix.size() + numRows, 1); + auto outputChunk = + ColumnChunkFactory::createColumnChunkData(memoryManager, makeStringLikeType(column), + true /*enableCompression*/, outputCapacity, ResidencyState::IN_MEMORY); + auto& outputStringChunk = outputChunk->cast(); + ValueVector valueVector{makeStringLikeType(column), &memoryManager}; + for (auto row = 0u; row < prefix.size(); row++) { + writeMaybeStringValue(outputStringChunk, valueVector, row, prefix[row]); + } + + if (prefix.empty()) { + column.scan(state, outputChunk.get(), startRow, numRows); + } else { + static_cast(column).scanSegment(state.segmentStates[0], outputChunk.get(), + startRow, numRows); + } + + return materializeStringChunk(outputStringChunk, prefix.size() + numRows); +} + +std::vector scanToValueVector(MemoryManager& memoryManager, StringColumn& column, + const ChunkState& state, offset_t startRow, offset_t numRows) { + ValueVector outputVector{makeStringLikeType(column), &memoryManager}; + column.scan(state, startRow, numRows, &outputVector, 0 /*offsetInVector*/); + return materializeValueVector(outputVector, numRows); +} + +struct ScanCase { + std::string name; + std::vector values; + offset_t startRow; + offset_t numRows; + std::vector prefix; +}; + +} // namespace + +TEST_F(StringChunkScanTest, PartialAndFullStringChunkScansMatchValueVectorScans) { + auto* memoryManager = getMemoryManager(*database); + auto* storageManager = getStorageManager(*database); + PageManager pageManager{storageManager->getDataFH()}; + StringColumn column{"scan_value", LogicalType::STRING(), storageManager->getDataFH(), + memoryManager, &storageManager->getShadowFile(), true /*enableCompression*/}; + + const std::vector cases = { + { + "duplicate first occurrence before partial range", + {value("REL_A"), value("REL_B"), value("REL_B"), value("REL_A")}, + 1, + 3, + {}, + }, + { + "dictionary order differs from row encounter order", + {value("ALPHA"), value("BETA"), value("ALPHA"), value("GAMMA"), value("BETA")}, + 1, + 4, + {}, + }, + { + "unique values partial range", + {value("ALPHA"), value("BETA"), value("GAMMA"), value("DELTA")}, + 1, + 3, + {}, + }, + { + "all values duplicate", + {value("ALPHA"), value("ALPHA"), value("ALPHA"), value("ALPHA")}, + 1, + 3, + {}, + }, + { + "duplicate values separated by null", + {value("ALPHA"), nullValue(), value("ALPHA"), value("BETA"), value("ALPHA")}, + 0, + 5, + {}, + }, + { + "all-null partial range", + {value("ROOT"), nullValue(), nullValue(), nullValue(), value("TAIL")}, + 1, + 3, + {}, + }, + { + "empty strings and duplicates", + {value(""), value("ALPHA"), value(""), nullValue(), value("BETA"), value("")}, + 0, + 6, + {}, + }, + { + "full segment uses entire dictionary fast path", + {value("REL_A"), value("REL_B"), value("REL_B"), value("REL_A")}, + 0, + 4, + {}, + }, + { + "partial scan appends after existing dictionary rows", + {value("REL_A"), value("REL_B"), value("REL_B"), value("REL_A")}, + 1, + 3, + {value("PREFIX_ONE"), value("PREFIX_TWO")}, + }, + { + "high duplicate pressure partial range", + {value("ROOT"), value("HOT"), value("HOT"), value("HOT"), value("COLD"), value("HOT"), + value("COLD"), value("HOT")}, + 1, + 7, + {}, + }, + { + "low duplicate pressure partial range", + {value("ROOT"), value("A"), value("B"), value("C"), value("D"), value("E"), value("F"), + value("A")}, + 1, + 7, + {}, + }, + }; + + for (const auto& scanCase : cases) { + SCOPED_TRACE(scanCase.name); + auto persisted = + buildPersistedStringChunk(*memoryManager, pageManager, column, scanCase.values); + + const auto valueVectorValues = scanToValueVector(*memoryManager, column, persisted.state, + scanCase.startRow, scanCase.numRows); + const auto stringChunkValues = scanToStringChunk(*memoryManager, column, persisted.state, + scanCase.startRow, scanCase.numRows, scanCase.prefix); + + EXPECT_EQ(valueVectorValues, + expectedRange(scanCase.values, scanCase.startRow, scanCase.numRows)); + EXPECT_EQ(stringChunkValues.values, expectedPrefixedRange(scanCase.prefix, scanCase.values, + scanCase.startRow, scanCase.numRows)); + expectIndexesInBounds(stringChunkValues); + } +} + +TEST_F(StringChunkScanTest, PartialStringChunkScansPreserveIndexInvariantsWhenAppending) { + auto* memoryManager = getMemoryManager(*database); + auto* storageManager = getStorageManager(*database); + PageManager pageManager{storageManager->getDataFH()}; + StringColumn column{"scan_value", LogicalType::STRING(), storageManager->getDataFH(), + memoryManager, &storageManager->getShadowFile(), true /*enableCompression*/}; + + const std::vector values = {value("REL_A"), value("REL_B"), value("REL_B"), + value("REL_A")}; + const std::vector prefix = {value("PREFIX_ONE"), value("PREFIX_TWO")}; + auto persisted = buildPersistedStringChunk(*memoryManager, pageManager, column, values); + + const auto scanned = scanToStringChunk(*memoryManager, column, persisted.state, 1, 3, prefix); + + EXPECT_EQ(scanned.values, expectedPrefixedRange(prefix, values, 1, 3)); + expectIndexesInBounds(scanned); + ASSERT_EQ(scanned.indexes.size(), 5u); + ASSERT_TRUE(scanned.indexes[2].has_value()); + ASSERT_TRUE(scanned.indexes[3].has_value()); + ASSERT_TRUE(scanned.indexes[4].has_value()); + EXPECT_EQ(scanned.indexes[2], scanned.indexes[3]); + EXPECT_NE(scanned.indexes[2], scanned.indexes[4]); + EXPECT_GE(*scanned.indexes[2], static_cast(prefix.size())); + EXPECT_GE(*scanned.indexes[4], static_cast(prefix.size())); +} + +TEST_F(StringChunkScanTest, JsonStringChunkScansMatchValueVectorScans) { + auto* memoryManager = getMemoryManager(*database); + auto* storageManager = getStorageManager(*database); + PageManager pageManager{storageManager->getDataFH()}; + StringColumn column{"scan_json", LogicalType::JSON(), storageManager->getDataFH(), + memoryManager, &storageManager->getShadowFile(), true /*enableCompression*/}; + + const std::vector values = {value("{\"kind\":\"root\"}"), + value("{\"kind\":\"edge\"}"), value("{\"kind\":\"edge\"}"), nullValue(), + value("{\"kind\":\"leaf\"}")}; + auto persisted = buildPersistedStringChunk(*memoryManager, pageManager, column, values); + + const auto valueVectorValues = scanToValueVector(*memoryManager, column, persisted.state, 1, 4); + const auto stringChunkValues = scanToStringChunk(*memoryManager, column, persisted.state, 1, 4); + + EXPECT_EQ(valueVectorValues, expectedRange(values, 1, 4)); + EXPECT_EQ(stringChunkValues.values, expectedRange(values, 1, 4)); + expectIndexesInBounds(stringChunkValues); +} + +TEST_F(StringChunkScanTest, ValueVectorPartialScansRemainCorrectWhenDictionaryOffsetsCanBeSorted) { + auto* memoryManager = getMemoryManager(*database); + auto* storageManager = getStorageManager(*database); + PageManager pageManager{storageManager->getDataFH()}; + StringColumn column{"scan_value", LogicalType::STRING(), storageManager->getDataFH(), + memoryManager, &storageManager->getShadowFile(), true /*enableCompression*/}; + + const std::vector values = {value("REL_A"), value("REL_B"), value("REL_B"), + value("REL_A")}; + auto persisted = buildPersistedStringChunk(*memoryManager, pageManager, column, values); + + const auto scanned = scanToValueVector(*memoryManager, column, persisted.state, 1, 3); + + EXPECT_EQ(scanned, expectedRange(values, 1, 3)); +} + +} // namespace testing +} // namespace lbug diff --git a/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated.test b/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated.test new file mode 100644 index 0000000000..749515a82e --- /dev/null +++ b/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated.test @@ -0,0 +1,80 @@ +-DATASET PARQUET empty +-SKIP_IN_MEM + +-- + +-CASE AnonymousParquetDeleteReload +-STATEMENT CREATE NODE TABLE `A` (`id` INT64, `key` STRING, PRIMARY KEY(`id`)); +---- ok + +-STATEMENT CREATE NODE TABLE `B` (`id` INT64, `key` STRING, PRIMARY KEY(`id`)); +---- ok + +-STATEMENT CREATE REL TABLE `A_TO_B` (FROM `A` TO `B`, `type` STRING, MANY_MANY); +---- ok + +-STATEMENT COPY `A` (`id`, `key`) FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/A.parquet"; +---- ok + +-STATEMENT COPY `B` (`id`, `key`) FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/B.parquet"; +---- ok + +-STATEMENT COPY `A_TO_B` (`type`) FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/A_TO_B.parquet"; +---- ok + +-STATEMENT MATCH ()-[r:A_TO_B]->() RETURN count(r); +---- 1 +953 + +-STATEMENT MATCH (a:A)-[r:A_TO_B]->(b:B) + WHERE offset(ID(r)) = 952 + RETURN a.key, r.type, b.key; +---- 1 +node_10049|REL_A|node_10050 + +-STATEMENT MATCH (a:A {key: 'node_3379'})-[r:A_TO_B]->(b:B {key: 'node_3382'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A + +-STATEMENT MATCH (b:B {key: 'node_3382'})<-[r:A_TO_B]-(a:A {key: 'node_3379'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A + +-STATEMENT MATCH ()-[r:A_TO_B]->() + WHERE offset(ID(r)) = 952 + DELETE r; +---- ok + +-STATEMENT MATCH ()-[r:A_TO_B]->() + WHERE offset(ID(r)) = 952 + RETURN count(r); +---- 1 +0 + +-STATEMENT MATCH (a:A {key: 'node_3379'})-[r:A_TO_B]->(b:B {key: 'node_3382'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A + +-STATEMENT MATCH (b:B {key: 'node_3382'})<-[r:A_TO_B]-(a:A {key: 'node_3379'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A + +-RELOADDB + +-STATEMENT MATCH ()-[r:A_TO_B]->() RETURN count(r); +---- 1 +952 + +-STATEMENT MATCH (a:A {key: 'node_3379'})-[r:A_TO_B]->(b:B {key: 'node_3382'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A + +-STATEMENT MATCH (b:B {key: 'node_3382'})<-[r:A_TO_B]-(a:A {key: 'node_3379'}) + RETURN offset(ID(r)), r.type; +---- 1 +0|REL_A diff --git a/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/node_ids.u32le.zlib b/test/test_files/dictionary_bug/orb383_relationship_projection_obfuscated_fixture/node_ids.u32le.zlib new file mode 100644 index 0000000000000000000000000000000000000000..f6ead4f2f61f01bfc35546ee712aad5ec4f56004 GIT binary patch literal 91563 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%0A5cuhKdwE`LT9SKw@6E}=8taKy4(`8wLNPx+tHht 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 z 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 FruX>uq=Wzf literal 0 HcmV?d00001 From e4514de54e42a0d3200a91ad4a50ea2098b9f888 Mon Sep 17 00:00:00 2001 From: jkumz Date: Thu, 9 Jul 2026 13:15:33 +0700 Subject: [PATCH 2/2] fix: StringChunkData relationship property corruption Separate dictionary materialization from row-index assignment in partial string chunk scans so checkpointed relationship properties stay correct after delete/reload. --- src/include/storage/table/dictionary_column.h | 35 ++++-- src/storage/table/dictionary_column.cpp | 105 ++++++++++-------- src/storage/table/string_column.cpp | 50 ++++++--- 3 files changed, 115 insertions(+), 75 deletions(-) diff --git a/src/include/storage/table/dictionary_column.h b/src/include/storage/table/dictionary_column.h index ad2f8110eb..06a29c8466 100644 --- a/src/include/storage/table/dictionary_column.h +++ b/src/include/storage/table/dictionary_column.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "dictionary_chunk.h" #include "storage/table/column.h" #include "storage/table/column_chunk_data.h" @@ -8,19 +11,14 @@ namespace lbug { namespace storage { +class StringColumn; + class DictionaryColumn { public: DictionaryColumn(const std::string& name, FileHandle* dataFH, MemoryManager* mm, ShadowFile* shadowFile, bool enableCompression); void scan(const SegmentState& state, DictionaryChunk& dictChunk) const; - // Offsets to scan should be a sorted list of pairs mapping the index of the entry in the string - // dictionary (as read from the index column) to the output index in the result vector to store - // the string. - template - void scan(const SegmentState& offsetState, const SegmentState& dataState, - std::vector>& offsetsToScan, - Result* result, const ColumnChunkMetadata& indexMeta) const; DictionaryChunk::string_index_t append(const DictionaryChunk& dictChunk, SegmentState& state, std::string_view val) const; @@ -32,12 +30,31 @@ class DictionaryColumn { Column* getOffsetColumn() const { return offsetColumn.get(); } private: + friend class StringColumn; + + // Offsets to scan should be a sorted list of pairs mapping the index of the entry in the string + // dictionary (as read from the index column) to the output index in the result vector to store + // the string. + void scan(const SegmentState& offsetState, const SegmentState& dataState, + std::vector>& offsetsToScan, + common::ValueVector* result, const ColumnChunkMetadata& indexMeta) const; + + // Materializes unique source dictionary indexes into a StringChunkData dictionary. + // The input indexes identify entries in the persistent source dictionary. The function may + // reorder them for locality, appends the corresponding string bytes and offsets to the result + // dictionary, and returns the actual old-index to new-index mapping. It does not write row + // indexes; callers that know result row positions must update the StringChunkData index chunk. + std::vector> + materializeToStringChunkDictionary(const SegmentState& offsetState, + const SegmentState& dataState, std::vector& indexesToScan, + StringChunkData& result, const ColumnChunkMetadata& indexMeta) const; + void scanOffsets(const SegmentState& state, DictionaryChunk::string_offset_t* offsets, uint64_t index, uint64_t numValues, uint64_t dataSize) const; - void scanValue(const SegmentState& dataState, uint64_t startOffset, uint64_t endOffset, - StringChunkData* result, uint64_t offsetInVector) const; void scanValue(const SegmentState& dataState, uint64_t startOffset, uint64_t endOffset, common::ValueVector* resultVector, uint64_t offsetInVector) const; + DictionaryChunk::string_index_t appendScannedValueToDictionary(const SegmentState& dataState, + uint64_t startOffset, uint64_t length, StringChunkData& result) const; static bool canDataCommitInPlace(const SegmentState& dataState, uint64_t totalStringLengthToAdd); diff --git a/src/storage/table/dictionary_column.cpp b/src/storage/table/dictionary_column.cpp index f1213e546c..98efd3b328 100644 --- a/src/storage/table/dictionary_column.cpp +++ b/src/storage/table/dictionary_column.cpp @@ -13,7 +13,6 @@ #include "storage/table/string_chunk_data.h" #include "storage/table/string_column.h" #include -#include using namespace lbug::common; using namespace lbug::transaction; @@ -68,16 +67,15 @@ void DictionaryColumn::scan(const SegmentState& state, DictionaryChunk& dictChun } } -template void DictionaryColumn::scan(const SegmentState& offsetState, const SegmentState& dataState, - std::vector>& offsetsToScan, Result* result, + std::vector>& offsetsToScan, ValueVector* result, const ColumnChunkMetadata& indexMeta) const { string_index_t firstOffsetToScan = 0, lastOffsetToScan = 0; auto comp = [](auto pair1, auto pair2) { return pair1.first < pair2.first; }; auto duplicationFactor = (double)offsetState.metadata.numValues / indexMeta.numValues; if (duplicationFactor <= 0.5) { // If at least 50% of strings are duplicated, sort the offsets so we can re-use scanned - // strings + // strings. std::sort(offsetsToScan.begin(), offsetsToScan.end(), comp); firstOffsetToScan = offsetsToScan.front().first; lastOffsetToScan = offsetsToScan.back().first; @@ -100,13 +98,6 @@ void DictionaryColumn::scan(const SegmentState& offsetState, const SegmentState& scanOffsets(offsetState, offsets.data(), firstOffsetToScan, numOffsetsToScan, dataState.metadata.numValues); - if constexpr (std::same_as) { - auto& offsetChunk = *result->getDictionaryChunk()->getOffsetChunk(); - if (offsetChunk.getNumValues() + offsetsToScan.size() > offsetChunk.getCapacity()) { - offsetChunk.resize(std::bit_ceil(offsetChunk.getNumValues() + offsetsToScan.size())); - } - } - for (auto pos = 0u; pos < offsetsToScan.size(); pos++) { auto startOffset = offsets[offsetsToScan[pos].first - firstOffsetToScan]; auto endOffset = offsets[offsetsToScan[pos].first - firstOffsetToScan + 1]; @@ -114,36 +105,56 @@ void DictionaryColumn::scan(const SegmentState& offsetState, const SegmentState& DASSERT(endOffset >= startOffset); scanValue(dataState, startOffset, lengthToScan, result, offsetsToScan[pos].second); // For each string which has the same index in the dictionary as the one we scanned, - // copy the scanned string to its position in the result vector - if constexpr (std::same_as) { - auto& scannedString = result->template getValue(offsetsToScan[pos].second); - while (pos + 1 < offsetsToScan.size() && - offsetsToScan[pos + 1].first == offsetsToScan[pos].first) { - pos++; - result->template setValue(offsetsToScan[pos].second, scannedString); - } - } else { - // When scanning to chunks de-duplication should be done prior to this function such - // that you can have multiple positions in the string index chunk pointing to one string - // in this dictionary chunk. - // The offset chunk cannot have multiple offsets pointing to the same data, even if - // consecutive, since that would break the mechanism for calculating the size of a - // string. - DASSERT(pos == offsetsToScan.size() - 1 || - offsetsToScan[pos].first != offsetsToScan[pos + 1].first); + // copy the scanned string to its position in the result vector. + auto& scannedString = result->getValue(offsetsToScan[pos].second); + while (pos + 1 < offsetsToScan.size() && + offsetsToScan[pos + 1].first == offsetsToScan[pos].first) { + pos++; + result->setValue(offsetsToScan[pos].second, scannedString); } } } -template void DictionaryColumn::scan(const SegmentState& offsetState, - const SegmentState& dataState, - std::vector>& offsetsToScan, - common::ValueVector* result, const ColumnChunkMetadata& indexMeta) const; +std::vector> +DictionaryColumn::materializeToStringChunkDictionary(const SegmentState& offsetState, + const SegmentState& dataState, std::vector& indexesToScan, + StringChunkData& result, const ColumnChunkMetadata& indexMeta) const { + if (indexesToScan.empty()) { + return {}; + } -template void DictionaryColumn::scan(const SegmentState& offsetState, - const SegmentState& dataState, - std::vector>& offsetsToScan, - StringChunkData* result, const ColumnChunkMetadata& indexMeta) const; + string_index_t firstOffsetToScan = 0, lastOffsetToScan = 0; + auto comp = [](auto index1, auto index2) { return index1 < index2; }; + auto duplicationFactor = (double)offsetState.metadata.numValues / indexMeta.numValues; + if (duplicationFactor <= 0.5) { + std::sort(indexesToScan.begin(), indexesToScan.end(), comp); + firstOffsetToScan = indexesToScan.front(); + lastOffsetToScan = indexesToScan.back(); + } else { + const auto& [min, max] = + std::minmax_element(indexesToScan.begin(), indexesToScan.end(), comp); + firstOffsetToScan = *min; + lastOffsetToScan = *max; + } + + auto numOffsetsToScan = lastOffsetToScan - firstOffsetToScan + 1; + std::vector offsets(numOffsetsToScan + 1); + scanOffsets(offsetState, offsets.data(), firstOffsetToScan, numOffsetsToScan, + dataState.metadata.numValues); + + std::vector> mapping; + mapping.reserve(indexesToScan.size()); + for (const auto indexToScan : indexesToScan) { + auto startOffset = offsets[indexToScan - firstOffsetToScan]; + auto endOffset = offsets[indexToScan - firstOffsetToScan + 1]; + auto lengthToScan = endOffset - startOffset; + DASSERT(endOffset >= startOffset); + auto newIndex = + appendScannedValueToDictionary(dataState, startOffset, lengthToScan, result); + mapping.emplace_back(indexToScan, newIndex); + } + return mapping; +} string_index_t DictionaryColumn::append(const DictionaryChunk& dictChunk, SegmentState& state, std::string_view val) const { @@ -179,26 +190,24 @@ void DictionaryColumn::scanValue(const SegmentState& dataState, uint64_t startOf } } -void DictionaryColumn::scanValue(const SegmentState& dataState, uint64_t startOffset, - uint64_t length, StringChunkData* result, uint64_t offsetInResult) const { - auto& stringDataChunk = *result->getDictionaryChunk().getStringDataChunk(); - auto& offsetChunk = *result->getDictionaryChunk().getOffsetChunk(); - auto& indexChunk = *result->getIndexColumnChunk(); +string_index_t DictionaryColumn::appendScannedValueToDictionary(const SegmentState& dataState, + uint64_t startOffset, uint64_t length, StringChunkData& result) const { + auto& stringDataChunk = *result.getDictionaryChunk().getStringDataChunk(); + auto& offsetChunk = *result.getDictionaryChunk().getOffsetChunk(); if (stringDataChunk.getCapacity() < stringDataChunk.getNumValues() + length) { stringDataChunk.resize(std::bit_ceil(stringDataChunk.getNumValues() + length)); } if (offsetChunk.getNumValues() == offsetChunk.getCapacity()) { offsetChunk.resize(std::bit_ceil(offsetChunk.getNumValues() + 1)); } - if (offsetInResult >= indexChunk.getCapacity()) { - indexChunk.resize(std::bit_ceil(offsetInResult + 1)); + const auto newIndex = static_cast(offsetChunk.getNumValues()); + if (length > 0) { + dataColumn->scanSegment(dataState, startOffset, length, + stringDataChunk.getData() + stringDataChunk.getNumValues()); } - dataColumn->scanSegment(dataState, startOffset, length, - stringDataChunk.getData() + stringDataChunk.getNumValues()); - indexChunk.setValue(offsetChunk.getNumValues(), offsetInResult); - offsetChunk.setValue(stringDataChunk.getNumValues(), - offsetChunk.getNumValues()); + offsetChunk.setValue(stringDataChunk.getNumValues(), newIndex); stringDataChunk.setNumValues(stringDataChunk.getNumValues() + length); + return newIndex; } bool DictionaryColumn::canCommitInPlace(const SegmentState& state, uint64_t numNewStrings, diff --git a/src/storage/table/string_column.cpp b/src/storage/table/string_column.cpp index d561b86284..43b5a97d90 100644 --- a/src/storage/table/string_column.cpp +++ b/src/storage/table/string_column.cpp @@ -166,32 +166,46 @@ void StringColumn::scanSegment(const SegmentState& state, ColumnChunkData* resul } dictionary.scan(state, stringResultChunk->getDictionaryChunk()); } else { - // Any strings which are duplicated only need to be scanned once, so we track duplicate - // indices - std::unordered_map indexMap; - std::vector> offsetsToScan; + // Partial chunk scans first collect old dictionary indexes per result row. The dictionary + // materialization step may scan those old indexes in a different order for locality, so row + // index values are written only after the actual old-to-new dictionary mapping is known. + std::unordered_map oldToNewIndex; + std::vector oldIndexesByRow; + std::vector indexesToScan; + oldIndexesByRow.resize(numValuesToScan); for (auto i = 0u; i < numValuesToScan; i++) { - if (!resultChunk->isNull(startOffsetInResult + i)) { - auto index = indexChunk->getValue(startOffsetInResult + i); - auto element = indexMap.find(index); - if (element == indexMap.end()) { - indexMap.insert(std::make_pair(index, initialDictSize + offsetsToScan.size())); - indexChunk->setValue(initialDictSize + offsetsToScan.size(), - startOffsetInResult + i); - offsetsToScan.emplace_back(index, initialDictSize + offsetsToScan.size()); - } else { - indexChunk->setValue(element->second, startOffsetInResult + i); - } + auto resultPos = startOffsetInResult + i; + if (resultChunk->isNull(resultPos)) { + continue; + } + auto index = indexChunk->getValue(resultPos); + oldIndexesByRow[i] = index; + if (oldToNewIndex.find(index) == oldToNewIndex.end()) { + oldToNewIndex.insert(std::make_pair(index, 0)); + indexesToScan.push_back(index); } } - if (offsetsToScan.size() == 0) { + if (indexesToScan.empty()) { // All scanned values are null return; } - dictionary.scan(getChildState(state, ChildStateIndex::OFFSET), - getChildState(state, ChildStateIndex::DATA), offsetsToScan, stringResultChunk, + auto materializedIndexes = dictionary.materializeToStringChunkDictionary( + getChildState(state, ChildStateIndex::OFFSET), + getChildState(state, ChildStateIndex::DATA), indexesToScan, *stringResultChunk, getChildState(state, ChildStateIndex::INDEX).metadata); + for (const auto& [oldIndex, newIndex] : materializedIndexes) { + oldToNewIndex[oldIndex] = newIndex; + } + for (auto i = 0u; i < numValuesToScan; i++) { + auto resultPos = startOffsetInResult + i; + if (resultChunk->isNull(resultPos)) { + continue; + } + auto element = oldToNewIndex.find(oldIndexesByRow[i]); + DASSERT(element != oldToNewIndex.end()); + indexChunk->setValue(element->second, resultPos); + } } DASSERT(resultChunk->getNumValues() == startOffsetInResult + numValuesToScan && stringResultChunk->getIndexColumnChunk()->getNumValues() ==