This is an automated email from the ASF dual-hosted git repository.

my-ship-it pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit 74a5b90d58a517760e519dd923c3286433b93ed1
Author: GongXun <[email protected]>
AuthorDate: Mon Jul 27 03:02:51 2026 +0800

    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
---
 contrib/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(-)

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,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to