Zoltán Borók-Nagy created IMPALA-15373:
------------------------------------------

             Summary: Wrong FILE__POSITION when the first materialized column 
is complex and the file has multiple row groups
                 Key: IMPALA-15373
                 URL: https://issues.apache.org/jira/browse/IMPALA-15373
             Project: IMPALA
          Issue Type: Bug
          Components: Backend
            Reporter: Zoltán Borók-Nagy


FILE__POSITION is row-group-relative instead of file-relative whenever the 
Parquet column reader that fills the slot is a complex reader (collection, 
struct or VARIANT) and the data file has more than one row group. Rows in the 
second and later row groups get positions that are too small by the row-group 
offset.

This is a regression introduced by IMPALA-11780.

h2. Root cause

IMPALA-11780 made {{current_row_}} row-group-relative and compensated for it 
where the file position is produced:

{code:java}
// parquet-column-readers.cc:1250, BaseScalarColumnReader::Reset()
-  current_row_ = row_group_first_row - 1;
+  row_group_first_row_ = row_group_first_row;
+  current_row_ = -1;

// parquet-column-readers.h:738, 
ParquetColumnReader::ReadFilePositionNonBatched()
-  *file_pos = LastProcessedRow() + 1;
+  *file_pos = row_group_first_row_ + LastProcessedRow() + 1;
{code}

{{row_group_first_row_}} (parquet-column-readers.h:235) is a member of the
{{ParquetColumnReader}} base, but it is assigned in exactly one place:
{{BaseScalarColumnReader::Reset()}} (parquet-column-readers.cc:1250).
{{ComplexColumnReader::Reset()}} (parquet-complex-column-reader.h:53-57) does 
not set it, and
{{HdfsParquetScanner::InitComplexColumns()}} (hdfs-parquet-scanner.cc:1005, 
:3094) takes no
row_group_first_row argument. A complex reader therefore keeps the default 0 
forever.

{{ReadFilePositionNonBatched()}} is called on {{this}} from
{{StructColumnReader::ReadValueBatch()}} and 
{{CollectionColumnReader::ReadValueBatch()}}, so
for a complex reader the compensation term is always 0. Before IMPALA-11780 
this path was
correct, because {{LastProcessedRow()}} delegates to {{children_[0]}}, whose 
{{current_row_}}
was absolute at the time.

The file-position slot is attached to {{(*column_readers)[0]}} regardless of 
reader kind
(hdfs-parquet-scanner.cc:3021-3024). Slot order is analysis/registration order 
(FROM-clause
refs, then the select list left to right, then WHERE), so the first reader is a 
complex one
for queries such as {{select file__position, v from t}}, {{select v from t 
where id = 5}} or
{{select ... from t c, c.c_orders}}.

h2. The repo already contains the wrong values

{{QueryTest/virtual-column-file-position-parquet.test:264-279}} has goldens for
{{customer_nested_multiblock_multipage}} (300 rows in 3 row groups of 100). The 
only
top-level reader in those queries is the {{c_orders}} CollectionColumnReader, 
so it owns the file-position slot.

|| query || golden || true file position ||
| l_shipdate='1998-11-26' | 80 | 280 |
| l_partkey = 199994 | 51, 82 | 151, 282 |

The true positions were read back from
{{testdata/data/customer_nested_multiblock_multipage.parquet}} with pyarrow. 
The goldens are exactly the true values minus the row-group offsets (100, 200, 
200). They were added by
c56cd7b214 itself, so the test has been locking in the bug since 4.3.0.

h2. Impact

* Wrong FILE__POSITION values returned to the user.
* Wrong application of Iceberg V2 position deletes / V3 deletion vectors: 
IcebergScanPlanner
  adds FILE__POSITION to the data scan tuple and IcebergDeleteNode probes with 
it. Deletes
  aimed at row group 1 positions are also applied at the same relative offsets 
in later row
  groups, and deletes aimed at later row groups are missed.
* Worst case, persistent corruption: a DELETE whose predicate only touches a 
complex or
  VARIANT column materializes only that column, so the complex reader owns the 
slot and
  *wrong positions are written into the new position-delete file / DV*.

Only files with more than one row group are affected. Impala writes one row 
group per file,
so this needs data written by Spark, Hive, Trino or Flink (Iceberg defaults: 
512 MB target
file, 128 MB row group, i.e. up to ~4 row groups per file).

h2. Suggested fix

Give complex readers the offset, e.g.:

{code:java}
// parquet-complex-column-reader.h
void Reset(int64_t row_group_first_row) {
  def_level_ = rep_level_ = ParquetLevel::INVALID_LEVEL;
  pos_current_value_ = ParquetLevel::INVALID_POS;
  row_group_first_row_ = row_group_first_row;
}
// hdfs-parquet-scanner.{h,cc}: InitComplexColumns(int64_t 
row_group_first_row), passed at :1005
{code}

{{complex_readers_}} already holds every complex reader, including nested ones 
and the
collection built by {{CreateCountingReader()}}, via the {{PartitionReaders()}} 
recursion.
An alternative is to take the offset from {{children_[0]}} inside
{{ReadFilePositionNonBatched()}}. Note that {{LastProcessedRow()}} must stay 
row-group-relative
either way: it feeds {{skip_row_id}} (hdfs-parquet-scanner.cc:2442) and the 
page-index
arithmetic in {{SkipRowsInternal()}}.

The fix must also correct the goldens in 
virtual-column-file-position-parquet.test:268 and
:275-276 to 280 and 151, 282, and state in the commit message that the old 
values were wrong.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to