parquet_tables_rowmutate Submodule

Mutation that changes a parquet_table's ROW SET: %filter_rows, %sort_by, %top_n, %delete_rows, %truncate, %append and %append_null_rows.

Everything in this file detaches the table when it actually changes the row set (F-mut-7), and that is why it is a separate file from parquet_tables_mutate.f90. Once the row set has changed, a column still sitting in the file can never be lined up with the columns already in memory again -- so rather than hand back silently misaligned data later, the table records that it has left its file behind and every later read from that file is a named error.

A call that changes nothing changes nothing at all -- it does not touch a column, does not invalidate a %col pointer, and does not detach. Detaching costs the caller their file, so it is only ever paid for when something actually required it. Five calls reach this file with nothing to do, all decided by the caller's own arguments (%truncate(n) with n >= %nrows(), %filter_rows with an all-.true. mask, %delete_rows with no indices, %append of a zero-row table, %append_null_rows(0)), plus one decided by the data (%sort_by whose permutation moves no row, which includes every table of fewer than two rows). Each returns early, AFTER its own validation -- a no-op still rejects a bad argument. %top_n with n >= %nrows() reaches the same place by delegating to %sort_by, which is where its own data-decided no-op comes from.

Two rules hold this together, and a new operation added here must follow both:

  1. Validate everything, THEN mutate everything. A mutation that aborts halfway leaves some columns changed and some not -- a state with no diagnostic and no way back. So every check (mask length, index bounds, key names and kinds, the appended table's column set) runs before the first column is touched.
  2. Detach LAST. If a mutation does fail, the table should still be attached and diagnosable rather than detached and half-changed.

A column that has not been read is skipped, not an obstacle (table_mutable_column). That is the approved policy: requiring every column to be resident first would force a lazy table to read everything it has before it could drop a single row, which is exactly the memory cost laziness and the slice regime exist to avoid. The skipped column is then unreadable for good -- detaching sees to that -- so a caller who wants it must %prefetch it BEFORE mutating, and the detach guard is what says so if they did not.

%append is the one mutation a SHARED table permits, and the split between its two public entry points and the private append_table_worker is what makes that safe: each entry point takes the table's own lock exactly once and then calls the worker, and neither calls the other. An OpenMP simple lock is not recursive, so a second acquisition on one thread deadlocks rather than failing to build -- any future internal caller must come to the worker, never to table_append_table. Everything else in this file is refused outright on a shared table (table_check_not_shared), because it would pull storage out from under another thread.


Uses