From afd65abd6431144e0f05a2f8173b25587d493b3c Mon Sep 17 00:00:00 2001 From: GongXun Date: Mon, 27 Jul 2026 03:02:51 +0800 Subject: [PATCH] PAX: fix SIGSEGV in SUM stats merge during DELETE on minmax columns When a PAX table has minmax_columns that also support SUM statistics (e.g. int, bigint, numeric), repeated DELETE operations can crash a segment with SIGSEGV inside datumCopy() during the visibility-map statistics refresh path. Two bugs were identified in MicroPartitionStats::MergeRawInfo() and MergeTo(): 1. Wrong type metadata in FromValue(): The serialized SUM value was deserialized using the column physical type (typlen/typbyval) instead of the SUM aggregate return type (rettyplen/rettypbyval). For example, sum(bigint) returns numeric, so the stored SUM datum must be interpreted as numeric, not as int8. Using the wrong type metadata produces an invalid Datum that may crash in datumCopy(). 2. Swapped arguments in datumCopy(): The call passed (value, typlen, typbyval) but the wrapper signature is datumCopy(value, typByVal, typLen). This caused pass-by-value vs pass-by-reference confusion, leading to memory corruption. Add regression test (delete_sum_stats) covering DELETE + INSERT + DELETE on int, bigint, and numeric columns with minmax_columns enabled. Fixes #1767 --- .../pax_storage/expected/delete_sum_stats.out | 53 +++++++++++++++++++ contrib/pax_storage/pax_schedule | 1 + contrib/pax_storage/sql/delete_sum_stats.sql | 47 ++++++++++++++++ .../src/cpp/storage/micro_partition_stats.cc | 10 ++-- 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 contrib/pax_storage/expected/delete_sum_stats.out create mode 100644 contrib/pax_storage/sql/delete_sum_stats.sql diff --git a/contrib/pax_storage/expected/delete_sum_stats.out b/contrib/pax_storage/expected/delete_sum_stats.out new file mode 100644 index 00000000000..8b4b200cd26 --- /dev/null +++ b/contrib/pax_storage/expected/delete_sum_stats.out @@ -0,0 +1,53 @@ +-- Verify that DELETE statistics refresh uses the SUM result type when +-- deserializing and merging stats. In particular, sum(bigint) returns numeric. +set pax.max_tuples_per_group = 25; +create table pax_delete_sum_stats ( + dist_key int, + id bigint, + int_amount int, + bigint_amount bigint, + numeric_amount numeric +) +using pax +with (minmax_columns = 'int_amount,bigint_amount,numeric_amount') +distributed by (dist_key); +insert into pax_delete_sum_stats +select 1, i, i, i, i::numeric * 2 +from generate_series(1, 1000) i; +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + rows | int_sum | bigint_sum | numeric_sum +------+---------+------------+------------- + 1000 | 500500 | 500500 | 1001000 +(1 row) + +delete from pax_delete_sum_stats where id between 1 and 100; +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + rows | int_sum | bigint_sum | numeric_sum +------+---------+------------+------------- + 900 | 495450 | 495450 | 990900 +(1 row) + +insert into pax_delete_sum_stats +select 1, i, i, i, i::numeric * 2 +from generate_series(1001, 1100) i; +delete from pax_delete_sum_stats where id between 301 and 400; +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + rows | int_sum | bigint_sum | numeric_sum +------+---------+------------+------------- + 900 | 565450 | 565450 | 1130900 +(1 row) + +drop table pax_delete_sum_stats; +reset pax.max_tuples_per_group; diff --git a/contrib/pax_storage/pax_schedule b/contrib/pax_storage/pax_schedule index f9818f8e0b7..5f7ef41d2d8 100644 --- a/contrib/pax_storage/pax_schedule +++ b/contrib/pax_storage/pax_schedule @@ -10,6 +10,7 @@ test: statistics_bloom_filter test: filter_tree filter_tree_arithmetic test: filter_tree_root_quals test: statistics/statistics +test: delete_sum_stats test: statistics/min_max_time_types statistics/min_max_text_types statistics/min_max_other_types statistics/min_max_net_types statistics/min_max_int_types statistics/min_max_geo_types statistics/min_max_float_types statistics/min_max_bit_byte_types diff --git a/contrib/pax_storage/sql/delete_sum_stats.sql b/contrib/pax_storage/sql/delete_sum_stats.sql new file mode 100644 index 00000000000..dce2c9eba18 --- /dev/null +++ b/contrib/pax_storage/sql/delete_sum_stats.sql @@ -0,0 +1,47 @@ +-- Verify that DELETE statistics refresh uses the SUM result type when +-- deserializing and merging stats. In particular, sum(bigint) returns numeric. +set pax.max_tuples_per_group = 25; + +create table pax_delete_sum_stats ( + dist_key int, + id bigint, + int_amount int, + bigint_amount bigint, + numeric_amount numeric +) +using pax +with (minmax_columns = 'int_amount,bigint_amount,numeric_amount') +distributed by (dist_key); + +insert into pax_delete_sum_stats +select 1, i, i, i, i::numeric * 2 +from generate_series(1, 1000) i; + +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + +delete from pax_delete_sum_stats where id between 1 and 100; + +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + +insert into pax_delete_sum_stats +select 1, i, i, i, i::numeric * 2 +from generate_series(1001, 1100) i; + +delete from pax_delete_sum_stats where id between 301 and 400; + +select count(*) as rows, + sum(int_amount) as int_sum, + sum(bigint_amount) as bigint_sum, + sum(numeric_amount) as numeric_sum +from pax_delete_sum_stats; + +drop table pax_delete_sum_stats; +reset pax.max_tuples_per_group; diff --git a/contrib/pax_storage/src/cpp/storage/micro_partition_stats.cc b/contrib/pax_storage/src/cpp/storage/micro_partition_stats.cc index 53342ad0e2a..d307ed49431 100644 --- a/contrib/pax_storage/src/cpp/storage/micro_partition_stats.cc +++ b/contrib/pax_storage/src/cpp/storage/micro_partition_stats.cc @@ -758,7 +758,7 @@ void MicroPartitionStats::MergeRawInfo( } else if (sum_stat->status == STATUS_NEED_UPDATE) { sum_result = FromValue(stats_info->columnstats(column_index).datastats().sum(), - typlen, typbyval, column_index); + sum_stat->rettyplen, sum_stat->rettypbyval, column_index); Datum newval = cbdb::FunctionCall2Coll(&sum_stat->add_func, InvalidOid, sum_stat->result, sum_result); if (!sum_stat->rettypbyval && newval != sum_stat->result && @@ -766,11 +766,11 @@ void MicroPartitionStats::MergeRawInfo( cbdb::Pfree(cbdb::DatumToPointer(sum_stat->result)); } sum_stat->result = - cbdb::datumCopy(newval, sum_stat->rettyplen, sum_stat->rettypbyval); + cbdb::datumCopy(newval, sum_stat->rettypbyval, sum_stat->rettyplen); } else if (sum_stat->status == STATUS_MISSING_INIT_VAL) { sum_result = FromValue(stats_info->columnstats(column_index).datastats().sum(), - typlen, typbyval, column_index); + sum_stat->rettyplen, sum_stat->rettypbyval, column_index); sum_stat->result = cbdb::datumCopy(sum_result, sum_stat->rettypbyval, sum_stat->rettyplen); sum_stat->status = STATUS_NEED_UPDATE; @@ -966,8 +966,8 @@ void MicroPartitionStats::MergeTo(MicroPartitionStats *stats) { left_sum_stat->result) { cbdb::Pfree(cbdb::DatumToPointer(left_sum_stat->result)); } - left_sum_stat->result = cbdb::datumCopy(newval, left_sum_stat->rettyplen, - left_sum_stat->rettypbyval); + left_sum_stat->result = cbdb::datumCopy( + newval, left_sum_stat->rettypbyval, left_sum_stat->rettyplen); } else if (left_sum_stat->status == STATUS_MISSING_INIT_VAL) { left_sum_stat->result = cbdb::datumCopy(right_sum_stat->result, left_sum_stat->rettypbyval,