rangareddy commented on issue #15970:
URL: https://github.com/apache/hudi/issues/15970#issuecomment-4889969014

   **Sample Tested code:**
   
   ```python
   import os
   from pyspark.sql import SparkSession
   from pyspark.sql.types import StructType, StructField, StringType, LongType, 
BooleanType
   
   root = os.environ.get("ROOT", "/tmp/cs15970py")
   spark = SparkSession.builder.appName("hudi-15970").getOrCreate()
   spark.sparkContext.setLogLevel("WARN")
   
   base_schema = StructType([
       StructField("id", StringType(), False),
       StructField("ts", LongType(), False),
       StructField("part", StringType(), False),
       StructField("fare", LongType(), False),
   ])
   del_schema = StructType(base_schema.fields + 
[StructField("_hoodie_is_deleted", BooleanType(), False)])
   
   def opts(extra=None):
       o = {
           "hoodie.table.name": "cs_mor",
           "hoodie.datasource.write.recordkey.field": "id",
           "hoodie.datasource.write.precombine.field": "ts",
           "hoodie.datasource.write.partitionpath.field": "part",
           "hoodie.datasource.write.table.type": "MERGE_ON_READ",
           "hoodie.metadata.enable": "true",
           "hoodie.metadata.index.column.stats.enable": "true",
           "hoodie.metadata.index.column.stats.column.list": "fare",
           "hoodie.datasource.write.hive_style_partitioning": "true",
       }
       if extra:
           o.update(extra)
       return o
   
   def wr(rows, schema, o, op, mode, path):
       df = spark.createDataFrame(rows, schema)   # rows are position-matched 
tuples
       (df.write.format("hudi").options(**o)
           .option("hoodie.datasource.write.operation", 
op).mode(mode).save(path))
   
   def rd(path, pred):
       return (spark.read.format("hudi")
               .option("hoodie.metadata.enable", "true")
               .option("hoodie.enable.data.skipping", "true")
               .load(path).where(pred).count())
   
   def dump_cs(path):
       try:
           (spark.read.format("hudi").load(path + "/.hoodie/metadata")
               .where("type = 3 AND ColumnStatsMetadata.columnName = 'fare'")
               .selectExpr("ColumnStatsMetadata.fileName AS file",
                           "ColumnStatsMetadata.minValue AS minV",
                           "ColumnStatsMetadata.maxValue AS maxV",
                           "ColumnStatsMetadata.nullCount AS nulls",
                           "ColumnStatsMetadata.isDeleted AS 
del").show(truncate=False))
       except Exception as e:
           print(">>> (col_stats dump skipped: %s)" % type(e).__name__)
   
   # ---- items (1)+(2): one value per slice, merged base+log ----
   try:
       p = root + "/slice"
       wr([("a", 1, "p1", 10), ("b", 1, "p1", 20), ("c", 1, "p1", 30)], 
base_schema, opts(), "insert", "overwrite", p)
       wr([("a", 2, "p1", 1000)], base_schema, opts(), "upsert", "append", p)   
# fare=1000 lives ONLY in the
       total, log_only, beyond = rd(p, "1=1"), rd(p, "fare >= 900"), rd(p, 
"fare > 5000")
       print(">>> SLICE col_stats (one row per data file in the slice):"); 
dump_cs(p)
       ok = (total == 3 and log_only == 1 and beyond == 0)
       print(">>> RESULT=SLICE %s total=%d fare>=900=%d fare>5000=%d (expect 3, 
1 [log value survives], 0)"
             % ("OK" if ok else "MISMATCH", total, log_only, beyond))
   except Exception as e:
       print(">>> RESULT=SLICE ERROR :: %s :: %s" % (type(e).__name__, 
str(e)[:200]))
   
   # ---- item (3): delete log block (write.operation=delete) ----
   try:
       p = root + "/del"
       wr([("a", 1, "p1", 10), ("b", 1, "p1", 20), ("c", 1, "p1", 30), ("d", 1, 
"p1", 1000)],
          base_schema, opts(), "insert", "overwrite", p)
       wr([("d", 2, "p1", 1000)], base_schema, opts(), "delete", "append", p)
       total, hi = rd(p, "1=1"), rd(p, "fare >= 900")
       print(">>> DELETE_BLOCK col_stats:"); dump_cs(p)
       print(">>> RESULT=DELETE_BLOCK %s total=%d fare>=900=%d (expect 3, 0 -> 
d deleted, no stale read)"
             % ("OK" if (total == 3 and hi == 0) else "MISMATCH", total, hi))
   except Exception as e:
       print(">>> RESULT=DELETE_BLOCK ERROR :: %s :: %s" % (type(e).__name__, 
str(e)[:200]))
   
   # ---- item (4): custom delete payload (soft delete via _hoodie_is_deleted) 
----
   try:
       p = root + "/custdel"
       o = opts({"hoodie.datasource.write.payload.class": 
"org.apache.hudi.common.model.DefaultHoodieRecordPayload"})
       wr([("a", 1, "p1", 10, False), ("b", 1, "p1", 20, False), ("c", 1, "p1", 
30, False), ("d", 1, "p1", 1000, False)],
          del_schema, o, "insert", "overwrite", p)
       wr([("d", 2, "p1", 1000, True)], del_schema, o, "upsert", "append", p)
       total, hi = rd(p, "1=1"), rd(p, "fare >= 900")
       print(">>> CUSTOM_PAYLOAD col_stats:"); dump_cs(p)
       print(">>> RESULT=CUSTOM_PAYLOAD %s total=%d fare>=900=%d (expect 3, 0 
-> d soft-deleted via payload)"
             % ("OK" if (total == 3 and hi == 0) else "MISMATCH", total, hi))
   except Exception as e:
       print(">>> RESULT=CUSTOM_PAYLOAD ERROR :: %s :: %s" % (type(e).__name__, 
str(e)[:200]))
   
   spark.stop()
   ```
   
   **Output:**
   
   ```sh
   >>> SLICE col_stats (one row per data file in the slice):
   
+-------------------------------------------------------------------------+--------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   |file                                                                     
|minV                                                                           
 |maxV                                                                          
  |nulls|del  |
   
+-------------------------------------------------------------------------+--------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   |.ce0f7c3c-03a4-460f-9993-c18c61e238f1-0_20260706065436335.log.1_0-68-161 
|{null, null, {1000}, null, null, null, null, null, null, null, null, null, 
null}|{null, null, {1000}, null, null, null, null, null, null, null, null, 
null, null}|0    |false|
   
|ce0f7c3c-03a4-460f-9993-c18c61e238f1-0_0-44-101_20260706065429746.parquet|{null,
 null, {10}, null, null, null, null, null, null, null, null, null, null}  
|{null, null, {30}, null, null, null, null, null, null, null, null, null, null} 
 |0    |false|
   
+-------------------------------------------------------------------------+--------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   
   >>> RESULT=SLICE OK total=3 fare>=900=1 fare>5000=0 (expect 3, 1 [log value 
survives], 0)
   
   >>> DELETE_BLOCK col_stats:
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   |file                                                                      
|minV                                                                          
|maxV                                                                           
 |nulls|del  |
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   
|a0e304d1-053f-40f3-a18f-8524a2bdd29e-0_0-122-261_20260706065442514.parquet|{null,
 null, {10}, null, null, null, null, null, null, null, null, null, null}|{null, 
null, {1000}, null, null, null, null, null, null, null, null, null, null}|0    
|false|
   |.a0e304d1-053f-40f3-a18f-8524a2bdd29e-0_20260706065445618.log.1_0-150-322 
|null                                                                          
|null                                                                           
 |0    |false|
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   
   >>> CUSTOM_PAYLOAD col_stats:
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   |file                                                                      
|minV                                                                          
|maxV                                                                           
 |nulls|del  |
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   
|cc6dedf6-dd0e-402a-94e5-10c538641e32-0_0-198-417_20260706065449261.parquet|{null,
 null, {10}, null, null, null, null, null, null, null, null, null, null}|{null, 
null, {1000}, null, null, null, null, null, null, null, null, null, null}|0    
|false|
   |.cc6dedf6-dd0e-402a-94e5-10c538641e32-0_20260706065452291.log.1_0-222-477 
|null                                                                          
|null                                                                           
 |0    |false|
   
+--------------------------------------------------------------------------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------+-----+-----+
   
   >>> RESULT=CUSTOM_PAYLOAD OK total=3 fare>=900=0 (expect 3, 0 -> d 
soft-deleted via payload)
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to