From 753c13683ddb4e9f8f7d4fbf3417d824c7bfab6c Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Wed, 11 Mar 2026 15:23:46 +0800 Subject: [PATCH 1/6] stash changes to fix edge table transform between bundle and unbundled fix test fix test fix test fix revert changes fix fix fix fix test name address greptile issues --- include/neug/storages/graph/edge_table.h | 2 +- src/storages/graph/edge_table.cc | 125 ++++++-- tests/storage/test_edge_table.cc | 383 ++++++++++++++++++++++- tests/storage/test_vertex_table.cc | 2 +- tools/python_bind/tests/test_ddl.py | 68 ++++ 5 files changed, 555 insertions(+), 25 deletions(-) diff --git a/include/neug/storages/graph/edge_table.h b/include/neug/storages/graph/edge_table.h index 1884eefd..3495461c 100644 --- a/include/neug/storages/graph/edge_table.h +++ b/include/neug/storages/graph/edge_table.h @@ -134,7 +134,7 @@ class EdgeTable { size_t Capacity() const; private: - void dropAndCreateNewBundledCSR(); + void dropAndCreateNewBundledCSR(std::shared_ptr prev_data_col); void dropAndCreateNewUnbundledCSR(bool delete_property); std::string get_next_csr_path_suffix(); diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index b68acd4c..beca9411 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -152,12 +152,57 @@ void batch_put_edges_with_default_edata(const std::vector& src_lid, case DataTypeId::kEmpty: batch_put_edges_with_default_edata_impl(src_lid, dst_lid, EmptyType(), out_csr); + break; default: THROW_NOT_SUPPORTED_EXCEPTION("not support edge data type: " + std::to_string(property_type)); } } +void batch_put_edges_to_bundled_csr(const std::vector& src_lid, + const std::vector& dst_lid, + DataTypeId property_type, + const std::vector& edge_data, + CsrBase* out_csr) { + switch (property_type) { +#define TYPE_DISPATCHER(enum_val, type) \ + case DataTypeId::enum_val: { \ + std::vector typed_data; \ + typed_data.reserve(edge_data.size()); \ + for (const auto& prop : edge_data) { \ + typed_data.emplace_back(PropUtils::to_typed(prop)); \ + } \ + dynamic_cast*>(out_csr)->batch_put_edges( \ + src_lid, dst_lid, typed_data); \ + break; \ + } + TYPE_DISPATCHER(kBoolean, bool); + TYPE_DISPATCHER(kInt32, int32_t); + TYPE_DISPATCHER(kUInt32, uint32_t); + TYPE_DISPATCHER(kInt64, int64_t); + TYPE_DISPATCHER(kUInt64, uint64_t); + TYPE_DISPATCHER(kFloat, float); + TYPE_DISPATCHER(kDouble, double); + TYPE_DISPATCHER(kDate, Date); + TYPE_DISPATCHER(kTimestampMs, DateTime); + TYPE_DISPATCHER(kInterval, Interval); +#undef TYPE_DISPATCHER + case DataTypeId::kEmpty: { + dynamic_cast*>(out_csr)->batch_put_edges( + src_lid, dst_lid, {}); + break; + } + case DataTypeId::kVarchar: { + THROW_NOT_SUPPORTED_EXCEPTION("not support edge data type: " + + std::to_string(property_type)); + break; + } + default: + LOG(FATAL) << "Unsupported edge property type " + << static_cast(property_type); + } +} + template std::unique_ptr create_csr_impl(bool is_mutable, EdgeStrategy strategy) { @@ -191,7 +236,8 @@ static std::unique_ptr create_csr(bool is_mutable, return create_csr_impl(is_mutable, strategy); } default: { - LOG(FATAL) << "not support edge data type"; + LOG(FATAL) << "not support edge data type: " + << std::to_string(property_type); return nullptr; } } @@ -780,8 +826,9 @@ void EdgeTable::AddProperties(const std::vector& prop_names, if (table_->col_num() == 0) { // NOTE: Rather than check meta_->is_bundled(),we check whether the table // is empty. - if (meta_->properties.size() == 1) { - dropAndCreateNewBundledCSR(); + if (meta_->properties.size() == 1 && + meta_->properties[0].id() != DataTypeId::kVarchar) { + dropAndCreateNewBundledCSR(nullptr); } else { dropAndCreateNewUnbundledCSR(false); } @@ -822,6 +869,14 @@ void EdgeTable::DeleteProperties(const std::vector& col_names) { table_->delete_column(col); VLOG(1) << "delete column " << col; } + if (table_->col_num() == 0) { + dropAndCreateNewUnbundledCSR(true); + } else if (table_->col_num() == 1) { + auto remaining_col = table_->get_column_by_id(0); + if (remaining_col->type() != DataTypeId::kVarchar) { + dropAndCreateNewBundledCSR(remaining_col); + } + } } } @@ -834,9 +889,10 @@ int32_t EdgeTable::AddEdge(vid_t src_lid, vid_t dst_lid, (edge_data.size() == 0 && (meta_->properties.empty() || meta_->properties[0] == DataTypeId::kEmpty))); - in_csr_->put_generic_edge(dst_lid, src_lid, edge_data[0], ts, alloc); + Property bundled_data = edge_data.empty() ? Property() : edge_data[0]; + in_csr_->put_generic_edge(dst_lid, src_lid, bundled_data, ts, alloc); oe_offset = - out_csr_->put_generic_edge(src_lid, dst_lid, edge_data[0], ts, alloc); + out_csr_->put_generic_edge(src_lid, dst_lid, bundled_data, ts, alloc); } else { if (meta_->properties.size() != edge_data.size()) { THROW_INVALID_ARGUMENT_EXCEPTION( @@ -980,7 +1036,11 @@ size_t EdgeTable::Capacity() const { return capacity_.load(); } -void EdgeTable::dropAndCreateNewBundledCSR() { +void EdgeTable::dropAndCreateNewBundledCSR( + std::shared_ptr remaining_col) { + DataTypeId property_type = (remaining_col == nullptr) + ? meta_->properties[0].id() + : remaining_col->type(); auto suffix = get_next_csr_path_suffix(); std::string next_oe_csr_path = tmp_dir(work_dir_) + "/" + @@ -993,26 +1053,46 @@ void EdgeTable::dropAndCreateNewBundledCSR() { meta_->edge_label_name) + suffix; - auto edges = out_csr_->batch_export(nullptr); std::unique_ptr new_out_csr, new_in_csr; - assert(meta_->properties.size() == 1); - new_out_csr = create_csr(meta_->oe_mutable, meta_->oe_strategy, - meta_->properties[0].id()); - new_in_csr = create_csr(meta_->ie_mutable, meta_->ie_strategy, - meta_->properties[0].id()); + new_out_csr = + create_csr(meta_->oe_mutable, meta_->oe_strategy, property_type); + new_in_csr = create_csr(meta_->ie_mutable, meta_->ie_strategy, property_type); new_out_csr->open_in_memory(next_oe_csr_path); new_in_csr->open_in_memory(next_ie_csr_path); new_out_csr->resize(out_csr_->size()); new_in_csr->resize(in_csr_->size()); - batch_put_edges_with_default_edata( - std::get<0>(edges), std::get<1>(edges), meta_->properties[0].id(), - meta_->default_property_values[0], new_out_csr.get()); - batch_put_edges_with_default_edata( - std::get<1>(edges), std::get<0>(edges), meta_->properties[0].id(), - meta_->default_property_values[0], new_in_csr.get()); + if (remaining_col == nullptr) { + auto edges = out_csr_->batch_export(nullptr); + batch_put_edges_with_default_edata( + std::get<0>(edges), std::get<1>(edges), property_type, + meta_->default_property_values[0], new_out_csr.get()); + batch_put_edges_with_default_edata( + std::get<1>(edges), std::get<0>(edges), property_type, + meta_->default_property_values[0], new_in_csr.get()); + } else { + auto row_id_col = std::make_shared(0, StorageStrategy::kMem); + auto edges = out_csr_->batch_export(row_id_col); + std::vector remaining_data; + remaining_data.reserve(row_id_col->size()); + for (size_t i = 0; i < row_id_col->size(); ++i) { + auto row_id = row_id_col->get_view(i); + CHECK_LT(row_id, remaining_col->size()); + remaining_data.emplace_back(remaining_col->get_prop(row_id)); + } + batch_put_edges_to_bundled_csr(std::get<0>(edges), std::get<1>(edges), + property_type, remaining_data, + new_out_csr.get()); + batch_put_edges_to_bundled_csr(std::get<1>(edges), std::get<0>(edges), + property_type, remaining_data, + new_in_csr.get()); + } + table_->drop(); + table_ = std::make_unique(); + table_idx_.store(0); + capacity_.store(0); out_csr_->close(); in_csr_->close(); out_csr_ = std::move(new_out_csr); @@ -1046,7 +1126,9 @@ void EdgeTable::dropAndCreateNewUnbundledCSR(bool delete_property) { std::shared_ptr prev_data_col = nullptr; if (!delete_property) { - if (table_->col_num() >= 1) { + if (table_->col_num() >= 1 && + table_->get_column_by_id(0)->type() != DataTypeId::kVarchar && + table_->get_column_by_id(0)->type() != DataTypeId::kEmpty) { prev_data_col = table_->get_column_by_id(0); } } else { @@ -1061,12 +1143,15 @@ void EdgeTable::dropAndCreateNewUnbundledCSR(bool delete_property) { table_->resize(prev_data_col->size(), meta_->default_property_values); table_idx_.store(prev_data_col->size()); EnsureCapacity(prev_data_col->size()); + } else if (!delete_property) { + table_->resize(std::get<0>(edges).size()); + table_idx_.store(std::get<0>(edges).size()); + EnsureCapacity(std::get<0>(edges).size()); } std::vector row_ids; for (size_t i = 0; i < std::get<0>(edges).size(); ++i) { row_ids.push_back(i); } - std::unique_ptr new_out_csr, new_in_csr; if (delete_property) { new_out_csr = diff --git a/tests/storage/test_edge_table.cc b/tests/storage/test_edge_table.cc index 9b61c38a..f2326e80 100644 --- a/tests/storage/test_edge_table.cc +++ b/tests/storage/test_edge_table.cc @@ -26,6 +26,26 @@ namespace neug { namespace test { +class LocalGeneratedRecordBatchSupplier : public neug::IRecordBatchSupplier { + public: + explicit LocalGeneratedRecordBatchSupplier( + std::vector>&& batches) + : batch_index_(0), batches_(std::move(batches)) {} + + std::shared_ptr GetNextBatch() override { + if (batch_index_ >= batches_.size()) { + return nullptr; + } + auto batch = batches_[batch_index_]; + batch_index_++; + return batch; + } + + private: + size_t batch_index_ = 0; + std::vector> batches_; +}; + class EdgeTableTest : public ::testing::Test { protected: void SetUp() override { @@ -51,6 +71,10 @@ class EdgeTableTest : public ::testing::Test { "comment", {}, {}, {std::make_tuple(neug::DataTypeId::kInt64, "id", 0)}, {neug::StorageStrategy::kMem}, static_cast(1) << 32, "comment vertex label"); + schema_.AddEdgeLabel("person", "comment", "create0", {}, {}, {}, + neug::EdgeStrategy::kMultiple, + neug::EdgeStrategy::kMultiple, true, true, false, + "person creates comment edge without properties"); schema_.AddEdgeLabel( "person", "comment", "create1", {neug::DataTypeId::kInt32}, {"data"}, {neug::StorageStrategy::kMem}, neug::EdgeStrategy::kMultiple, @@ -70,6 +94,7 @@ class EdgeTableTest : public ::testing::Test { true, false, "person creates comment edge with two properties"); src_label_ = schema_.get_vertex_label_id("person"); dst_label_ = schema_.get_vertex_label_id("comment"); + edge_label_empty_ = schema_.get_edge_label_id("create0"); edge_label_int_ = schema_.get_edge_label_id("create1"); edge_label_str_ = schema_.get_edge_label_id("create2"); edge_label_str_int_ = schema_.get_edge_label_id("create3"); @@ -123,10 +148,34 @@ class EdgeTableTest : public ::testing::Test { void BatchInsert(std::vector>&& batches) { auto supplier = - std::make_shared(std::move(batches)); + std::make_shared(std::move(batches)); edge_table->BatchAddEdges(src_indexer, dst_indexer, supplier); } + size_t ExpectedBatchInsertCapacity(size_t inserted_edge_num) const { + if (inserted_edge_num == 0) { + return 0; + } + size_t new_cap = inserted_edge_num; + while (inserted_edge_num >= new_cap) { + new_cap = new_cap < 4096 ? 4096 : new_cap + (new_cap + 4) / 5; + } + return new_cap; + } + + void ExpectBundledStats(size_t expected_size) const { + ASSERT_NE(edge_table, nullptr); + EXPECT_EQ(edge_table->Size(), expected_size); + EXPECT_EQ(edge_table->Capacity(), neug::CsrBase::INFINITE_CAPACITY); + } + + void ExpectUnbundledStats(size_t expected_size, + size_t expected_capacity) const { + ASSERT_NE(edge_table, nullptr); + EXPECT_EQ(edge_table->Size(), expected_size); + EXPECT_EQ(edge_table->Capacity(), expected_capacity); + } + void OutputOutgoingEndpoints(std::vector& srcs, std::vector& dsts, neug::timestamp_t ts) { @@ -207,8 +256,8 @@ class EdgeTableTest : public ::testing::Test { neug::LFIndexer src_indexer; neug::LFIndexer dst_indexer; neug::Schema schema_; - neug::label_t src_label_, dst_label_, edge_label_int_, edge_label_str_, - edge_label_str_int_; + neug::label_t src_label_, dst_label_, edge_label_empty_, edge_label_int_, + edge_label_str_, edge_label_str_int_; std::string allocator_dir_; private: @@ -243,7 +292,9 @@ TEST_F(EdgeTableTest, TestBundledInt32) { this->InitIndexers(src_num, dst_num); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_int_); this->OpenEdgeTable(); + this->ExpectBundledStats(0); this->BatchInsert(std::move(batches)); + this->ExpectBundledStats(edge_num); std::vector> input; for (size_t i = 0; i < edge_num; ++i) { @@ -313,7 +364,9 @@ TEST_F(EdgeTableTest, TestSeperatedString) { this->InitIndexers(src_num, dst_num); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_); this->OpenEdgeTable(); + this->ExpectUnbundledStats(0, 0); this->BatchInsert(std::move(batches)); + this->ExpectUnbundledStats(edge_num, ExpectedBatchInsertCapacity(edge_num)); std::vector> input; for (size_t i = 0; i < edge_num; ++i) { @@ -386,7 +439,9 @@ TEST_F(EdgeTableTest, TestSeperatedIntString) { this->InitIndexers(src_num, dst_num); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); this->OpenEdgeTable(); + this->ExpectUnbundledStats(0, 0); this->BatchInsert(std::move(batches)); + this->ExpectUnbundledStats(edge_num, ExpectedBatchInsertCapacity(edge_num)); std::vector> input; for (size_t i = 0; i < edge_num; ++i) { @@ -445,6 +500,7 @@ TEST_F(EdgeTableTest, TestSeperatedIntString) { this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); this->OpenEdgeTable(); + this->ExpectUnbundledStats(edge_num, ExpectedBatchInsertCapacity(edge_num)); { std::vector srcs, dsts; this->OutputOutgoingEndpoints(srcs, dsts, 0); @@ -519,6 +575,7 @@ TEST_F(EdgeTableTest, TestCountEdgeNum) { this->BatchInsert(std::move(batches)); EXPECT_EQ(this->edge_table->EdgeNum(), edge_num); + this->ExpectBundledStats(edge_num); } TEST_F(EdgeTableTest, TestDeleteEdge) { @@ -550,6 +607,7 @@ TEST_F(EdgeTableTest, TestDeleteEdge) { this->ConstructEdgeTable(src_label_, dst_label_, edge_label_int_); this->OpenEdgeTable(); this->BatchInsert(std::move(batches)); + this->ExpectBundledStats(edge_num); auto oe_view = this->edge_table->get_outgoing_view(neug::MAX_TIMESTAMP); auto ie_view = this->edge_table->get_incoming_view(neug::MAX_TIMESTAMP); @@ -579,6 +637,7 @@ TEST_F(EdgeTableTest, TestDeleteEdge) { this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num - delete_count); ASSERT_EQ(dsts.size(), edge_num - delete_count); + this->ExpectBundledStats(edge_num - delete_count); // Test delete edge with soft delete, and revert. size_t soft_delete_todo = std::max(100, (int32_t) srcs.size() / 10); @@ -610,6 +669,8 @@ TEST_F(EdgeTableTest, TestDeleteEdge) { edge_num - delete_count - soft_deleted_edges.size()); ASSERT_EQ(tmp_dsts.size(), edge_num - delete_count - soft_deleted_edges.size()); + this->ExpectBundledStats(edge_num - delete_count - + soft_deleted_edges.size()); } // Revert soft deleted edges for (const auto& edge_record : soft_deleted_edges) { @@ -622,6 +683,7 @@ TEST_F(EdgeTableTest, TestDeleteEdge) { this->OutputOutgoingEndpoints(tmp_srcs, tmp_dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(tmp_srcs.size(), edge_num - delete_count); ASSERT_EQ(tmp_dsts.size(), edge_num - delete_count); + this->ExpectBundledStats(edge_num - delete_count); } } @@ -643,8 +705,10 @@ TEST_F(EdgeTableTest, TestBatchAddEdgesBundled) { this->InitIndexers(src_num, dst_num); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_int_); this->OpenEdgeTableInMemory(src_num, dst_num); + this->ExpectBundledStats(0); this->BatchInsert(std::move(batches)); EXPECT_EQ(this->edge_table->EdgeNum(), edge_num); + this->ExpectBundledStats(edge_num); // Generate more edges int64_t more_edge_num = 50; @@ -661,6 +725,7 @@ TEST_F(EdgeTableTest, TestBatchAddEdgesBundled) { // Insert more edges this->edge_table->BatchAddEdges(more_src_list, more_dst_list, edge_data); + this->ExpectBundledStats(edge_num + more_edge_num); std::vector srcs, dsts; this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num + more_edge_num); @@ -688,8 +753,10 @@ TEST_F(EdgeTableTest, TestBatchAddEdgesUnbundled) { this->InitIndexers(src_num, dst_num); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); this->OpenEdgeTableInMemory(src_num, dst_num); + this->ExpectUnbundledStats(0, 0); this->BatchInsert(std::move(batches)); EXPECT_EQ(this->edge_table->EdgeNum(), edge_num); + this->ExpectUnbundledStats(edge_num, ExpectedBatchInsertCapacity(edge_num)); // Generate more edges int64_t more_edge_num = 50; @@ -708,6 +775,8 @@ TEST_F(EdgeTableTest, TestBatchAddEdgesUnbundled) { // Insert more edges this->edge_table->BatchAddEdges(more_src_list, more_dst_list, edge_data); + this->ExpectUnbundledStats(edge_num + more_edge_num, + ExpectedBatchInsertCapacity(edge_num)); std::vector srcs, dsts; this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num + more_edge_num); @@ -741,6 +810,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { } this->edge_table->EnsureCapacity(this->src_indexer.size(), this->dst_indexer.size()); + this->ExpectBundledStats(0); std::vector> edge_data; for (size_t i = 0; i < src_lids.size(); ++i) { edge_data.push_back({neug::Property::from_int32(static_cast(i))}); @@ -755,6 +825,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { edge_count++; } EXPECT_EQ(edge_count, src_lids.size()); + this->ExpectBundledStats(edge_num); std::vector srcs, dsts; this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num); @@ -788,6 +859,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num - edges_to_delete.size()); ASSERT_EQ(dsts.size(), edge_num - edges_to_delete.size()); + this->ExpectBundledStats(edge_num - edges_to_delete.size()); srcs.clear(); dsts.clear(); this->OutputIncomingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); @@ -805,6 +877,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num); ASSERT_EQ(dsts.size(), edge_num); + this->ExpectBundledStats(edge_num); srcs.clear(); dsts.clear(); this->OutputIncomingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); @@ -815,6 +888,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { for (timestamp_t ts = 1; ts < 10; ++ts) { this->edge_table->AddEdge(0, 1, edge_data[0], ts, allocator); } + this->ExpectBundledStats(edge_num + 9); std::vector< std::pair, timestamp_t>> multi_edges_to_delete; @@ -843,6 +917,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { for (auto it = es_after_delete.begin(); it != es_after_delete.end(); ++it) { EXPECT_FALSE(it.get_vertex() == 0 && it.get_timestamp() % 2 == 1); } + this->ExpectBundledStats(edge_num + 9 - multi_edges_to_delete.size()); for (const auto& pair : multi_edges_to_delete) { const auto& edge_record = pair.first; this->edge_table->RevertDeleteEdge( @@ -859,6 +934,7 @@ TEST_F(EdgeTableTest, TestAddEdgeAndDelete) { } } EXPECT_EQ(revert_count, multi_edges_to_delete.size()); + this->ExpectBundledStats(edge_num + 9); } TEST_F(EdgeTableTest, TestAddEdgeDeleteUnbundled) { @@ -888,6 +964,7 @@ TEST_F(EdgeTableTest, TestAddEdgeDeleteUnbundled) { } this->edge_table->EnsureCapacity(this->src_indexer.size(), this->dst_indexer.size()); + this->ExpectUnbundledStats(0, 0); std::vector> edge_data; for (size_t i = 0; i < src_lids.size(); ++i) { edge_data.push_back({neug::Property::from_string_view("edge_data"), @@ -898,12 +975,14 @@ TEST_F(EdgeTableTest, TestAddEdgeDeleteUnbundled) { size_t edge_count = 0; this->edge_table->EnsureCapacity(edge_data.size()); + this->ExpectUnbundledStats(0, 4096); for (size_t i = 0; i < src_lids.size(); ++i) { this->edge_table->AddEdge(src_lids[i], dst_lids[i], edge_data[i], 0, allocator); edge_count++; } EXPECT_EQ(edge_count, src_lids.size()); + this->ExpectUnbundledStats(edge_num, 4096); std::vector srcs, dsts; this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); ASSERT_EQ(srcs.size(), edge_num); @@ -935,6 +1014,7 @@ TEST_F(EdgeTableTest, TestAddEdgeDeleteUnbundled) { neug::MAX_TIMESTAMP); ASSERT_EQ(srcs_after_delete.size(), edge_num - deleted_edge_indices.size()); ASSERT_EQ(dsts_after_delete.size(), edge_num - deleted_edge_indices.size()); + this->ExpectUnbundledStats(edge_num, 4096); for (size_t i = 0, j = 0; i < edge_num; ++i) { if (j < deleted_edge_indices.size() && i == deleted_edge_indices[j]) { j++; @@ -971,6 +1051,7 @@ TEST_F(EdgeTableTest, TestEdgeTableCompaction) { } this->edge_table->EnsureCapacity(this->src_indexer.size(), this->dst_indexer.size()); + this->ExpectBundledStats(0); std::vector> edge_data; for (size_t i = 0; i < src_lids.size(); ++i) { edge_data.push_back({neug::Property::from_int32(static_cast(i))}); @@ -981,6 +1062,7 @@ TEST_F(EdgeTableTest, TestEdgeTableCompaction) { this->edge_table->AddEdge(src_lids[i], dst_lids[i], edge_data[i], 0, allocator); } + this->ExpectBundledStats(edge_num); auto oe_view = this->edge_table->get_outgoing_view(neug::MAX_TIMESTAMP); auto ie_view = this->edge_table->get_incoming_view(neug::MAX_TIMESTAMP); size_t delete_count = 0; @@ -1005,7 +1087,9 @@ TEST_F(EdgeTableTest, TestEdgeTableCompaction) { delete_count++; } } + this->ExpectBundledStats(edge_num - delete_count); this->edge_table->Compact(true, false, neug::MAX_TIMESTAMP); + this->ExpectBundledStats(edge_num - delete_count); size_t edge_count = 0; for (size_t i = 0; i < dst_lids.size(); ++i) { auto edges = ie_view.get_edges(dst_lids[i]); @@ -1042,6 +1126,7 @@ TEST_F(EdgeTableTest, TestUpdateEdgeData) { } this->edge_table->EnsureCapacity(this->src_indexer.size(), this->dst_indexer.size()); + this->ExpectUnbundledStats(0, 0); std::vector> edge_data; for (size_t i = 0; i < src_lids.size(); ++i) { edge_data.push_back({neug::Property::from_string_view("old_data"), @@ -1049,11 +1134,13 @@ TEST_F(EdgeTableTest, TestUpdateEdgeData) { } this->edge_table->EnsureCapacity(edge_data.size()); + this->ExpectUnbundledStats(0, 4096); neug::Allocator allocator(neug::MemoryStrategy::kMemoryOnly, allocator_dir_); for (size_t i = 0; i < src_lids.size(); ++i) { this->edge_table->AddEdge(src_lids[i], dst_lids[i], edge_data[i], 0, allocator); } + this->ExpectUnbundledStats(edge_num, 4096); std::vector new_data = { neug::Property::from_string_view("new_data"), neug::Property::from_int32(static_cast(1))}; @@ -1088,6 +1175,289 @@ TEST_F(EdgeTableTest, TestUpdateEdgeData) { } } +TEST_F(EdgeTableTest, TestAddPropertiesTransitionFromEmptyToBundledUnbundled) { + GTEST_SKIP() << "Enable this check when string default value is ok"; + this->InitIndexers(4, 4); + this->ConstructEdgeTable(src_label_, dst_label_, edge_label_empty_); + this->OpenEdgeTableInMemory(4, 4); + this->ExpectBundledStats(0); + + std::vector> endpoints = {{0, 1}, {1, 2}, {2, 3}}; + std::vector src_list, dst_list; + for (const auto& [src_oid, dst_oid] : endpoints) { + src_list.emplace_back(src_oid); + dst_list.emplace_back(dst_oid); + } + auto src_arrs = convert_to_arrow_arrays(src_list, src_list.size()); + auto dst_arrs = convert_to_arrow_arrays(dst_list, dst_list.size()); + auto batches = + convert_to_record_batches({"src", "dst"}, {src_arrs, dst_arrs}); + this->BatchInsert(std::move(batches)); + this->ExpectBundledStats(endpoints.size()); + + schema_.AddEdgeProperties("person", "comment", "create0", {"weight"}, + {neug::DataTypeId::kInt32}, + {neug::Property::from_int32(7)}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_)); + this->edge_table->AddProperties({"weight"}, {neug::DataTypeId::kInt32}, + {neug::Property::from_int32(7)}); + this->ExpectBundledStats(endpoints.size()); + + std::vector srcs, dsts; + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + ASSERT_EQ(srcs.size(), endpoints.size()); + std::vector weights; + this->OutputOutgoingEdgeData(weights, neug::MAX_TIMESTAMP, 0); + ASSERT_EQ(weights.size(), endpoints.size()); + for (auto weight : weights) { + EXPECT_EQ(weight, 7); + } + + schema_.AddEdgeProperties("person", "comment", "create0", {"tag"}, + {neug::DataTypeId::kVarchar}, + {neug::Property::from_string_view("new-tag")}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_)); + this->edge_table->AddProperties( + {"tag"}, {neug::DataTypeId::kVarchar}, + {neug::Property::from_string_view("new-tag")}); + this->ExpectUnbundledStats(endpoints.size(), 4096); + + std::vector weights_after; + std::vector tags; + this->OutputOutgoingEdgeData(weights_after, neug::MAX_TIMESTAMP, 0); + this->OutputOutgoingEdgeData(tags, neug::MAX_TIMESTAMP, 1); + ASSERT_EQ(weights_after.size(), endpoints.size()); + ASSERT_EQ(tags.size(), endpoints.size()); + for (size_t i = 0; i < endpoints.size(); ++i) { + EXPECT_EQ(weights_after[i], 7); + EXPECT_EQ(tags[i], "new-tag"); + } +} + +TEST_F(EdgeTableTest, TestAddStringPropertyTransitionFromEmptyToUnbundled) { + GTEST_SKIP() << "Enable this check when string default value is ok"; + this->InitIndexers(4, 4); + this->ConstructEdgeTable(src_label_, dst_label_, edge_label_empty_); + this->OpenEdgeTableInMemory(4, 4); + this->ExpectBundledStats(0); + + std::vector src_list = {0, 1, 2}; + std::vector dst_list = {1, 2, 3}; + auto src_arrs = convert_to_arrow_arrays(src_list, src_list.size()); + auto dst_arrs = convert_to_arrow_arrays(dst_list, dst_list.size()); + auto batches = + convert_to_record_batches({"src", "dst"}, {src_arrs, dst_arrs}); + this->BatchInsert(std::move(batches)); + this->ExpectBundledStats(src_list.size()); + + schema_.AddEdgeProperties("person", "comment", "create0", {"tag"}, + {neug::DataTypeId::kVarchar}, + {neug::Property::from_string_view("seed")}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_)); + this->edge_table->AddProperties({"tag"}, {neug::DataTypeId::kVarchar}, + {neug::Property::from_string_view("seed")}); + this->ExpectUnbundledStats(src_list.size(), 4096); + + std::vector tags; + this->OutputOutgoingEdgeData(tags, neug::MAX_TIMESTAMP, 0); + ASSERT_EQ(tags.size(), src_list.size()); + for (auto tag : tags) { + EXPECT_EQ(tag, "seed"); + } +} + +TEST_F(EdgeTableTest, + TestDeletePropertiesTransitionFromUnbundledToBundledEmpty) { + this->InitIndexers(4, 4); + this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); + this->OpenEdgeTableInMemory(4, 4); + this->edge_table->EnsureCapacity(this->src_indexer.size(), + this->dst_indexer.size(), 100); + this->ExpectUnbundledStats(0, 4096); + + std::vector> input = { + {0, 1, "a", 11}, {1, 2, "b", 22}, {2, 3, "c", 33}}; + neug::Allocator allocator(neug::MemoryStrategy::kMemoryOnly, allocator_dir_); + for (const auto& [src_oid, dst_oid, data0, data1] : input) { + this->edge_table->AddEdge( + this->GetSrcLid(neug::Property::from_int64(src_oid)), + this->GetDstLid(neug::Property::from_int64(dst_oid)), + {neug::Property::from_string_view(data0), + neug::Property::from_int32(data1)}, + 0, allocator); + } + this->ExpectUnbundledStats(input.size(), 4096); + + this->edge_table->DeleteProperties({"data1"}); + schema_.DeleteEdgeProperties("person", "comment", "create3", {"data1"}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_str_int_)); + this->ExpectUnbundledStats(input.size(), 4096); + + std::vector srcs, dsts; + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + std::vector remaining_prop; + this->OutputOutgoingEdgeData(remaining_prop, + neug::MAX_TIMESTAMP, 0); + ASSERT_EQ(srcs.size(), input.size()); + ASSERT_EQ(remaining_prop.size(), input.size()); + std::vector> output; + for (size_t i = 0; i < srcs.size(); ++i) { + output.emplace_back(srcs[i], dsts[i], std::string(remaining_prop[i])); + } + std::sort(output.begin(), output.end()); + std::vector> expected; + for (const auto& [src_oid, dst_oid, data0, data1] : input) { + expected.emplace_back(src_oid, dst_oid, data0); + } + std::sort(expected.begin(), expected.end()); + EXPECT_EQ(output, expected); + + this->edge_table->DeleteProperties({"data0"}); + schema_.DeleteEdgeProperties("person", "comment", "create3", {"data0"}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_str_int_)); + this->ExpectBundledStats(input.size()); + + srcs.clear(); + dsts.clear(); + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + ASSERT_EQ(srcs.size(), input.size()); + ASSERT_EQ(dsts.size(), input.size()); + + this->edge_table->AddEdge(this->GetSrcLid(neug::Property::from_int64(3)), + this->GetDstLid(neug::Property::from_int64(0)), {}, + 0, allocator); + this->ExpectBundledStats(input.size() + 1); + srcs.clear(); + dsts.clear(); + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + ASSERT_EQ(srcs.size(), input.size() + 1); + ASSERT_EQ(dsts.size(), input.size() + 1); +} + +TEST_F(EdgeTableTest, TestDeletePropertiesTransitionFromUnbundledToBundled) { + this->InitIndexers(4, 4); + this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); + this->OpenEdgeTableInMemory(4, 4); + this->edge_table->EnsureCapacity(this->src_indexer.size(), + this->dst_indexer.size(), 100); + this->ExpectUnbundledStats(0, 4096); + + std::vector> input = { + {0, 1, "a", 11}, {1, 2, "b", 22}, {2, 3, "c", 33}}; + neug::Allocator allocator(neug::MemoryStrategy::kMemoryOnly, allocator_dir_); + for (const auto& [src_oid, dst_oid, data0, data1] : input) { + this->edge_table->AddEdge( + this->GetSrcLid(neug::Property::from_int64(src_oid)), + this->GetDstLid(neug::Property::from_int64(dst_oid)), + {neug::Property::from_string_view(data0), + neug::Property::from_int32(data1)}, + 0, allocator); + } + this->ExpectUnbundledStats(input.size(), 4096); + + this->edge_table->DeleteProperties({"data0"}); + schema_.DeleteEdgeProperties("person", "comment", "create3", {"data0"}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_str_int_)); + + std::vector srcs, dsts; + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + std::vector remaining_prop; + this->OutputOutgoingEdgeData(remaining_prop, neug::MAX_TIMESTAMP, 0); + ASSERT_EQ(srcs.size(), input.size()); + ASSERT_EQ(remaining_prop.size(), input.size()); + std::vector> output; + for (size_t i = 0; i < srcs.size(); ++i) { + output.emplace_back(srcs[i], dsts[i], remaining_prop[i]); + } + std::sort(output.begin(), output.end()); + std::vector> expected; + for (const auto& [src_oid, dst_oid, data0, data1] : input) { + expected.emplace_back(src_oid, dst_oid, data1); + } + std::sort(expected.begin(), expected.end()); + EXPECT_EQ(output, expected); + + srcs.clear(); + dsts.clear(); + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + ASSERT_EQ(srcs.size(), input.size()); + ASSERT_EQ(dsts.size(), input.size()); + + this->ExpectBundledStats(input.size()); + this->edge_table->AddEdge(this->GetSrcLid(neug::Property::from_int64(3)), + this->GetDstLid(neug::Property::from_int64(0)), + {Property::from_int32(44)}, 0, allocator); + this->ExpectBundledStats(input.size() + 1); +} + +TEST_F(EdgeTableTest, TestAddAndDeletePropertiesStayUnbundled) { + this->InitIndexers(4, 4); + this->ConstructEdgeTable(src_label_, dst_label_, edge_label_str_int_); + this->OpenEdgeTableInMemory(4, 4); + this->edge_table->EnsureCapacity(this->src_indexer.size(), + this->dst_indexer.size(), 100); + this->ExpectUnbundledStats(0, 4096); + + std::vector> input = { + {0, 1, "a", 11}, {1, 2, "b", 22}, {2, 3, "c", 33}}; + neug::Allocator allocator(neug::MemoryStrategy::kMemoryOnly, allocator_dir_); + for (const auto& [src_oid, dst_oid, data0, data1] : input) { + this->edge_table->AddEdge( + this->GetSrcLid(neug::Property::from_int64(src_oid)), + this->GetDstLid(neug::Property::from_int64(dst_oid)), + {neug::Property::from_string_view(data0), + neug::Property::from_int32(data1)}, + 0, allocator); + } + this->ExpectUnbundledStats(input.size(), 4096); + + schema_.AddEdgeProperties("person", "comment", "create3", {"score"}, + {neug::DataTypeId::kInt32}, + {neug::Property::from_int32(99)}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_str_int_)); + this->edge_table->AddProperties({"score"}, {neug::DataTypeId::kInt32}, + {neug::Property::from_int32(99)}); + this->ExpectUnbundledStats(input.size(), 4096); + + std::vector score; + this->OutputOutgoingEdgeData(score, neug::MAX_TIMESTAMP, 2); + ASSERT_EQ(score.size(), input.size()); + for (auto value : score) { + EXPECT_EQ(value, 99); + } + + this->edge_table->DeleteProperties({"score"}); + schema_.DeleteEdgeProperties("person", "comment", "create3", {"score"}); + this->edge_table->SetEdgeSchema( + schema_.get_edge_schema(src_label_, dst_label_, edge_label_str_int_)); + this->ExpectUnbundledStats(input.size(), 4096); + + std::vector data0_after; + std::vector data1_after; + this->OutputOutgoingEdgeData(data0_after, + neug::MAX_TIMESTAMP, 0); + this->OutputOutgoingEdgeData(data1_after, neug::MAX_TIMESTAMP, 1); + ASSERT_EQ(data0_after.size(), input.size()); + ASSERT_EQ(data1_after.size(), input.size()); + std::vector> output; + std::vector srcs, dsts; + this->OutputOutgoingEndpoints(srcs, dsts, neug::MAX_TIMESTAMP); + for (size_t i = 0; i < srcs.size(); ++i) { + output.emplace_back(srcs[i], dsts[i], std::string(data0_after[i]), + data1_after[i]); + } + std::sort(output.begin(), output.end()); + std::sort(input.begin(), input.end()); + EXPECT_EQ(output, input); +} + template struct TypePair { using EdType = EDATA_T; @@ -1209,12 +1579,15 @@ TYPED_TEST(EdgeTableToolsTest, TestBatchAddEdges) { EdgeTable e_table = EdgeTable(edge_schema); e_table.BatchAddEdges(indexer, indexer, suppliers[0]); EXPECT_EQ(e_table.EdgeNum(), 10); + EXPECT_EQ(e_table.Size(), 10); + EXPECT_EQ(e_table.Capacity(), neug::CsrBase::INFINITE_CAPACITY); std::vector new_property_name = {"new_property"}; std::vector new_property_type = {DataTypeId::kInt32}; edge_schema->add_properties(new_property_name, new_property_type); e_table.AddProperties(new_property_name, new_property_type); EXPECT_EQ(e_table.PropertyNum(), 2); + EXPECT_EQ(e_table.Size(), 10); } TYPED_TEST(EdgeTableToolsTest, TestAddProperties) { @@ -1253,6 +1626,8 @@ TYPED_TEST(EdgeTableToolsTest, TestAddProperties) { EdgeTable e_table = EdgeTable(edge_schema); e_table.BatchAddEdges(indexer, indexer, suppliers[0]); EXPECT_EQ(e_table.EdgeNum(), 10); + EXPECT_EQ(e_table.Size(), 10); + EXPECT_EQ(e_table.Capacity(), neug::CsrBase::INFINITE_CAPACITY); if constexpr (std::is_same_v) { new_property_type = {DataTypeId::kInt32}; } else if constexpr (std::is_same_v) { @@ -1277,6 +1652,8 @@ TYPED_TEST(EdgeTableToolsTest, TestAddProperties) { edge_schema->add_properties(new_property_name, new_property_type); e_table.AddProperties(new_property_name, new_property_type); + EXPECT_EQ(e_table.Size(), 10); + EXPECT_EQ(e_table.Capacity(), neug::CsrBase::INFINITE_CAPACITY); } } // namespace test diff --git a/tests/storage/test_vertex_table.cc b/tests/storage/test_vertex_table.cc index 56a7a24e..45757065 100644 --- a/tests/storage/test_vertex_table.cc +++ b/tests/storage/test_vertex_table.cc @@ -811,4 +811,4 @@ TEST_F(VertexTableTest, VertexSetForeachVertex) { size_t count = 0; vset.foreach_vertex([&](neug::vid_t vid) { count++; }); EXPECT_EQ(count, 5); // Only odd lids are valid at ts=20 -} \ No newline at end of file +} diff --git a/tools/python_bind/tests/test_ddl.py b/tools/python_bind/tests/test_ddl.py index 8b9b63ea..1f23e085 100644 --- a/tools/python_bind/tests/test_ddl.py +++ b/tools/python_bind/tests/test_ddl.py @@ -419,3 +419,71 @@ def test_get_varchar_default_value_2(): assert list(res) == [[1234567890], [9876543210]] conn.close() db.close() + + +def test_drop_add_edge_table_column(): + db_dir = "/tmp/test_drop_add_edge_table_column" + shutil.rmtree(db_dir, ignore_errors=True) + db = Database(db_dir, "w") + conn = db.connect() + # First create the graph schema + conn.execute( + """ + CREATE NODE TABLE IF NOT EXISTS TestNode( + id INT64 PRIMARY KEY, + thread_id INT64 + ) + """ + ) + conn.execute( + """ + CREATE REL TABLE IF NOT EXISTS TestEdge( + FROM TestNode TO TestNode + ) + """ + ) + conn.close() + db.close() + + db2 = Database(db_dir, "w") + conn2 = db2.connect() + conn2.execute("CREATE (v:TestNode {id: 1, thread_id: 1});") + conn2.execute("CREATE (v:TestNode {id: 2, thread_id: 2});") + conn2.execute("CREATE (v:TestNode {id: 3, thread_id: 3});") + conn2.execute( + "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge]->(v2);" + ) + conn2.execute( + "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 3}) CREATE (v)-[:TestEdge]->(v2);" + ) + conn2.execute("ALTER TABLE TestEdge ADD iteration INT64;") + conn2.execute( + "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge {iteration: 1}]->(v2);" + ) + ret = conn2.execute( + "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.iteration;" + ) + assert list(ret) == [[0], [0], [1]] + conn2.execute("ALTER TABLE TestEdge DROP iteration;") + conn2.execute("ALTER TABLE TestEdge ADD iteration INT64;") + conn2.execute("ALTER TABLE TestEdge ADD iteration2 INT64;") + conn2.execute( + "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge {iteration: 2, iteration2: 3}]->(v2);" + ) + ret = conn2.execute( + "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.iteration, e.iteration2;" + ) + assert list(ret) == [[0, 0], [0, 0], [0, 0], [2, 3]] + conn2.execute("ALTER TABLE TestEdge DROP iteration;") + conn2.execute("ALTER TABLE TestEdge DROP iteration2;") + conn2.execute("ALTER TABLE TestEdge ADD description STRING;") + conn2.execute( + "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge {description: 'test'}]->(v2);" + ) + ret = conn2.execute( + "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.description ORDER BY e.description; " + ) + assert list(ret) == [[""], [""], [""], ["test"], ["test"]] + print(list(ret)) + conn2.close() + db2.close() From f7cc99ce3ac2cee28c332c61551d5958a7a66dc2 Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Thu, 19 Mar 2026 10:22:20 +0800 Subject: [PATCH 2/6] fix --- src/storages/graph/edge_table.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index beca9411..c363e516 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -1072,7 +1072,7 @@ void EdgeTable::dropAndCreateNewBundledCSR( std::get<1>(edges), std::get<0>(edges), property_type, meta_->default_property_values[0], new_in_csr.get()); } else { - auto row_id_col = std::make_shared(0, StorageStrategy::kMem); + auto row_id_col = std::make_shared(StorageStrategy::kMem); auto edges = out_csr_->batch_export(row_id_col); std::vector remaining_data; remaining_data.reserve(row_id_col->size()); From 5c32837e0e8ff254c6d35833e691abed5a535958 Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Thu, 19 Mar 2026 10:40:06 +0800 Subject: [PATCH 3/6] fix ci --- src/storages/graph/edge_table.cc | 2 +- tests/storage/test_edge_table.cc | 21 +++++++++++++++------ tools/python_bind/tests/test_ddl.py | 1 - 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index c363e516..6e216173 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -1144,7 +1144,7 @@ void EdgeTable::dropAndCreateNewUnbundledCSR(bool delete_property) { table_idx_.store(prev_data_col->size()); EnsureCapacity(prev_data_col->size()); } else if (!delete_property) { - table_->resize(std::get<0>(edges).size()); + table_->resize(std::get<0>(edges).size(), meta_->default_property_values); table_idx_.store(std::get<0>(edges).size()); EnsureCapacity(std::get<0>(edges).size()); } diff --git a/tests/storage/test_edge_table.cc b/tests/storage/test_edge_table.cc index f2326e80..91390288 100644 --- a/tests/storage/test_edge_table.cc +++ b/tests/storage/test_edge_table.cc @@ -1176,7 +1176,6 @@ TEST_F(EdgeTableTest, TestUpdateEdgeData) { } TEST_F(EdgeTableTest, TestAddPropertiesTransitionFromEmptyToBundledUnbundled) { - GTEST_SKIP() << "Enable this check when string default value is ok"; this->InitIndexers(4, 4); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_empty_); this->OpenEdgeTableInMemory(4, 4); @@ -1237,7 +1236,6 @@ TEST_F(EdgeTableTest, TestAddPropertiesTransitionFromEmptyToBundledUnbundled) { } TEST_F(EdgeTableTest, TestAddStringPropertyTransitionFromEmptyToUnbundled) { - GTEST_SKIP() << "Enable this check when string default value is ok"; this->InitIndexers(4, 4); this->ConstructEdgeTable(src_label_, dst_label_, edge_label_empty_); this->OpenEdgeTableInMemory(4, 4); @@ -1252,21 +1250,32 @@ TEST_F(EdgeTableTest, TestAddStringPropertyTransitionFromEmptyToUnbundled) { this->BatchInsert(std::move(batches)); this->ExpectBundledStats(src_list.size()); - schema_.AddEdgeProperties("person", "comment", "create0", {"tag"}, - {neug::DataTypeId::kVarchar}, - {neug::Property::from_string_view("seed")}); this->edge_table->SetEdgeSchema( schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_)); + schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_) + ->add_properties({"tag"}, {neug::DataTypeId::kVarchar}, {}, + {neug::Property::from_string_view("seed")}); this->edge_table->AddProperties({"tag"}, {neug::DataTypeId::kVarchar}, {neug::Property::from_string_view("seed")}); + schema_.get_edge_schema(src_label_, dst_label_, edge_label_empty_) + ->add_properties({"desc"}, {neug::DataTypeId::kVarchar}, {}, + {neug::Property::from_string_view("unknown")}); + this->edge_table->AddProperties( + {"desc"}, {neug::DataTypeId::kVarchar}, + {neug::Property::from_string_view("unknown")}); this->ExpectUnbundledStats(src_list.size(), 4096); - std::vector tags; + std::vector tags, descs; this->OutputOutgoingEdgeData(tags, neug::MAX_TIMESTAMP, 0); + this->OutputIncomingEdgeData(descs, neug::MAX_TIMESTAMP, 1); ASSERT_EQ(tags.size(), src_list.size()); for (auto tag : tags) { EXPECT_EQ(tag, "seed"); } + ASSERT_EQ(descs.size(), dst_list.size()); + for (auto desc : descs) { + EXPECT_EQ(desc, "unknown"); + } } TEST_F(EdgeTableTest, diff --git a/tools/python_bind/tests/test_ddl.py b/tools/python_bind/tests/test_ddl.py index 1f23e085..9858ae3b 100644 --- a/tools/python_bind/tests/test_ddl.py +++ b/tools/python_bind/tests/test_ddl.py @@ -484,6 +484,5 @@ def test_drop_add_edge_table_column(): "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.description ORDER BY e.description; " ) assert list(ret) == [[""], [""], [""], ["test"], ["test"]] - print(list(ret)) conn2.close() db2.close() From 24472ad3e646254dd7472d9e059f6e4a54a7db88 Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Thu, 19 Mar 2026 10:43:49 +0800 Subject: [PATCH 4/6] change log(fatal) to throw_exception --- src/storages/graph/edge_table.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index 6e216173..2f7fc7a9 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -236,8 +236,8 @@ static std::unique_ptr create_csr(bool is_mutable, return create_csr_impl(is_mutable, strategy); } default: { - LOG(FATAL) << "not support edge data type: " - << std::to_string(property_type); + THROW_NOT_SUPPORTED_EXCEPTION("not support edge data type: " + + std::to_string(property_type)); return nullptr; } } @@ -472,7 +472,8 @@ void batch_add_bundled_edges_impl(CsrBase* out_csr, CsrBase* in_csr, FOR_EACH_DATA_TYPE_NO_STRING(TYPE_DISPATCHER) #undef TYPE_DISPATCHER default: - LOG(FATAL) << "not support edge data type: " << prop_types[0].ToString(); + THROW_NOT_SUPPORTED_EXCEPTION("not support edge data type: " + + std::to_string(prop_types[0].id())); } } From 907cebba91bc9afcbf9999f37fbadcde00b98451 Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Thu, 19 Mar 2026 11:20:24 +0800 Subject: [PATCH 5/6] turn off some testing code --- tools/python_bind/tests/test_ddl.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/python_bind/tests/test_ddl.py b/tools/python_bind/tests/test_ddl.py index 9858ae3b..9765039f 100644 --- a/tools/python_bind/tests/test_ddl.py +++ b/tools/python_bind/tests/test_ddl.py @@ -476,13 +476,14 @@ def test_drop_add_edge_table_column(): assert list(ret) == [[0, 0], [0, 0], [0, 0], [2, 3]] conn2.execute("ALTER TABLE TestEdge DROP iteration;") conn2.execute("ALTER TABLE TestEdge DROP iteration2;") - conn2.execute("ALTER TABLE TestEdge ADD description STRING;") - conn2.execute( - "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge {description: 'test'}]->(v2);" - ) - ret = conn2.execute( - "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.description ORDER BY e.description; " - ) - assert list(ret) == [[""], [""], [""], ["test"], ["test"]] + # TODO(zhanglei): Turn on the test after issue #85 is fixed. + # conn2.execute("ALTER TABLE TestEdge ADD description STRING DEFAULT 'unknown';") + # conn2.execute( + # "MATCH (v:TestNode {id: 1}), (v2:TestNode {id: 2}) CREATE (v)-[:TestEdge {description: 'test'}]->(v2);" + # ) + # ret = conn2.execute( + # "MATCH (v1:TestNode)-[e:TestEdge]->(v2:TestNode) RETURN e.description ORDER BY e.description; " + # ) + # assert list(ret) == [["unknown"], ["unknown"], ["unknown"], ["unknown"], ["test"]] conn2.close() db2.close() From 0d2ef3cce715dd42c8026d1a87e28056f16dfc8f Mon Sep 17 00:00:00 2001 From: zhanglei1949 Date: Thu, 19 Mar 2026 11:23:36 +0800 Subject: [PATCH 6/6] fix --- src/storages/graph/edge_table.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index 2f7fc7a9..44190afd 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -198,8 +198,9 @@ void batch_put_edges_to_bundled_csr(const std::vector& src_lid, break; } default: - LOG(FATAL) << "Unsupported edge property type " - << static_cast(property_type); + THROW_NOT_SUPPORTED_EXCEPTION( + "Unsupported edge property type: " + + std::to_string(static_cast(property_type))); } }