parquet_tables.f90 Source File


Source Code

!===========================================
! Author: Elmo Tempel (elmo.tempel@ut.ee)
!===========================================
!
! GENERATED FILE -- DO NOT EDIT BY HAND.
! Regenerate with:  tools/generate_parquet_tables.py
! The kind table lives in tools/generate_parquet_columns.py; edit it there, not here.
!
!> A whole parquet file as one in-memory table: `parquet_table`.
!!
!! `parquet_table` sits on top of the `parquet` reader/writer rather than replacing it. Opening
!! one reads the file's SCHEMA and nothing else; each column's values are read into the
!! type-erased `parquet_column` store (`parquet_columns`) the first time something asks for them,
!! and the Arrow-side buffers are freed as soon as that copy exists, so the table owns the sole
!! Fortran copy of each column it holds. From there a column is reached either by a zero-copy
!! typed pointer (`%col`, exact kind) or by a widening copy (`%get`), a table can be built from
!! scratch in memory (`parquet_new_table` + `%add_column`), and the whole thing is written back
!! out through an ordinary `parquet_schema` (`parquet_write_table`).
!!
!! Five things are worth knowing before using it:
!!
!! * **`%get` is the friendly path; `%col` is the fast one.** `%get` copies the column into an
!!   allocatable array of the caller's own kind, widening int32 -> int64 and float32 -> float64
!!   on the way, so a caller who just wants the numbers never has to ask what type the file used.
!!   `%col` hands back a pointer straight into the store -- zero copy, writable -- but the pointer
!!   kind must match the stored kind EXACTLY, so it is for code that already knows the type (or
!!   has asked `%kind`).
!! * **Reads happen on first touch.** `%nrows`/`%kind`/`%width`/`%column_names` answer from the
!!   schema and read nothing; a value access reads that column, whole, across the table's row
!!   scope. `%residency` reports what is held, `%prefetch`/`%materialize_all` read ahead of time,
!!   and `%reload` goes back to the file.
!! * **A table can cover part of a file.** `parquet_open_table(t, file, row_lo, row_hi)` reads
!!   only the row groups covering that range, which is how a file bigger than memory is worked
!!   through and how a parallel program gives each thread its own share.
!! * **Assignment is blocked.** The column store lives behind a pointer, so `b = a` would leave two
!!   tables sharing (and later double-freeing) one store. `b = a` is a hard error rather than a
!!   silent corruption; copying a table comes with `%clone` in a later milestone.
!! * **Concurrency is enforced, not merely documented.** Reading an already-resident column is
!!   free -- no lock, no atomic, any number of threads -- and %append into a shared table is
!!   serialised by the table's own lock, so a parallel producer needs no !$omp critical. Every
!!   other change to a table another thread may be using is a hard error naming what to do
!!   instead: a lazy first touch, any structural change, nulling a column whose validity storage
!!   does not exist yet, and any write to a string column (whose rows share one packed store). A
!!   table a thread opened ITSELF inside the region is thread-private and exempt from all of them.
!!   See parquet_tables_parallel.f90 and doc/pages/operating/thread-safety.md.
!!
!! Depends on `parquet_columns` (the value store) and `parquet_core` (the reader/writer it drives).
module parquet_tables
    use, intrinsic :: iso_fortran_env, only : int32, int64, real32, real64
#ifdef _OPENMP
    ! Only omp_lock_kind is needed at module scope, for parquet_table_cache's own lock component.
    ! The procedures that operate on it import omp_lib themselves, exactly as unsafe_first_touch
    ! and record_open_thread already do.
    use omp_lib, only : omp_lock_kind
#endif
    use parquet_columns
    use parquet_strings, only : parquet_string_column
    use parquet_temporal, only : parquet_date, parquet_time, parquet_timestamp
    use parquet_core, only : parquet_reader, parquet_writer, parquet_schema, &
        parquet_open_reader, parquet_close_reader, parquet_get_nrows, parquet_get_col_size, &
        parquet_get_column_names, parquet_get_column_type, parquet_column_exists, &
        parquet_release_column, parquet_read_column, parquet_get_string_length, &
        parquet_get_num_row_groups, parquet_get_chunk_size, parquet_read_column_chunk, &
        parquet_open_writer, parquet_write_column, parquet_close_writer, parquet_write_row_mask, &
        parquet_measure_list_width, parquet_column_width_needs_data, parquet_column_has_nulls, &
        parquet_load_qc_maml_file, parquet_filter, parquet_sortkey, parquet_read_qc, &
        parquet_compose_read_qc, parquet_reader_set_filter, parquet_parse_maml, &
        parquet_get_metadata_items, parquet_get_qc_columns, parquet_get_physical_row_indices, &
        parquet_get_column_time_info, parquet_size_auto, parquet_reader_adopt_transform, &
        parquet_unit_millis, parquet_unit_micros, parquet_unit_nanos, &
        parquet_split_name_list, parquet_parse_sort_key
    ! The table layer's two solicited printers (%print_stat) and its own warnings go through the
    ! library's output channels rather than printing directly, so verbosity/message_stream apply
    ! here as everywhere -- see tools/check_source_conventions.py's `no direct printing` check.
    use parquet_settings, only : parquet_emit_warning, parquet_output_is_suppressed, &
        parquet_get_arrow_version
    ! The one binding this layer reaches for directly, and it needs no reader: the seed for a
    ! sample_fraction= open is settled BEFORE the table's reader is created, so that reader and
    ! every later one (a clone's, a per-thread one) draw the identical rows. It stays out of the
    ! `use parquet` namespace via this module's default-private accessibility.
    use parquet_random, only : pf_random_seed
    !
    implicit none
    private
    !
    public :: parquet_table
    public :: parquet_table_row
    public :: parquet_table_col
    public :: parquet_slice
    public :: parquet_slice_range
    public :: parquet_slice_list
    public :: parquet_open_table
    public :: parquet_new_table
    public :: parquet_write_table
    public :: parquet_table_row_group_bounds
    !> Re-exported from parquet_settings so that a `use parquet_tables` program can report
    !! which Arrow/Parquet C++ it is linked against without a second import. The library's
    !! OWN version is not re-exported here -- that is `parquet_get_version`, in the leaf
    !! module parquet_version, carried only by the `parquet` facade.
    public :: parquet_get_arrow_version
    public :: PARQUET_ROW_INDEX
    ! REGIME_FULL/REGIME_SLICE are deliberately NOT public. `regime` is a private component of
    ! parquet_table and no binding exposes it, so a caller could never obtain a value to compare
    ! against either constant -- publishing them advertised a distinction the public API cannot
    ! express. The submodules that assign `regime` reach both by host association, which is why
    ! nothing inside the tier changes. Row 30's code issue 1.
    public :: RES_EMPTY, RES_PARTIAL, RES_FULL
    !> TEST-ONLY debug hook; deliberately NOT in README.md's API overview. See its own
    !! doc-comment for why it has to be public at all.
    public :: parquet_debug_table_set_inflight
    public :: parquet_debug_colread_block_rows
    public :: parquet_debug_table_drop_name_index
    !
    !> Error-message prefix for every `error stop` raised by this module.
    character(len=*), parameter :: EP = "parquet_table: "
    !
    !> Spare descriptor slots allocated beyond the file's own column count, so the common
    !! "open a file, add a few computed columns" case never reallocates `cols(:)` (RF16). This
    !! is an optimization only -- the documented contract stays the broad one: ANY column- or
    !! row-structural mutation invalidates every outstanding pointer into the table.
    integer, parameter :: COL_HEADROOM = 8
    !
    ! ---- Row-scope regimes (D14) ----
    integer, parameter :: REGIME_FULL = 0  !! the table covers every row of the file.
    integer, parameter :: REGIME_SLICE = 1 !! the table covers one contiguous row range of the file.
    !
    !> Opens a file-backed table, over the whole file or over one contiguous row slice.
    !!
    !! Given `row_lo`/`row_hi` (1-based, inclusive, in either integer kind) the table covers only
    !! those rows, and reads only the row groups covering them -- this is how a file bigger than
    !! memory is worked through, and how a parallel program gives each thread its own share
    !! (`parquet_table_row_group_bounds` reports where the natural boundaries are). Row indices
    !! everywhere else, `%row(i)` included, are then relative to the slice, not to the file.
    !!
    !! A slice accepts the same read-time transform the whole-file form does, with ONE exception:
    !! there is no `sort` argument on the slice forms at all. A sort reorders rows across the whole
    !! file, so "the 1000th row" would no longer name anything a slice could be cut along -- and
    !! omitting the argument makes that a compile error rather than a runtime one. (A `maml=` whose
    !! `extra: sort:` list is non-empty is the same rejection, necessarily at runtime.)
    !!
    !! **A filtered or sampled slice does not have `row_hi - row_lo + 1` rows.** `%nrows()` is the
    !! number of rows of `[row_lo, row_hi]` that survive the transform, and every row index the
    !! table takes or reports counts those survivors -- row 1 is the first surviving row, not file
    !! row `row_lo`. Without a filter and without `sample_fraction=` nothing changes: the slice is
    !! trimmed out of the covering row groups in memory, exactly as it always was.
    interface parquet_open_table
        module procedure open_table_full
        module procedure open_table_slice_i32
        module procedure open_table_slice_i64
    end interface parquet_open_table
    !
    !> The name of the automatic column holding each row's PHYSICAL row number in the source
    !! parquet file. Declared as a constant so a program can name it without hard-coding the
    !! string, and so a collision test has something to compare against.
    character(len=*), parameter :: PARQUET_ROW_INDEX = "parquet_row_index"
    !
    ! ---- Per-column work a row-structural mutation hands to `table_colwork` ----
    ! Private: an op code is how one parallel region serves three operations without an abstract
    ! interface in this spec and an indirect call inside the region. One branch per column.
    integer, parameter :: PCW_REINDEX_TRUSTED = 1 !! %reindex_trusted(rows) -- the sort's replay.
    integer, parameter :: PCW_DELETE_MASK = 2     !! %delete_by_mask(keep) -- filter/delete/truncate.
    integer, parameter :: PCW_GATHER = 3          !! %gather(rows) -- %top_n's selection.
    private :: PCW_REINDEX_TRUSTED, PCW_DELETE_MASK, PCW_GATHER
    !
    ! ---- Column residency (D14/RF20) ----
    integer, parameter :: RES_EMPTY = 0   !! no values held (never read, or an unsupported type).
    integer, parameter :: RES_PARTIAL = 1 !! reserved: some row groups resident (a later milestone).
    integer, parameter :: RES_FULL = 2    !! the whole column, across the table's row scope, is held.
    !
    !> One column slot: its identity, its shape, its provenance, and the values themselves.
    !!
    !! `declared_kind`, `width` and `supported` are settled at OPEN time, from the file schema
    !! alone -- they must be answerable before any data is read, because `%kind` is how a caller
    !! decides which `%col` specific to call in the first place. `values` stays empty until the
    !! column is first touched.
    type :: parquet_table_column
        character(len=:), allocatable :: name      !! internal/logical name -- ALWAYS the lookup key.
        character(len=:), allocatable :: file_name !! physical name in the file (== name for now).
        integer :: declared_kind = PK_NONE         !! PK_* this slot holds, or PK_NONE if unsupported.
        integer :: width = 1                       !! values per row: 1 scalar, col_size for a *_VEC.
        !> .true. while this column's kind and width are still UNKNOWN, which happens for exactly
        !! one column type: a plain LIST/LARGE_LIST from a foreign writer, whose per-row width is
        !! a property of the data rather than the schema. While set, `declared_kind` is PK_NONE and
        !! `width` is 0, and anything that needs either must call table_resolve_width first.
        !! Deferring this is what keeps parquet_open_table schema-only -- measuring at open meant
        !! decoding every such column just to classify it.
        logical :: width_pending = .false.
        !> .true. while a `%cast` on a column nothing has read yet is still waiting for the read
        !! that will carry it out. `%cast` on a non-resident file-backed column only rewrites
        !! `declared_kind` and sets this, so the first touch decodes STRAIGHT into the target
        !! kind -- one pass instead of read-then-convert, using the reader's own numeric
        !! conversions. Cleared by `table_materialize`, and by anything that gives the slot values
        !! some other way. A second `%cast` while it is set materializes first, so that a chain of
        !! casts cannot silently forget the intermediate one (see `table_cast`).
        logical :: cast_pending = .false.
        !> The TIME/TIMESTAMP resolution this column is stored at in the file, as a parquet_unit_*
        !! selector, or 0 for every other kind (and for a column with no file behind it). Recorded
        !! at classification time because it is recoverable from nowhere else afterwards: a
        !! `parquet_timestamp` holds seconds+nanoseconds and carries no unit of its own, so once
        !! the reader is gone the table would have no way to know it read a `timestamp[ns]` column
        !! rather than a `timestamp[us]` one. A schema-less write needs it to declare the matching
        !! data_type token -- without it the writer defaults to microseconds and a nanosecond
        !! column fails the write outright (`to_unix` aborts rather than truncating).
        integer :: time_unit = 0
        !> .true. when this column is a TIMESTAMP stored with a timezone (the MAML `,utc` token).
        !! Recorded and used for the same reason as `time_unit`.
        logical :: time_utc = .false.
        !> Unit declared for this column by the read-in MAML (`fields:`' `unit:` key), looked up
        !! by the column's FILE name at open. Unallocated when no MAML was given, or when it
        !! declares nothing for this column. It is held here rather than only on `values` because
        !! a lazy column has no values yet, and %unit must answer for a column nothing has read;
        !! table_materialize copies it onto the values as they arrive, so everything downstream
        !! (%append's unit check, %clone, the writer) sees one unit rather than two.
        character(len=:), allocatable :: unit
        logical :: file_source = .false.           !! .true. iff a backing file column exists.
        logical :: predefined = .false.            !! reserved: a generated accessor exists for it.
        logical :: user_populated = .false.        !! .true. once user values were written into it.
        logical :: supported = .true.              !! .false. for a foreign/container column type.
        integer :: residency = RES_EMPTY           !! RES_EMPTY or RES_FULL; RES_PARTIAL reserved.
        logical, allocatable :: rg_loaded(:)       !! reserved for per-row-group residency.
        type(parquet_column) :: values             !! the value store (parquet_columns).
    end type parquet_table_column
    !
    !> Everything a read may have to mutate, held behind ONE pointer so that read accessors can
    !! stay `intent(in)`: allocating through a pointer's target is allowed on an `intent(in)`
    !! dummy, whereas allocating an `allocatable` component of one is not. This is also what
    !! keeps `%col`'s returned pointer valid without the caller declaring the table `target` --
    !! see the module doc and `col_ptr_*`.
    !!
    !! The source file's identity lives here rather than on `parquet_table` for the same reason
    !! the reader does: a first touch, and the error message it may raise, must be reachable from
    !! the cache alone, because that is all a `parquet_table_row` handle holds.
    type :: parquet_table_cache
        type(parquet_table_column), allocatable :: cols(:) !! descriptor slots; `ncols` are live.
        integer :: ncols = 0                               !! live slot count (cols may be longer).
        !> The live slots' indices ordered by column NAME, so `cache_find` can bisect instead of
        !! scanning; the ordering covers `cols(1:ncols)`. Every value accessor resolves a name
        !! through `cache_find`, so on a wide table that scan is most of the cost of a
        !! `%get_element` call.
        !!
        !! **Maintained EAGERLY, by the mutations that change the column set** -- never rebuilt
        !! lazily inside a lookup. `cache_find` takes the cache `intent(in)` precisely so that
        !! concurrent readers of a shared table need no synchronisation to read it (see
        !! `readers_active`); a lookup that rebuilt this would be a write on that path, and two
        !! threads reallocating one array is heap corruption rather than a stale answer.
        !!
        !! **A stale or absent index can never produce a wrong answer**, only a slow one:
        !! `cache_find` re-checks the name at the slot it lands on and falls back to the linear
        !! scan if it does not match. That is what makes a forgotten rebuild site a performance
        !! bug a benchmark catches, rather than a silently wrong column.
        integer, allocatable :: name_order(:)
        !> `name_order`'s entries' sort keys: the first 7 bytes of each name, packed big-endian
        !! into an integer and blank-padded, so the bisection above compares INTEGERS out of one
        !! contiguous array instead of chasing a deferred-length `character` allocation per probe.
        !! That is the difference between beating the linear scan and merely matching it: the scan
        !! walks `cols` in order and prefetches well, while a bisection over the names alone jumps
        !! about and misses. Seven bytes rather than eight keeps every key positive, so a plain
        !! signed comparison orders them; names sharing a 7-byte prefix tie, and the search falls
        !! back to comparing the names themselves for those.
        integer(int64), allocatable :: name_key(:)
        type(parquet_reader), allocatable :: reader        !! present iff the table is file-backed.
        logical :: reads_started = .false.                 !! .true. once any column has been read.
        !> Bumped by every structural change, so a caller can tell whether a pointer it holds may
        !! have been invalidated (%generation). On the CACHE rather than on `parquet_table`, like
        !! all other table state -- see this type's own note above.
        integer(int64) :: generation = 0_int64
        logical :: file_backed = .false.                   !! .true. if opened from a parquet file.
        character(len=:), allocatable :: source_file       !! the file this table was opened from.
        ! --- the source file's key/value metadata, copied ONCE at open.
        !
        !     Snapshotted rather than read through `reader` on demand, because `reader` does not
        !     survive a row mutation (table_detach releases it) and a file's metadata is a
        !     property of where the rows came from, which detaching does not change. Both arrays
        !     are allocated for every file-backed open, zero-size when the file carries no
        !     metadata at all -- so `allocated(meta_keys)` answers "did this table come from a
        !     file", which is the question %get_file_metadata has to ask once file_backed can no
        !     longer be trusted to mean it.
        character(len=:), allocatable :: meta_keys(:)      !! metadata keys, blank-padded; file order.
        character(len=:), allocatable :: meta_values(:)    !! the matching values, same order.
        ! --- row-group geometry, slice regime only. TWO coordinate systems, and which one a given
        !     array is in is the whole reason there are two of them:
        !
        !     `rg_bounds` is always in the coordinates `row_lo`/`row_hi`/`%row(i)` use, i.e. the
        !     TABLE's own row numbering, because that is what materialize_slice and
        !     resolve_width_row_groups compare their scope against. On an unfiltered slice those
        !     ARE the file's rows, so it holds physical bounds and `rg_bounds_physical` stays
        !     unallocated. On a filtered or sampled slice the table counts survivors instead, so
        !     `rg_bounds` holds each row group's surviving rows and the file's own numbering is
        !     kept separately -- captured before the mask is attached, since afterwards the reader
        !     reports survivors and the physical numbering is simply no longer askable.
        integer(int64), allocatable :: rg_bounds(:,:)      !! (2, nrg) row-group ranges, TABLE rows.
        integer(int64), allocatable :: rg_bounds_physical(:,:) !! the same, FILE rows; masked slice only.
        ! --- physical row geometry, captured at open so both survive a detach. `%nrows()` counts
        !     what the table HOLDS; these two count what it came from, which no other query can
        !     answer once a filter is active (the reader then reports survivors) or once the
        !     table has cut its file loose.
        !> .true. once the automatic parquet_row_index column has been asked for and given a real
        !! slot. Until then the column is VIRTUAL: %has_column answers for it, but it occupies no
        !! slot, is not listed by %column_names or counted by %ncols, and costs nothing -- 8 bytes
        !! a row is 8 GB at a billion rows, so materializing it at open would undo the laziness
        !! the whole type is built on.
        logical :: row_index_live = .false.
        !> .true. when the source file has its own column called parquet_row_index that a read-in
        !! MAML did NOT remap. That column is unreachable (the reserved name resolves to the
        !! automatic one), which parquet_open_table warns about at open.
        logical :: row_index_shadowed = .false.
        integer(int64) :: unfiltered_rows = 0              !! rows before filter/sample: the slice's length, or the file's.
        integer(int64) :: rg_extent_rows = 0               !! summed length of the row groups this table covers.
        integer(int64) :: slice_row_lo = 0                 !! slice's first FILE row (0 = not a slice).
        integer(int64) :: slice_row_hi = 0                 !! slice's last FILE row (0 = not a slice).
        logical :: opened_in_parallel = .false.            !! .true. if opened inside a parallel region.
        integer :: owner_thread = -1                       !! OpenMP thread that opened it (-1 if serial).
        ! --- the table's own lock, and the two counters the read/append guards read ---
        !     The lock serialises %append into a SHARED table, so a parallel producer region needs
        !     no !$omp critical of its own and cannot wrap the wrong statement. It is an OpenMP
        !     SIMPLE lock, which is not recursive: `table_append_table` is the only procedure that
        !     may take it, and every internal caller goes to `append_table_worker` instead (see its
        !     own doc-comment). Taking it twice on one thread deadlocks rather than failing to
        !     build.
        !
        !     A lock is a HANDLE, not a value: %clone must initialise a fresh one rather than copy
        !     the source's, and `table_finalize` must destroy it exactly once.
#ifdef _OPENMP
        integer(omp_lock_kind) :: lock                     !! serialises %append on a shared table.
#endif
        logical :: lock_ready = .false.                    !! .true. between omp_init_lock and omp_destroy_lock.
        !> Nonzero while some thread is inside %append. Every table READ entry point checks it and
        !! aborts, because the parallel append region is append-only: a reader inside a value array
        !! while the appender reallocates it is reading freed memory. Read/written with
        !! `!$omp atomic`, and deliberately an integer rather than a logical so the atomic update
        !! is an increment (nested/overlapping appends stay correct without a second flag).
        integer :: append_active = 0
        !> Long reads currently in flight -- the ones that would still be reading storage when a
        !! concurrent %append reallocated it. %append checks this under the lock and aborts
        !! rather than reallocating underneath one.
        !!
        !! **Maintained around exactly TWO windows, and neither is "a coarse accessor":** the lazy
        !! FIRST TOUCH inside table_resolve_slot -- whichever accessor triggered it, %get and
        !! %get_element alike -- and %prefetch/%materialize_all's bulk materialize
        !! (materialize_marked). Both read from the file and take real time.
        !!
        !! **No accessor of any kind maintains it for a column that is already RESIDENT.** Two
        !! atomics per cell would dominate a %get_element loop over a large column, and a resident
        !! read is a memcpy over memory no %append can be reallocating -- the cheap
        !! `append_active` check above already covers that shorter window, and every accessor
        !! takes it, resident or not. Do not "fix" that asymmetry.
        integer :: readers_active = 0
        ! --- read-time transform, composed ONCE at parquet_open_table time and retained only so
        !     %clone can reattach the same one when it reopens the file. Already translated to
        !     FILE names and already merged with whatever the read-in MAML declared, so nothing
        !     downstream has to redo either step. Each stays unallocated when nothing was supplied,
        !     which is how a clone tells "nothing to reattach" from "an empty filter was composed".
        !
        !     THESE BELONG HERE, NOT ON parquet_table ITSELF, and that is not a filing decision:
        !     `parquet_table` is deliberately five scalars and one pointer, with NO allocatable
        !     components at all, so its intent(out) entry and its FINAL do no recursive walk over
        !     nested derived types. Hanging a `type(parquet_schema), allocatable` off it (maml +
        !     cinfo + metadata, each holding allocatable arrays of derived types with their own
        !     allocatable components) makes every `parquet_open_table` entry perform exactly such a
        !     walk -- and this project already has two confirmed compiler bugs in that machinery on
        !     this very type (an OpenMP private() copy left uninitialized, and %detached surviving
        !     an intent(out) reset; see parquet_tables_lifecycle.f90 and CLAUDE.md). Doing it
        !     anyway segfaulted ifx inside the RTL's own recursive descriptor walker, on a
        !     block-local table opened inside a parallel region. Keep new transform state here.
        type(parquet_filter), allocatable :: read_filter    !! composed row filter, file names.
        type(parquet_sortkey), allocatable :: read_sort     !! composed sort keys, file names.
        type(parquet_schema), allocatable :: read_qc_schema !! merged qc schema, file names.
        logical :: read_qc_soft = .false.                   !! qc_soft as given at open.
        real(real64), allocatable :: read_sample_fraction   !! sample_fraction as given at open.
        !> The seed every reader this table opens will sample with. Allocated and POSITIVE whenever
        !! `read_sample_fraction` is allocated -- an unseeded open settles one at open time
        !! (`pf_random_seed`) rather than leaving each reader to draw its own. That invariant is
        !! what makes a `%clone`, a reopen and a per-thread reader all keep the same rows; before
        !! it existed, an unseeded clone redrew and silently held a different sample.
        integer(int64), allocatable :: read_sample_seed
    end type parquet_table_cache
    !
    !> Which rows to pick out of a column: `1:`, `1:10`, `1:10:2` or an explicit list.
    !!
    !! Fortran cannot overload `t%col('x')(1:10:2)` on an arbitrary column expression, so the
    !! selection has to be an object the copy path can be handed. Build one with
    !! `parquet_slice_range` or `parquet_slice_list` and pass it to `%get_slice`.
    !!
    !! The rows it selects are rows of THIS TABLE -- in the slice regime, index 1 is the table's
    !! first row, not the file's. That is a different thing from the table-level slice regime,
    !! which decides how much of the file the table covers in the first place.
    type :: parquet_slice
        private
        logical :: strided = .true.       !! .true. for start:stop:step, .false. for a list.
        integer(int64) :: start = 1       !! first row (strided form).
        integer(int64) :: stop = -1       !! last row, or -1 meaning "to the end" (strided form).
        integer(int64) :: step = 1        !! stride, may be negative, never 0 (strided form).
        integer(int64), allocatable :: indices(:) !! explicit row list (list form).
    end type parquet_slice
    !
    !> Builds a `start:stop:step` slice. `stop` defaults to the table's last row (resolved when
    !! the slice is USED, not when it is built, so one slice object can outlive a row count),
    !! `step` to 1. A negative step counts down; a zero step is an error.
    interface parquet_slice_range
        module procedure slice_range_i32
        module procedure slice_range_i64
    end interface parquet_slice_range
    !
    !> Builds a slice from an explicit list of 1-based row indices, in the order given --
    !! repeats and non-monotone order are both allowed, since this is a gather, not a range.
    interface parquet_slice_list
        module procedure slice_list_i32
        module procedure slice_list_i64
    end interface parquet_slice_list
    !
    !> The table-level state a first touch needs, grouped so it can be passed as one argument:
    !! which rows the table covers, and whether it still has a file to read them from.
    !!
    !! It exists because a first touch has two callers with nothing else in common -- the table
    !! itself, and a `parquet_table_row` handle, which by design holds only the cache pointer
    !! plus by-value copies of exactly these scalars (RF3). Grouping them keeps that contract in
    !! one place instead of five arguments repeated down the call chain.
    type :: table_scope
        integer :: regime = REGIME_FULL   !! REGIME_FULL or REGIME_SLICE.
        integer(int64) :: row_lo = 1      !! first file row this table covers.
        integer(int64) :: row_hi = -1     !! last file row this table covers.
        integer(int64) :: nrows = 0       !! rows the table has (row_hi - row_lo + 1).
        logical :: detached = .false.     !! .true. once a row-structural mutation cut the file loose.
    end type table_scope
    !
    !> A whole table: a column store plus the row scope and provenance describing it.
    !! Declared by the caller (`type(parquet_table) :: t`), filled by `parquet_open_table` or
    !! `parquet_new_table`, and freed automatically when it goes out of scope.
    type :: parquet_table
        private
        logical :: detached = .false.               !! reserved: set by a row-structural mutation.
        integer :: regime = REGIME_FULL             !! REGIME_FULL; REGIME_SLICE reserved.
        integer(int64) :: row_lo = 1                !! first row of the scope (1 in the full regime).
        integer(int64) :: row_hi = -1               !! last row of the scope (nrows in the full regime).
        integer(int64) :: row_count = 0             !! rows every column in this table holds.
        type(parquet_table_cache), pointer :: cache => null() !! the column store (see its own doc).
    contains
        ! --- introspection ---
        procedure :: nrows => table_nrows            !! Number of rows every column holds.
        procedure :: ncols => table_ncols            !! Number of columns the table has.
        procedure :: column_names => table_column_names !! Copy out every column name, in order.
        procedure :: column_index => table_column_index !! A column's 1-based position, 0 when absent.
        procedure :: column_name => table_column_name !! Copy out the name at a 1-based position.
        procedure, private :: column_by_name !! %column specific taking a column name.
        procedure, private :: column_by_index !! %column specific taking a 1-based position.
        !> A resolved handle on one column, by name or by 1-based position. Resolves once -- the
        !! lookup, the lazy first touch and the kind -- so a per-element loop over that column
        !! stops paying for a name lookup on every access. See `parquet_table_col`.
        generic :: column => column_by_name, column_by_index
        procedure, private :: has_nulls_name => table_has_nulls !! %has_nulls specific, by name.
        procedure, private :: has_nulls_at => table_has_nulls_at !! %has_nulls specific, by position.
        !> Whether a column holds (or may hold) nulls -- named, or by 1-based position.
        generic :: has_nulls => has_nulls_name, has_nulls_at
        procedure, private :: table_get_valid_mask     !! %get_valid_mask specific, per-row mask.
        procedure, private :: table_get_valid_mask_elem !! %get_valid_mask specific, per-element mask.
        !> Copy out a column's validity as a plain `logical` array. A rank-1 `mask` gives one entry
        !! per row (on a *_VEC column: "any element of the row is null"); a rank-2 `mask` gives the
        !! true `(width, nrows)` per-element state.
        generic :: get_valid_mask => table_get_valid_mask, table_get_valid_mask_elem
        procedure :: generation => table_generation  !! Counter bumped by every structural change.
        procedure :: has_column => table_has_column  !! Whether a column of this name exists.
        procedure, private :: missing_columns_string !! %missing_columns specific, separated string.
        procedure, private :: missing_columns_array  !! %missing_columns specific, array of names.
        !> Which of these columns the table does NOT have, as a packed array (zero-size when it
        !! has them all). The non-aborting half of %require_columns.
        generic :: missing_columns => missing_columns_string, missing_columns_array
        procedure, private :: require_columns_string !! %require_columns specific, separated string.
        procedure, private :: require_columns_array  !! %require_columns specific, array of names.
        !> Aborts unless the table has every one of these columns, naming EVERY missing one --
        !! not just the first, which is what a hand-written loop reports.
        generic :: require_columns => require_columns_string, require_columns_array
        procedure, private :: kind_name => table_column_kind !! %kind specific, by name.
        procedure, private :: kind_at => table_column_kind_at !! %kind specific, by position.
        !> A column's PK_* kind discriminator -- named, or by 1-based position.
        generic :: kind => kind_name, kind_at
        procedure, private :: width_name => table_column_width !! %width specific, by name.
        procedure, private :: width_at => table_column_width_at !! %width specific, by position.
        !> A column's values-per-row (1 if scalar) -- named, or by 1-based position.
        generic :: width => width_name, width_at
        procedure, private :: unit_name => table_column_unit !! %unit specific, by name.
        procedure, private :: unit_at => table_column_unit_at !! %unit specific, by position.
        !> Copy out a column's unit string -- named, or by 1-based position.
        generic :: unit => unit_name, unit_at
        procedure, private :: residency_name => table_column_residency !! %residency specific, by name.
        procedure, private :: residency_at => table_column_residency_at !! %residency specific, by position.
        !> A column's RES_* residency state -- named, or by 1-based position.
        generic :: residency => residency_name, residency_at
        procedure, private :: is_null_i32 => table_is_null_i32 !! %is_null specific, int32 row index.
        procedure, private :: is_null_i64 => table_is_null_i64 !! %is_null specific, int64 row index.
        procedure, private :: is_null_e32 => table_is_null_e32 !! %is_null specific, int32 row + element.
        procedure, private :: is_null_e64 => table_is_null_e64 !! %is_null specific, int64 row + element.
        procedure, private :: is_null_at_i32 => table_is_null_at_i32 !! %is_null by position, int32 row.
        procedure, private :: is_null_at_i64 => table_is_null_at_i64 !! %is_null by position, int64 row.
        procedure, private :: is_null_at_e32 => table_is_null_at_e32 !! %is_null by position, int32 row + element.
        procedure, private :: is_null_at_e64 => table_is_null_at_e64 !! %is_null by position, int64 row + element.
        !> Whether row `i` of a column is null, or -- given `e` as well -- element `e` of it.
        !!
        !! On a *_VEC column the row form answers "ANY element of the row is null"; the element
        !! form answers about that one element. Defined on a scalar column too, where `e` can only
        !! be 1 and the two agree.
        generic :: is_null => is_null_i32, is_null_i64, is_null_e32, is_null_e64, &
            is_null_at_i32, is_null_at_i64, is_null_at_e32, is_null_at_e64
        procedure :: is_detached => table_is_detached !! Whether the table has left its file behind.
        procedure, private :: is_supported_name => table_is_supported !! %is_supported specific, by name.
        procedure, private :: is_supported_at => table_is_supported_at !! %is_supported specific, by position.
        !> Whether a column's type can be read -- named, or by 1-based position.
        generic :: is_supported => is_supported_name, is_supported_at
        procedure :: filename => table_filename      !! Copy out the file this table came from.
        procedure :: get_file_metadata => table_get_file_metadata !! One key from the file's metadata.
        ! --- residency control ---
        procedure, private :: prefetch_string !! %prefetch specific taking a separated name string.
        procedure, private :: prefetch_array  !! %prefetch specific taking an array of names.
        !> Reads the named column(s) now, instead of on first touch. Required before a parallel
        !! region: a first touch inside one is a hard error, since it would mutate shared state.
        generic :: prefetch => prefetch_string, prefetch_array
        !> The same call as %prefetch, under the name that pairs with %materialize_all. Reaching
        !! for the definitive-sounding %materialize_all when only a few columns are wanted reads
        !! the whole file, silently; %materialize(names) is the one to find first.
        generic :: materialize => prefetch_string, prefetch_array
        procedure :: materialize_all => table_materialize_every !! Read every column not yet read.
        procedure :: reload => table_reload           !! Re-read one column; force= to discard local edits.
        procedure :: evict_column => table_evict_column !! Drop a column's VALUES; force= if it holds local edits.
        procedure :: set_user_populated => table_set_user_populated !! Claim a column's values as the caller's own, or unclaim.
        procedure :: is_user_populated => table_is_user_populated !! Whether a column is claimed as holding the caller's values.
        procedure :: validate_qc => table_validate_qc !! Check every qc-declaring column, holding none.
        procedure :: print_stat => table_print_stat  !! Print what the table holds, to stdout.
        procedure :: nrows_unfiltered => table_nrows_unfiltered !! Rows before filter=/sample_fraction=.
        procedure :: row_group_extent => table_row_group_extent !! Rows in the row groups this table covers.
        procedure :: row_group_bounds => table_row_group_bounds !! Row-group row ranges, this table's rows or the file's.
        ! --- row view ---
        procedure, private :: row_at_i32 !! %row specific taking an int32 index.
        procedure, private :: row_at_i64 !! %row specific taking an int64 index.
        !> A handle on one row, for code that works a row at a time rather than a column at a
        !! time. The index is 1-based within THIS table -- in the slice regime, row 1 is the
        !! slice's first row, not the file's.
        generic :: row => row_at_i32, row_at_i64
        ! --- copy out a row selection ---
        procedure, private :: get_slice_i32 !! %get_slice specific for the i32 kind.
        procedure, private :: get_slice_i64 !! %get_slice specific for the i64 kind.
        procedure, private :: get_slice_f32 !! %get_slice specific for the f32 kind.
        procedure, private :: get_slice_f64 !! %get_slice specific for the f64 kind.
        procedure, private :: get_slice_bool !! %get_slice specific for the bool kind.
        procedure, private :: get_slice_date !! %get_slice specific for the date kind.
        procedure, private :: get_slice_time !! %get_slice specific for the time kind.
        procedure, private :: get_slice_ts !! %get_slice specific for the ts kind.
        procedure, private :: get_slice_i32v !! %get_slice specific for the i32v kind.
        procedure, private :: get_slice_i64v !! %get_slice specific for the i64v kind.
        procedure, private :: get_slice_f32v !! %get_slice specific for the f32v kind.
        procedure, private :: get_slice_f64v !! %get_slice specific for the f64v kind.
        procedure, private :: get_slice_boolv !! %get_slice specific for the boolv kind.
        procedure, private :: get_slice_datev !! %get_slice specific for the datev kind.
        procedure, private :: get_slice_timev !! %get_slice specific for the timev kind.
        procedure, private :: get_slice_tsv !! %get_slice specific for the tsv kind.
        procedure, private :: get_slice_str  !! %get_slice specific returning a parquet_string_column.
        procedure, private :: get_slice_chr  !! %get_slice specific returning a character array.
        procedure, private :: get_slice_chrv !! %get_slice specific returning a character (elem, row) array.
        !> Copies the rows a `parquet_slice` selects into a freshly allocated array of
        !! the caller's own kind, widening on the way exactly as %get does.
        !!
        !! **The selection is taken in the order given, duplicates included.** A
        !! `parquet_slice_list` may name a row more than once and may name rows in any
        !! order, so the result has one entry per SELECTION -- not per distinct row -- and
        !! its order is the selection's, never the table's.
        generic :: get_slice => get_slice_i32, get_slice_i64, get_slice_f32, get_slice_f64, get_slice_bool, get_slice_date, &
            get_slice_time, get_slice_ts, get_slice_i32v, get_slice_i64v, get_slice_f32v, get_slice_f64v, get_slice_boolv, &
            get_slice_datev, get_slice_timev, get_slice_tsv, get_slice_str, get_slice_chr, get_slice_chrv
        ! --- write a row selection back ---
        procedure, private :: set_slice_i32 !! %set_slice specific for the i32 kind.
        procedure, private :: set_slice_i64 !! %set_slice specific for the i64 kind.
        procedure, private :: set_slice_f32 !! %set_slice specific for the f32 kind.
        procedure, private :: set_slice_f64 !! %set_slice specific for the f64 kind.
        procedure, private :: set_slice_bool !! %set_slice specific for the bool kind.
        procedure, private :: set_slice_date !! %set_slice specific for the date kind.
        procedure, private :: set_slice_time !! %set_slice specific for the time kind.
        procedure, private :: set_slice_ts !! %set_slice specific for the ts kind.
        procedure, private :: set_slice_i32v !! %set_slice specific for the i32v kind.
        procedure, private :: set_slice_i64v !! %set_slice specific for the i64v kind.
        procedure, private :: set_slice_f32v !! %set_slice specific for the f32v kind.
        procedure, private :: set_slice_f64v !! %set_slice specific for the f64v kind.
        procedure, private :: set_slice_boolv !! %set_slice specific for the boolv kind.
        procedure, private :: set_slice_datev !! %set_slice specific for the datev kind.
        procedure, private :: set_slice_timev !! %set_slice specific for the timev kind.
        procedure, private :: set_slice_tsv !! %set_slice specific for the tsv kind.
        procedure, private :: set_slice_chr  !! %set_slice specific taking a character array.
        procedure, private :: set_slice_chrv !! %set_slice specific taking a character (elem, row) array.
        !> Writes values into the rows a `parquet_slice` selects -- %get_slice's counterpart.
        !! The kind must match the column's exactly, and the array must have one value per
        !! selected row.
        !!
        !! **The selection is written in the order given, duplicates included, so a row
        !! named twice ends up holding the LAST value written to it.** Selections are
        !! applied one after another rather than merged or de-duplicated, which is what
        !! makes `%set_slice` the exact inverse of `%get_slice` for a selection that names
        !! each row once.
        generic :: set_slice => set_slice_i32, set_slice_i64, set_slice_f32, set_slice_f64, set_slice_bool, set_slice_date, &
            set_slice_time, set_slice_ts, set_slice_i32v, set_slice_i64v, set_slice_f32v, set_slice_f64v, set_slice_boolv, &
            set_slice_datev, set_slice_timev, set_slice_tsv, set_slice_chr, set_slice_chrv
        ! --- zero-copy pointer access (exact kind) ---
        procedure, private :: col_ptr_i32 !! %col specific for the i32 kind.
        procedure, private :: col_ptr_i64 !! %col specific for the i64 kind.
        procedure, private :: col_ptr_f32 !! %col specific for the f32 kind.
        procedure, private :: col_ptr_f64 !! %col specific for the f64 kind.
        procedure, private :: col_ptr_bool !! %col specific for the bool kind.
        procedure, private :: col_ptr_date !! %col specific for the date kind.
        procedure, private :: col_ptr_time !! %col specific for the time kind.
        procedure, private :: col_ptr_ts !! %col specific for the ts kind.
        procedure, private :: col_ptr_i32v !! %col specific for the i32v kind.
        procedure, private :: col_ptr_i64v !! %col specific for the i64v kind.
        procedure, private :: col_ptr_f32v !! %col specific for the f32v kind.
        procedure, private :: col_ptr_f64v !! %col specific for the f64v kind.
        procedure, private :: col_ptr_boolv !! %col specific for the boolv kind.
        procedure, private :: col_ptr_datev !! %col specific for the datev kind.
        procedure, private :: col_ptr_timev !! %col specific for the timev kind.
        procedure, private :: col_ptr_tsv !! %col specific for the tsv kind.
        procedure, private :: col_ptr_strcol !! %col specific aliasing the compact string store.
        !> Points `p` at a column's storage: zero copy, writable, and the pointer kind must
        !! match the stored kind exactly (ask %kind first if you do not know it). A
        !! `parquet_string_column` pointer aliases a PK_STRING column's packed store: read
        !! it and edit its values in place, but do NOT change its length or element count
        !! through the pointer -- the column's own row count would no longer describe it.
        !!
        !! For the same reason, do NOT REORDER a column through this pointer -- in particular
        !! `call pf_permute(p, perm)`, which compiles and runs happily. It reorders that one
        !! column and leaves every other column where it was, silently breaking the row
        !! correspondence, and nothing detects it: the row count is unchanged and every later
        !! read returns values that are individually valid and jointly wrong. Use `%sort_by`,
        !! which reorders every column together.
        generic :: col => col_ptr_i32, col_ptr_i64, col_ptr_f32, col_ptr_f64, col_ptr_bool, col_ptr_date, col_ptr_time, &
            col_ptr_ts, col_ptr_i32v, col_ptr_i64v, col_ptr_f32v, col_ptr_f64v, col_ptr_boolv, col_ptr_datev, col_ptr_timev, &
            col_ptr_tsv, col_ptr_strcol
        ! --- copy out (widens int32->int64, float32->float64) ---
        procedure, private :: get_arr_i32 !! %get specific for the i32 kind.
        procedure, private :: get_arr_i64 !! %get specific for the i64 kind.
        procedure, private :: get_arr_f32 !! %get specific for the f32 kind.
        procedure, private :: get_arr_f64 !! %get specific for the f64 kind.
        procedure, private :: get_arr_bool !! %get specific for the bool kind.
        procedure, private :: get_arr_date !! %get specific for the date kind.
        procedure, private :: get_arr_time !! %get specific for the time kind.
        procedure, private :: get_arr_ts !! %get specific for the ts kind.
        procedure, private :: get_arr_i32v !! %get specific for the i32v kind.
        procedure, private :: get_arr_i64v !! %get specific for the i64v kind.
        procedure, private :: get_arr_f32v !! %get specific for the f32v kind.
        procedure, private :: get_arr_f64v !! %get specific for the f64v kind.
        procedure, private :: get_arr_boolv !! %get specific for the boolv kind.
        procedure, private :: get_arr_datev !! %get specific for the datev kind.
        procedure, private :: get_arr_timev !! %get specific for the timev kind.
        procedure, private :: get_arr_tsv !! %get specific for the tsv kind.
        procedure, private :: get_arr_str  !! %get specific returning a parquet_string_column.
        procedure, private :: get_arr_chr  !! %get specific returning a character array.
        procedure, private :: get_arr_chrv !! %get specific returning a character (elem, row) array.
        !> Copies a column into a freshly allocated array of the caller's own kind.
        generic :: get => get_arr_i32, get_arr_i64, get_arr_f32, get_arr_f64, get_arr_bool, get_arr_date, get_arr_time, &
            get_arr_ts, get_arr_i32v, get_arr_i64v, get_arr_f32v, get_arr_f64v, get_arr_boolv, get_arr_datev, get_arr_timev, &
            get_arr_tsv, get_arr_str, get_arr_chr, get_arr_chrv
        ! --- copy back (same length, exact kind) ---
        procedure, private :: set_arr_i32 !! %set specific for the i32 kind.
        procedure, private :: set_arr_i64 !! %set specific for the i64 kind.
        procedure, private :: set_arr_f32 !! %set specific for the f32 kind.
        procedure, private :: set_arr_f64 !! %set specific for the f64 kind.
        procedure, private :: set_arr_bool !! %set specific for the bool kind.
        procedure, private :: set_arr_date !! %set specific for the date kind.
        procedure, private :: set_arr_time !! %set specific for the time kind.
        procedure, private :: set_arr_ts !! %set specific for the ts kind.
        procedure, private :: set_arr_i32v !! %set specific for the i32v kind.
        procedure, private :: set_arr_i64v !! %set specific for the i64v kind.
        procedure, private :: set_arr_f32v !! %set specific for the f32v kind.
        procedure, private :: set_arr_f64v !! %set specific for the f64v kind.
        procedure, private :: set_arr_boolv !! %set specific for the boolv kind.
        procedure, private :: set_arr_datev !! %set specific for the datev kind.
        procedure, private :: set_arr_timev !! %set specific for the timev kind.
        procedure, private :: set_arr_tsv !! %set specific for the tsv kind.
        procedure, private :: set_arr_chr  !! %set specific taking a character array.
        procedure, private :: set_arr_chrv !! %set specific taking a character (elem, row) array.
        !> Replaces every value of an existing column from an array of the same length.
        procedure, private :: set_arr_strcol !! %set specific taking a parquet_string_column.
        generic :: set => set_arr_i32, set_arr_i64, set_arr_f32, set_arr_f64, set_arr_bool, set_arr_date, set_arr_time, &
            set_arr_ts, set_arr_i32v, set_arr_i64v, set_arr_f32v, set_arr_f64v, set_arr_boolv, set_arr_datev, set_arr_timev, &
            set_arr_tsv, set_arr_chr, set_arr_chrv, set_arr_strcol
        ! --- from-scratch construction ---
        procedure, private :: add_column_i32 !! %add_column specific for the i32 kind.
        procedure, private :: add_column_i64 !! %add_column specific for the i64 kind.
        procedure, private :: add_column_f32 !! %add_column specific for the f32 kind.
        procedure, private :: add_column_f64 !! %add_column specific for the f64 kind.
        procedure, private :: add_column_bool !! %add_column specific for the bool kind.
        procedure, private :: add_column_date !! %add_column specific for the date kind.
        procedure, private :: add_column_time !! %add_column specific for the time kind.
        procedure, private :: add_column_ts !! %add_column specific for the ts kind.
        procedure, private :: add_column_i32v !! %add_column specific for the i32v kind.
        procedure, private :: add_column_i64v !! %add_column specific for the i64v kind.
        procedure, private :: add_column_f32v !! %add_column specific for the f32v kind.
        procedure, private :: add_column_f64v !! %add_column specific for the f64v kind.
        procedure, private :: add_column_boolv !! %add_column specific for the boolv kind.
        procedure, private :: add_column_datev !! %add_column specific for the datev kind.
        procedure, private :: add_column_timev !! %add_column specific for the timev kind.
        procedure, private :: add_column_tsv !! %add_column specific for the tsv kind.
        procedure, private :: add_column_chr  !! %add_column specific taking a character array.
        procedure, private :: add_column_chrv !! %add_column specific taking a character (elem, row) array.
        !> Appends a new column, taking its values (and so its kind, width and row count).
        procedure, private :: add_column_strcol !! %add_column specific taking a parquet_string_column.
        procedure, private :: add_column_col  !! %add_column specific taking a whole parquet_column.
        generic :: add_column => add_column_i32, add_column_i64, add_column_f32, add_column_f64, add_column_bool, &
            add_column_date, add_column_time, add_column_ts, add_column_i32v, add_column_i64v, add_column_f32v, add_column_f64v, &
            add_column_boolv, add_column_datev, add_column_timev, add_column_tsv, add_column_chr, add_column_chrv, &
            add_column_strcol, add_column_col
        ! --- mutation: one cell at a time (never changes the row set) ---
        procedure, private :: set_element_i32_i32 !! %set_element specific, i32 kind, i32 row index.
        procedure, private :: set_element_i32_i64 !! %set_element specific, i32 kind, i64 row index.
        procedure, private :: set_element_i64_i32 !! %set_element specific, i64 kind, i32 row index.
        procedure, private :: set_element_i64_i64 !! %set_element specific, i64 kind, i64 row index.
        procedure, private :: set_element_f32_i32 !! %set_element specific, f32 kind, i32 row index.
        procedure, private :: set_element_f32_i64 !! %set_element specific, f32 kind, i64 row index.
        procedure, private :: set_element_f64_i32 !! %set_element specific, f64 kind, i32 row index.
        procedure, private :: set_element_f64_i64 !! %set_element specific, f64 kind, i64 row index.
        procedure, private :: set_element_bool_i32 !! %set_element specific, bool kind, i32 row index.
        procedure, private :: set_element_bool_i64 !! %set_element specific, bool kind, i64 row index.
        procedure, private :: set_element_date_i32 !! %set_element specific, date kind, i32 row index.
        procedure, private :: set_element_date_i64 !! %set_element specific, date kind, i64 row index.
        procedure, private :: set_element_time_i32 !! %set_element specific, time kind, i32 row index.
        procedure, private :: set_element_time_i64 !! %set_element specific, time kind, i64 row index.
        procedure, private :: set_element_ts_i32 !! %set_element specific, ts kind, i32 row index.
        procedure, private :: set_element_ts_i64 !! %set_element specific, ts kind, i64 row index.
        procedure, private :: set_element_i32v_i32 !! %set_element specific, i32v kind, i32 row index.
        procedure, private :: set_element_i32v_i64 !! %set_element specific, i32v kind, i64 row index.
        procedure, private :: set_element_i64v_i32 !! %set_element specific, i64v kind, i32 row index.
        procedure, private :: set_element_i64v_i64 !! %set_element specific, i64v kind, i64 row index.
        procedure, private :: set_element_f32v_i32 !! %set_element specific, f32v kind, i32 row index.
        procedure, private :: set_element_f32v_i64 !! %set_element specific, f32v kind, i64 row index.
        procedure, private :: set_element_f64v_i32 !! %set_element specific, f64v kind, i32 row index.
        procedure, private :: set_element_f64v_i64 !! %set_element specific, f64v kind, i64 row index.
        procedure, private :: set_element_boolv_i32 !! %set_element specific, boolv kind, i32 row index.
        procedure, private :: set_element_boolv_i64 !! %set_element specific, boolv kind, i64 row index.
        procedure, private :: set_element_datev_i32 !! %set_element specific, datev kind, i32 row index.
        procedure, private :: set_element_datev_i64 !! %set_element specific, datev kind, i64 row index.
        procedure, private :: set_element_timev_i32 !! %set_element specific, timev kind, i32 row index.
        procedure, private :: set_element_timev_i64 !! %set_element specific, timev kind, i64 row index.
        procedure, private :: set_element_tsv_i32 !! %set_element specific, tsv kind, i32 row index.
        procedure, private :: set_element_tsv_i64 !! %set_element specific, tsv kind, i64 row index.
        procedure, private :: set_element_chr_i32 !! %set_element specific, character chr form, i32 row index.
        procedure, private :: set_element_chr_i64 !! %set_element specific, character chr form, i64 row index.
        procedure, private :: set_element_chrv_i32 !! %set_element specific, character chrv form, i32 row index.
        procedure, private :: set_element_chrv_i64 !! %set_element specific, character chrv form, i64 row index.
        procedure, private :: get_element_i32_i32 !! %get_element specific, i32 kind, i32 row index.
        procedure, private :: get_element_i32_i64 !! %get_element specific, i32 kind, i64 row index.
        procedure, private :: get_element_i64_i32 !! %get_element specific, i64 kind, i32 row index.
        procedure, private :: get_element_i64_i64 !! %get_element specific, i64 kind, i64 row index.
        procedure, private :: get_element_f32_i32 !! %get_element specific, f32 kind, i32 row index.
        procedure, private :: get_element_f32_i64 !! %get_element specific, f32 kind, i64 row index.
        procedure, private :: get_element_f64_i32 !! %get_element specific, f64 kind, i32 row index.
        procedure, private :: get_element_f64_i64 !! %get_element specific, f64 kind, i64 row index.
        procedure, private :: get_element_bool_i32 !! %get_element specific, bool kind, i32 row index.
        procedure, private :: get_element_bool_i64 !! %get_element specific, bool kind, i64 row index.
        procedure, private :: get_element_date_i32 !! %get_element specific, date kind, i32 row index.
        procedure, private :: get_element_date_i64 !! %get_element specific, date kind, i64 row index.
        procedure, private :: get_element_time_i32 !! %get_element specific, time kind, i32 row index.
        procedure, private :: get_element_time_i64 !! %get_element specific, time kind, i64 row index.
        procedure, private :: get_element_ts_i32 !! %get_element specific, ts kind, i32 row index.
        procedure, private :: get_element_ts_i64 !! %get_element specific, ts kind, i64 row index.
        procedure, private :: get_element_i32v_i32 !! %get_element specific, i32v kind, i32 row index.
        procedure, private :: get_element_i32v_i64 !! %get_element specific, i32v kind, i64 row index.
        procedure, private :: get_element_i64v_i32 !! %get_element specific, i64v kind, i32 row index.
        procedure, private :: get_element_i64v_i64 !! %get_element specific, i64v kind, i64 row index.
        procedure, private :: get_element_f32v_i32 !! %get_element specific, f32v kind, i32 row index.
        procedure, private :: get_element_f32v_i64 !! %get_element specific, f32v kind, i64 row index.
        procedure, private :: get_element_f64v_i32 !! %get_element specific, f64v kind, i32 row index.
        procedure, private :: get_element_f64v_i64 !! %get_element specific, f64v kind, i64 row index.
        procedure, private :: get_element_boolv_i32 !! %get_element specific, boolv kind, i32 row index.
        procedure, private :: get_element_boolv_i64 !! %get_element specific, boolv kind, i64 row index.
        procedure, private :: get_element_datev_i32 !! %get_element specific, datev kind, i32 row index.
        procedure, private :: get_element_datev_i64 !! %get_element specific, datev kind, i64 row index.
        procedure, private :: get_element_timev_i32 !! %get_element specific, timev kind, i32 row index.
        procedure, private :: get_element_timev_i64 !! %get_element specific, timev kind, i64 row index.
        procedure, private :: get_element_tsv_i32 !! %get_element specific, tsv kind, i32 row index.
        procedure, private :: get_element_tsv_i64 !! %get_element specific, tsv kind, i64 row index.
        procedure, private :: get_element_chr_i32 !! %get_element specific, character chr form, i32 row index.
        procedure, private :: get_element_chr_i64 !! %get_element specific, character chr form, i64 row index.
        procedure, private :: get_element_chrv_i32 !! %get_element specific, character chrv form, i32 row index.
        procedure, private :: get_element_chrv_i64 !! %get_element specific, character chrv form, i64 row index.
        !> Reads one row's value out of a column, widening into the caller's variable
        !! exactly as %get does -- the one-call form of `r = t%row(i)` then `r%get(name, v)`.
        !! On a *_VEC column the value is that row's whole vector.
        generic :: get_element => get_element_i32_i32, get_element_i32_i64, get_element_i64_i32, get_element_i64_i64, &
            get_element_f32_i32, get_element_f32_i64, get_element_f64_i32, get_element_f64_i64, get_element_bool_i32, &
            get_element_bool_i64, get_element_date_i32, get_element_date_i64, get_element_time_i32, get_element_time_i64, &
            get_element_ts_i32, get_element_ts_i64, get_element_i32v_i32, get_element_i32v_i64, get_element_i64v_i32, &
            get_element_i64v_i64, get_element_f32v_i32, get_element_f32v_i64, get_element_f64v_i32, get_element_f64v_i64, &
            get_element_boolv_i32, get_element_boolv_i64, get_element_datev_i32, get_element_datev_i64, get_element_timev_i32, &
            get_element_timev_i64, get_element_tsv_i32, get_element_tsv_i64, get_element_chr_i32, get_element_chr_i64, &
            get_element_chrv_i32, get_element_chrv_i64
        !> Writes one row's value in place. The kind must match the column's exactly (as
        !! %set does), and writing a value CLEARS that row's null -- use %set_null to put
        !! one back. On a *_VEC column the value is that row's whole vector.
        generic :: set_element => set_element_i32_i32, set_element_i32_i64, set_element_i64_i32, set_element_i64_i64, &
            set_element_f32_i32, set_element_f32_i64, set_element_f64_i32, set_element_f64_i64, set_element_bool_i32, &
            set_element_bool_i64, set_element_date_i32, set_element_date_i64, set_element_time_i32, set_element_time_i64, &
            set_element_ts_i32, set_element_ts_i64, set_element_i32v_i32, set_element_i32v_i64, set_element_i64v_i32, &
            set_element_i64v_i64, set_element_f32v_i32, set_element_f32v_i64, set_element_f64v_i32, set_element_f64v_i64, &
            set_element_boolv_i32, set_element_boolv_i64, set_element_datev_i32, set_element_datev_i64, set_element_timev_i32, &
            set_element_timev_i64, set_element_tsv_i32, set_element_tsv_i64, set_element_chr_i32, set_element_chr_i64, &
            set_element_chrv_i32, set_element_chrv_i64
        procedure, private :: set_null_i32   !! %set_null specific taking an int32 row index.
        procedure, private :: set_null_i64   !! %set_null specific taking an int64 row index.
        procedure, private :: set_null_e32   !! %set_null specific taking an int32 row + element.
        procedure, private :: set_null_e64   !! %set_null specific taking an int64 row + element.
        procedure, private :: set_null_mask  !! %set_null specific taking a per-row mask.
        procedure, private :: set_null_mask_elem !! %set_null specific taking a per-element mask.
        !> Marks null: row `i` of a column, element `e` of row `i`, or every entry a `logical`
        !! mask marks `.false.`.
        !!
        !! The row form is whole-row even on a *_VEC column -- naming only a row says the row is
        !! missing. Name `e` to null one element. The mask form takes either shape: one entry per
        !! row (whole rows), or a `(width, nrows)` mask (individual elements).
        generic :: set_null => set_null_i32, set_null_i64, set_null_e32, set_null_e64, &
            set_null_mask, set_null_mask_elem
        procedure, private :: clear_null_i32 !! %clear_null specific taking an int32 row index.
        procedure, private :: clear_null_i64 !! %clear_null specific taking an int64 row index.
        procedure, private :: clear_null_e32 !! %clear_null specific taking an int32 row + element.
        procedure, private :: clear_null_e64 !! %clear_null specific taking an int64 row + element.
        !> Marks row `i` -- or, given `e`, element `e` of it -- valid without saying what its value
        !! is. Only useful when a value is already there or is about to be written; %set_element
        !! clears the null itself.
        generic :: clear_null => clear_null_i32, clear_null_i64, clear_null_e32, clear_null_e64
        procedure :: compact_validity => table_compact_validity !! Drop a null bitmap that no longer has nulls.
        procedure :: ensure_validity => table_ensure_validity !! Allocate validity storage up front, for concurrent nulling.
        ! --- mutation: whole columns (never changes the row set) ---
        procedure :: drop_column => table_drop_column     !! Remove a column; force= for a predefined one.
        procedure :: rename_column => table_rename_column !! Change the name a column is looked up by.
        procedure :: copy_column => table_copy_column     !! Add a copy of a column, optionally of another kind.
        procedure :: cast => table_cast                   !! Convert a column to another kind, in place.
        ! --- mutation: the row set itself -- every one of these DETACHES the table ---
        procedure :: filter_rows => table_filter_rows !! Keep only the rows a mask selects.
        procedure, private :: table_sort_by           !! %sort_by specific, array of key names.
        procedure, private :: table_sort_by_string    !! %sort_by specific, separated key string.
        !> Reorders rows by one or more key columns. Detaching.
        generic :: sort_by => table_sort_by, table_sort_by_string
        procedure, private :: table_top_n             !! %top_n specific, array of key names.
        procedure, private :: table_top_n_string      !! %top_n specific, separated key string.
        !> Keeps only the n best rows, in key order. Detaching.
        generic :: top_n => table_top_n, table_top_n_string
        ! --- the ORDER, without applying it: read-only, and they do NOT detach ---
        procedure, private :: table_argsort_by_i32    !! %argsort_by specific, int32 permutation.
        procedure, private :: table_argsort_by_i64    !! %argsort_by specific, int64 permutation.
        procedure, private :: table_argsort_by_string_i32 !! %argsort_by specific, key string, int32.
        procedure, private :: table_argsort_by_string_i64 !! %argsort_by specific, key string, int64.
        !> The row order the keys imply, without reordering anything. Unlike %sort_by the table
        !! stays attached, so this is how to read rows in an order while keeping the file.
        generic :: argsort_by => table_argsort_by_i32, table_argsort_by_i64, &
                                 table_argsort_by_string_i32, table_argsort_by_string_i64
        procedure, private :: table_argsort_partial_i32 !! %argsort_partial specific, int32 perm.
        procedure, private :: table_argsort_partial_i64 !! %argsort_partial specific, int64 perm.
        procedure, private :: table_argsort_partial_string_i32 !! %argsort_partial, key string, int32.
        procedure, private :: table_argsort_partial_string_i64 !! %argsort_partial, key string, int64.
        !> The `n` best rows in order, by selection rather than a full sort. Also non-mutating.
        generic :: argsort_partial => table_argsort_partial_i32, table_argsort_partial_i64, &
                                      table_argsort_partial_string_i32, table_argsort_partial_string_i64
        procedure, private :: table_is_sorted_by        !! %is_sorted_by specific, array of key names.
        procedure, private :: table_is_sorted_by_string !! %is_sorted_by specific, key string.
        !> Whether the rows are already in that order.
        generic :: is_sorted_by => table_is_sorted_by, table_is_sorted_by_string
        procedure, private :: table_delete_rows_i32   !! %delete_rows specific, int32 indices.
        procedure, private :: table_delete_rows_i64   !! %delete_rows specific, int64 indices.
        !> Removes the listed rows. A thin convenience over %filter_rows, and like it, detaching.
        generic :: delete_rows => table_delete_rows_i32, table_delete_rows_i64
        procedure, private :: table_truncate_i32      !! %truncate specific, int32 count.
        procedure, private :: table_truncate_i64      !! %truncate specific, int64 count.
        !> Keeps only the first n rows. Detaching, like every row-structural change.
        generic :: truncate => table_truncate_i32, table_truncate_i64
        procedure, private :: table_append_table      !! %append specific taking another table.
        procedure, private :: table_append_row        !! %append specific taking one row handle.
        !> Appends rows: a whole table's worth, or one row. Detaching, like every row-structural
        !! change. The bulk idiom is %clone_structure -> fill -> %append(batch).
        generic :: append => table_append_table, table_append_row
        procedure, private :: table_append_null_rows_i32 !! %append_null_rows specific, int32 count.
        procedure, private :: table_append_null_rows_i64 !! %append_null_rows specific, int64 count.
        !> Appends n all-null rows, to be filled in afterwards. Detaching.
        generic :: append_null_rows => table_append_null_rows_i32, table_append_null_rows_i64
        ! --- capacity ---
        procedure :: compact => table_compact         !! Release capacity appends left behind.
        procedure, private :: table_reserve_i32       !! %reserve specific, int32 count.
        procedure, private :: table_reserve_i64       !! %reserve specific, int64 count.
        !> Makes room for n rows in every resident column, so the appends that follow do not
        !! reallocate. %compact's counterpart; neither changes the row set, so neither detaches.
        generic :: reserve => table_reserve_i32, table_reserve_i64
        !> Makes room for n COLUMNS, so that the %add_column calls that follow relocate nothing
        !! and leave an outstanding %col pointer valid. See its own doc-comment for the guarantee.
        procedure :: reserve_columns => table_reserve_columns
        procedure :: column_capacity => table_column_capacity !! Column slots allocated, or spare.
        ! --- copying ---
        procedure :: clone => table_clone                     !! Independent deep copy of this table.
        procedure :: clone_structure => table_clone_structure !! Empty table with the same columns.
        procedure :: clone_extra => table_clone_extra !! Hook -- copies an EXTENDING type's own components.
        ! --- generated table types (see doc/pages/utilities/generated-tables.md) ---
        procedure :: bind_predefined => table_bind_predefined !! Binds a generated type's predefined columns.
        ! --- lifecycle ---
        !> Blocks intrinsic assignment: the store lives behind a pointer, so a default `b = a`
        !! would leave two tables sharing one store and double-freeing it.
        !!
        !! **The binding is named `assign_guard`, and the name's ALPHABETICAL POSITION is
        !! load-bearing -- do not "tidy" it to `table_assign_guard` to match its implementation.**
        !! flang builds a type's binding table sorted by name and stores a SPECIAL binding's index
        !! (a defined assignment, here) in a single byte. `parquet_table` has ~285 bindings, so a
        !! guard sorting under "t" lands past index 255 and flang dies with an internal compiler
        !! error -- `CHECK(bindingIndex <= 255)` in runtime-type-info.cpp, which names neither this
        !! type nor this line. Sorting under "a" keeps it far below the limit. gfortran and ifx are
        !! indifferent, so nothing here will warn if this is undone.
        generic :: assignment(=) => assign_guard
        procedure, private :: assign_guard => table_assign_guard !! The blocking defined assignment.
        final :: table_finalize                  !! Frees the store; never fails, never validates.
    end type parquet_table
    !
    !> One row of a table, as a lightweight handle: `r = t%row(i)`.
    !!
    !! Non-owning and cheap to make, so it is the natural thing to pass to a procedure that
    !! works on a single row, or to build inside a loop over rows. It resolves the column by
    !! name and the row by index on EVERY access, so it survives anything that merely reallocates
    !! a column's values -- and it triggers the same lazy first touch that `%get` on the table
    !! does, so a handle can reach a column nothing has read yet.
    !!
    !! It points at the table's column STORE, not at the table, which is what lets `t%row(i)`
    !! return a usable handle without the caller declaring the table `target` (a pointer to a
    !! dummy's target would be undefined the moment the function returned).
    !!
    !! **A handle does not survive a structural change**, and says so rather than reading the wrong
    !! row: it stamps the table's `%generation()` when it is made and refuses once they differ.
    !! `%is_valid()` is the non-aborting way to ask. The rule, the stamp and the message are the
    !! same ones `parquet_table_col` uses -- two handles with one rule between them.
    !!
    !! Still treat it as short-lived: a handle is cheap to make and the refusal is deliberately
    !! conservative, so re-fetching inside the loop is the shape to reach for rather than working
    !! out which mutations a particular handle could have survived.
    !!
    !! **No finalizer, deliberately** -- and this once had one, `row_finalize`, which nullified the
    !! pointer, removed on the argument rather than on a measurement. It protected nothing: it ran
    !! at scope exit (the object is already dead), and on `r = t%row(i)` it nullified a pointer the
    !! copy overwrote a moment later. It could not catch the case that matters either -- a cache
    !! freed with its table leaves `associated()` answering `.true.`, not `.false.`. Meanwhile it
    !! cost two finalizer calls per assignment, on the shape this API makes idiomatic, and put the
    !! type in the class CLAUDE.md says never to give to OpenMP's `private()`.
    !!
    !! `parquet_string` still carries the equivalent nullify-only finalizer, so the library is not
    !! uniform here. That is accepted rather than overlooked: it is a released type, its finalizer
    !! has no measured cost, and the consistency that matters is between this handle and
    !! `parquet_table_col`, which the guide presents as its mirror image.
    type :: parquet_table_row
        private
        type(parquet_table_cache), pointer :: cache => null() !! the table's column store.
        integer(int64) :: irow = 0                            !! this row's 1-based index.
        integer(int64) :: gen = -1_int64                      !! cache%generation when this handle was made.
        type(table_scope) :: scope                            !! the table's row scope, by value.
    contains
        procedure, private :: row_get_i32 !! %get specific for the i32 kind.
        procedure, private :: row_get_i64 !! %get specific for the i64 kind.
        procedure, private :: row_get_f32 !! %get specific for the f32 kind.
        procedure, private :: row_get_f64 !! %get specific for the f64 kind.
        procedure, private :: row_get_bool !! %get specific for the bool kind.
        procedure, private :: row_get_str !! %get specific for the str kind.
        procedure, private :: row_get_date !! %get specific for the date kind.
        procedure, private :: row_get_time !! %get specific for the time kind.
        procedure, private :: row_get_ts !! %get specific for the ts kind.
        procedure, private :: row_get_i32v !! %get specific for the i32v kind.
        procedure, private :: row_get_i64v !! %get specific for the i64v kind.
        procedure, private :: row_get_f32v !! %get specific for the f32v kind.
        procedure, private :: row_get_f64v !! %get specific for the f64v kind.
        procedure, private :: row_get_boolv !! %get specific for the boolv kind.
        procedure, private :: row_get_strv !! %get specific for the strv kind.
        procedure, private :: row_get_datev !! %get specific for the datev kind.
        procedure, private :: row_get_timev !! %get specific for the timev kind.
        procedure, private :: row_get_tsv !! %get specific for the tsv kind.
        procedure, private :: row_get_col_i32 !! %get specific, i32 kind, column by handle.
        procedure, private :: row_get_col_i64 !! %get specific, i64 kind, column by handle.
        procedure, private :: row_get_col_f32 !! %get specific, f32 kind, column by handle.
        procedure, private :: row_get_col_f64 !! %get specific, f64 kind, column by handle.
        procedure, private :: row_get_col_bool !! %get specific, bool kind, column by handle.
        procedure, private :: row_get_col_str !! %get specific, str kind, column by handle.
        procedure, private :: row_get_col_date !! %get specific, date kind, column by handle.
        procedure, private :: row_get_col_time !! %get specific, time kind, column by handle.
        procedure, private :: row_get_col_ts !! %get specific, ts kind, column by handle.
        procedure, private :: row_get_col_i32v !! %get specific, i32v kind, column by handle.
        procedure, private :: row_get_col_i64v !! %get specific, i64v kind, column by handle.
        procedure, private :: row_get_col_f32v !! %get specific, f32v kind, column by handle.
        procedure, private :: row_get_col_f64v !! %get specific, f64v kind, column by handle.
        procedure, private :: row_get_col_boolv !! %get specific, boolv kind, column by handle.
        procedure, private :: row_get_col_strv !! %get specific, strv kind, column by handle.
        procedure, private :: row_get_col_datev !! %get specific, datev kind, column by handle.
        procedure, private :: row_get_col_timev !! %get specific, timev kind, column by handle.
        procedure, private :: row_get_col_tsv !! %get specific, tsv kind, column by handle.
        !> Copies this row's value for a column into the caller's own variable, widening
        !! int32 -> int64 and float32 -> float64 exactly as the table's own %get does. The
        !! column may be named by a string or by a `parquet_table_col` handle; the handle
        !! form does no name lookup, which is what a loop over rows would otherwise repeat.
        generic :: get => row_get_i32, row_get_i64, row_get_f32, row_get_f64, row_get_bool, row_get_str, row_get_date, &
            row_get_time, row_get_ts, row_get_i32v, row_get_i64v, row_get_f32v, row_get_f64v, row_get_boolv, row_get_strv, &
            row_get_datev, row_get_timev, row_get_tsv, row_get_col_i32, row_get_col_i64, row_get_col_f32, row_get_col_f64, &
            row_get_col_bool, row_get_col_str, row_get_col_date, row_get_col_time, row_get_col_ts, row_get_col_i32v, &
            row_get_col_i64v, row_get_col_f32v, row_get_col_f64v, row_get_col_boolv, row_get_col_strv, row_get_col_datev, &
            row_get_col_timev, row_get_col_tsv
        procedure, private :: row_set_i32 !! %set specific for the i32 kind.
        procedure, private :: row_set_i64 !! %set specific for the i64 kind.
        procedure, private :: row_set_f32 !! %set specific for the f32 kind.
        procedure, private :: row_set_f64 !! %set specific for the f64 kind.
        procedure, private :: row_set_bool !! %set specific for the bool kind.
        procedure, private :: row_set_str !! %set specific for the str kind.
        procedure, private :: row_set_date !! %set specific for the date kind.
        procedure, private :: row_set_time !! %set specific for the time kind.
        procedure, private :: row_set_ts !! %set specific for the ts kind.
        procedure, private :: row_set_i32v !! %set specific for the i32v kind.
        procedure, private :: row_set_i64v !! %set specific for the i64v kind.
        procedure, private :: row_set_f32v !! %set specific for the f32v kind.
        procedure, private :: row_set_f64v !! %set specific for the f64v kind.
        procedure, private :: row_set_boolv !! %set specific for the boolv kind.
        procedure, private :: row_set_strv !! %set specific for the strv kind.
        procedure, private :: row_set_datev !! %set specific for the datev kind.
        procedure, private :: row_set_timev !! %set specific for the timev kind.
        procedure, private :: row_set_tsv !! %set specific for the tsv kind.
        procedure, private :: row_set_col_i32 !! %set specific, i32 kind, column by handle.
        procedure, private :: row_set_col_i64 !! %set specific, i64 kind, column by handle.
        procedure, private :: row_set_col_f32 !! %set specific, f32 kind, column by handle.
        procedure, private :: row_set_col_f64 !! %set specific, f64 kind, column by handle.
        procedure, private :: row_set_col_bool !! %set specific, bool kind, column by handle.
        procedure, private :: row_set_col_str !! %set specific, str kind, column by handle.
        procedure, private :: row_set_col_date !! %set specific, date kind, column by handle.
        procedure, private :: row_set_col_time !! %set specific, time kind, column by handle.
        procedure, private :: row_set_col_ts !! %set specific, ts kind, column by handle.
        procedure, private :: row_set_col_i32v !! %set specific, i32v kind, column by handle.
        procedure, private :: row_set_col_i64v !! %set specific, i64v kind, column by handle.
        procedure, private :: row_set_col_f32v !! %set specific, f32v kind, column by handle.
        procedure, private :: row_set_col_f64v !! %set specific, f64v kind, column by handle.
        procedure, private :: row_set_col_boolv !! %set specific, boolv kind, column by handle.
        procedure, private :: row_set_col_strv !! %set specific, strv kind, column by handle.
        procedure, private :: row_set_col_datev !! %set specific, datev kind, column by handle.
        procedure, private :: row_set_col_timev !! %set specific, timev kind, column by handle.
        procedure, private :: row_set_col_tsv !! %set specific, tsv kind, column by handle.
        !> Writes this row's value for a column. The kind must match the column's exactly
        !! (a write never widens), and writing a value CLEARS that row's null. The TABLE is
        !! updated -- a handle is a view of it, not a copy. The column may be named by a
        !! string or by a `parquet_table_col` handle.
        generic :: set => row_set_i32, row_set_i64, row_set_f32, row_set_f64, row_set_bool, row_set_str, row_set_date, &
            row_set_time, row_set_ts, row_set_i32v, row_set_i64v, row_set_f32v, row_set_f64v, row_set_boolv, row_set_strv, &
            row_set_datev, row_set_timev, row_set_tsv, row_set_col_i32, row_set_col_i64, row_set_col_f32, row_set_col_f64, &
            row_set_col_bool, row_set_col_str, row_set_col_date, row_set_col_time, row_set_col_ts, row_set_col_i32v, &
            row_set_col_i64v, row_set_col_f32v, row_set_col_f64v, row_set_col_boolv, row_set_col_strv, row_set_col_datev, &
            row_set_col_timev, row_set_col_tsv
        procedure, private :: row_ref_i32 !! %ref specific for the i32 kind.
        procedure, private :: row_ref_i64 !! %ref specific for the i64 kind.
        procedure, private :: row_ref_f32 !! %ref specific for the f32 kind.
        procedure, private :: row_ref_f64 !! %ref specific for the f64 kind.
        procedure, private :: row_ref_bool !! %ref specific for the bool kind.
        procedure, private :: row_ref_date !! %ref specific for the date kind.
        procedure, private :: row_ref_time !! %ref specific for the time kind.
        procedure, private :: row_ref_ts !! %ref specific for the ts kind.
        procedure, private :: row_ref_i32v !! %ref specific for the i32v kind.
        procedure, private :: row_ref_i64v !! %ref specific for the i64v kind.
        procedure, private :: row_ref_f32v !! %ref specific for the f32v kind.
        procedure, private :: row_ref_f64v !! %ref specific for the f64v kind.
        procedure, private :: row_ref_boolv !! %ref specific for the boolv kind.
        procedure, private :: row_ref_datev !! %ref specific for the datev kind.
        procedure, private :: row_ref_timev !! %ref specific for the timev kind.
        procedure, private :: row_ref_tsv !! %ref specific for the tsv kind.
        !> Points `p` at this row's storage: zero copy, writable, exact kind. A scalar
        !! column gives a scalar pointer, a vector column a pointer to that row's whole
        !! vector. The two string kinds have no %ref -- a packed variable-length store has
        !! no fixed slot to point at -- and the pointer dies with any structural change,
        !! exactly as the table's own %col pointers do.
        generic :: ref => row_ref_i32, row_ref_i64, row_ref_f32, row_ref_f64, row_ref_bool, row_ref_date, row_ref_time, &
            row_ref_ts, row_ref_i32v, row_ref_i64v, row_ref_f32v, row_ref_f64v, row_ref_boolv, row_ref_datev, row_ref_timev, &
            row_ref_tsv
        procedure, private :: row_is_null      !! %is_null specific asking about the whole row.
        procedure, private :: row_is_null_elem !! %is_null specific asking about one element.
        !> Whether this row is null in a column, or -- given `e` -- element `e` of it.
        generic :: is_null => row_is_null, row_is_null_elem
        procedure :: index => row_index       !! This row's 1-based index within the table.
        procedure :: is_valid => row_is_valid !! Whether the handle is attached AND still current.
        ! NO `final` -- see the type's own doc-comment. This is a decision, not an omission.
    end type parquet_table_row
    !
    !> A resolved handle on ONE column of a `parquet_table`: the slot, its kind and the table's row
    !! scope, captured once so a per-element loop stops resolving a name on every access.
    !!
    !! Made with `t%column(name)` or `t%column(j)` and used as `call c%get(i, value)`. It is a
    !! VIEW, not a copy -- writes through it change the table -- and it points at the table's
    !! column store, never at the table itself, so the table needs no `target` attribute.
    !!
    !! **A handle does not survive a structural change**, and says so rather than reading the wrong
    !! column: it stamps the table's `%generation()` when it is made and refuses once they differ.
    !! `%is_valid()` is the non-aborting way to ask. Re-fetching costs one lookup.
    !!
    !! **No finalizer, deliberately** -- the same decision `parquet_table_row` now carries, and for
    !! the same reasons. The handle owns nothing and frees nothing, and a finalizer could not catch
    !! the case that matters (a cache freed with its table leaves `associated()` answering `.true.`,
    !! not `.false.`). It is not free either: intrinsic assignment to or from a finalizable type
    !! runs the finalizer twice, which `c = t%column(name)` would pay on every handle it makes.
    !!
    !! **No allocatable components, mandatory.** A per-thread handle declared in a `block` inside a
    !! parallel region is an obvious thing to write, and this project has recorded both an ifx
    !! segfault and a gfortran uninitialised-`private()` bug for types in that position that carry
    !! one. See CLAUDE.md's "New `parquet_table` state goes on the CACHE".
    type :: parquet_table_col
        private
        type(parquet_table_cache), pointer :: cache => null() !! the table's column store.
        integer :: slot = 0                        !! 1-based index into cache%cols.
        integer :: colkind = PK_NONE               !! the kind resolved when the handle was made.
        integer(int64) :: gen = -1_int64           !! cache%generation when this handle was made.
        type(table_scope) :: scope                 !! the table's row scope, by value.
    contains

        procedure, private :: col_get_i32_i32 !! %get specific, i32 value, int32 row index.
        procedure, private :: col_get_i32_i64 !! %get specific, i32 value, int64 row index.
        procedure, private :: col_get_i64_i32 !! %get specific, i64 value, int32 row index.
        procedure, private :: col_get_i64_i64 !! %get specific, i64 value, int64 row index.
        procedure, private :: col_get_f32_i32 !! %get specific, f32 value, int32 row index.
        procedure, private :: col_get_f32_i64 !! %get specific, f32 value, int64 row index.
        procedure, private :: col_get_f64_i32 !! %get specific, f64 value, int32 row index.
        procedure, private :: col_get_f64_i64 !! %get specific, f64 value, int64 row index.
        procedure, private :: col_get_bool_i32 !! %get specific, bool value, int32 row index.
        procedure, private :: col_get_bool_i64 !! %get specific, bool value, int64 row index.
        procedure, private :: col_get_str_i32 !! %get specific, str value, int32 row index.
        procedure, private :: col_get_str_i64 !! %get specific, str value, int64 row index.
        procedure, private :: col_get_date_i32 !! %get specific, date value, int32 row index.
        procedure, private :: col_get_date_i64 !! %get specific, date value, int64 row index.
        procedure, private :: col_get_time_i32 !! %get specific, time value, int32 row index.
        procedure, private :: col_get_time_i64 !! %get specific, time value, int64 row index.
        procedure, private :: col_get_ts_i32 !! %get specific, ts value, int32 row index.
        procedure, private :: col_get_ts_i64 !! %get specific, ts value, int64 row index.
        procedure, private :: col_get_i32v_i32 !! %get specific, i32v value, int32 row index.
        procedure, private :: col_get_i32v_i64 !! %get specific, i32v value, int64 row index.
        procedure, private :: col_get_i64v_i32 !! %get specific, i64v value, int32 row index.
        procedure, private :: col_get_i64v_i64 !! %get specific, i64v value, int64 row index.
        procedure, private :: col_get_f32v_i32 !! %get specific, f32v value, int32 row index.
        procedure, private :: col_get_f32v_i64 !! %get specific, f32v value, int64 row index.
        procedure, private :: col_get_f64v_i32 !! %get specific, f64v value, int32 row index.
        procedure, private :: col_get_f64v_i64 !! %get specific, f64v value, int64 row index.
        procedure, private :: col_get_boolv_i32 !! %get specific, boolv value, int32 row index.
        procedure, private :: col_get_boolv_i64 !! %get specific, boolv value, int64 row index.
        procedure, private :: col_get_strv_i32 !! %get specific, strv value, int32 row index.
        procedure, private :: col_get_strv_i64 !! %get specific, strv value, int64 row index.
        procedure, private :: col_get_datev_i32 !! %get specific, datev value, int32 row index.
        procedure, private :: col_get_datev_i64 !! %get specific, datev value, int64 row index.
        procedure, private :: col_get_timev_i32 !! %get specific, timev value, int32 row index.
        procedure, private :: col_get_timev_i64 !! %get specific, timev value, int64 row index.
        procedure, private :: col_get_tsv_i32 !! %get specific, tsv value, int32 row index.
        procedure, private :: col_get_tsv_i64 !! %get specific, tsv value, int64 row index.
        procedure, private :: col_get_i32v_e32 !! %get specific, one i32v element, int32 indices.
        procedure, private :: col_get_i32v_e64 !! %get specific, one i32v element, int64 indices.
        procedure, private :: col_get_i64v_e32 !! %get specific, one i64v element, int32 indices.
        procedure, private :: col_get_i64v_e64 !! %get specific, one i64v element, int64 indices.
        procedure, private :: col_get_f32v_e32 !! %get specific, one f32v element, int32 indices.
        procedure, private :: col_get_f32v_e64 !! %get specific, one f32v element, int64 indices.
        procedure, private :: col_get_f64v_e32 !! %get specific, one f64v element, int32 indices.
        procedure, private :: col_get_f64v_e64 !! %get specific, one f64v element, int64 indices.
        procedure, private :: col_get_boolv_e32 !! %get specific, one boolv element, int32 indices.
        procedure, private :: col_get_boolv_e64 !! %get specific, one boolv element, int64 indices.
        procedure, private :: col_get_strv_e32 !! %get specific, one strv element, int32 indices.
        procedure, private :: col_get_strv_e64 !! %get specific, one strv element, int64 indices.
        procedure, private :: col_get_datev_e32 !! %get specific, one datev element, int32 indices.
        procedure, private :: col_get_datev_e64 !! %get specific, one datev element, int64 indices.
        procedure, private :: col_get_timev_e32 !! %get specific, one timev element, int32 indices.
        procedure, private :: col_get_timev_e64 !! %get specific, one timev element, int64 indices.
        procedure, private :: col_get_tsv_e32 !! %get specific, one tsv element, int32 indices.
        procedure, private :: col_get_tsv_e64 !! %get specific, one tsv element, int64 indices.
        !> Copies one row's value into the caller's variable, widening exactly as the table's
        !! own `%get_element` does. No name, no lookup -- the handle already knows the slot.
        !! Given `e` as well, copies ONE ELEMENT of that row without materialising the rest,
        !! which the name form cannot do at all.
        generic :: get => col_get_i32_i32, col_get_i32_i64, col_get_i64_i32, col_get_i64_i64, col_get_f32_i32, col_get_f32_i64, &
            col_get_f64_i32, col_get_f64_i64, col_get_bool_i32, col_get_bool_i64, col_get_str_i32, col_get_str_i64, &
            col_get_date_i32, col_get_date_i64, col_get_time_i32, col_get_time_i64, col_get_ts_i32, col_get_ts_i64, &
            col_get_i32v_i32, col_get_i32v_i64, col_get_i64v_i32, col_get_i64v_i64, col_get_f32v_i32, col_get_f32v_i64, &
            col_get_f64v_i32, col_get_f64v_i64, col_get_boolv_i32, col_get_boolv_i64, col_get_strv_i32, col_get_strv_i64, &
            col_get_datev_i32, col_get_datev_i64, col_get_timev_i32, col_get_timev_i64, col_get_tsv_i32, col_get_tsv_i64, &
            col_get_i32v_e32, col_get_i32v_e64, col_get_i64v_e32, col_get_i64v_e64, col_get_f32v_e32, col_get_f32v_e64, &
            col_get_f64v_e32, col_get_f64v_e64, col_get_boolv_e32, col_get_boolv_e64, col_get_strv_e32, col_get_strv_e64, &
            col_get_datev_e32, col_get_datev_e64, col_get_timev_e32, col_get_timev_e64, col_get_tsv_e32, col_get_tsv_e64
        procedure, private :: col_set_i32_i32 !! %set specific, i32 value, int32 row index.
        procedure, private :: col_set_i32_i64 !! %set specific, i32 value, int64 row index.
        procedure, private :: col_set_i64_i32 !! %set specific, i64 value, int32 row index.
        procedure, private :: col_set_i64_i64 !! %set specific, i64 value, int64 row index.
        procedure, private :: col_set_f32_i32 !! %set specific, f32 value, int32 row index.
        procedure, private :: col_set_f32_i64 !! %set specific, f32 value, int64 row index.
        procedure, private :: col_set_f64_i32 !! %set specific, f64 value, int32 row index.
        procedure, private :: col_set_f64_i64 !! %set specific, f64 value, int64 row index.
        procedure, private :: col_set_bool_i32 !! %set specific, bool value, int32 row index.
        procedure, private :: col_set_bool_i64 !! %set specific, bool value, int64 row index.
        procedure, private :: col_set_str_i32 !! %set specific, str value, int32 row index.
        procedure, private :: col_set_str_i64 !! %set specific, str value, int64 row index.
        procedure, private :: col_set_date_i32 !! %set specific, date value, int32 row index.
        procedure, private :: col_set_date_i64 !! %set specific, date value, int64 row index.
        procedure, private :: col_set_time_i32 !! %set specific, time value, int32 row index.
        procedure, private :: col_set_time_i64 !! %set specific, time value, int64 row index.
        procedure, private :: col_set_ts_i32 !! %set specific, ts value, int32 row index.
        procedure, private :: col_set_ts_i64 !! %set specific, ts value, int64 row index.
        procedure, private :: col_set_i32v_i32 !! %set specific, i32v value, int32 row index.
        procedure, private :: col_set_i32v_i64 !! %set specific, i32v value, int64 row index.
        procedure, private :: col_set_i64v_i32 !! %set specific, i64v value, int32 row index.
        procedure, private :: col_set_i64v_i64 !! %set specific, i64v value, int64 row index.
        procedure, private :: col_set_f32v_i32 !! %set specific, f32v value, int32 row index.
        procedure, private :: col_set_f32v_i64 !! %set specific, f32v value, int64 row index.
        procedure, private :: col_set_f64v_i32 !! %set specific, f64v value, int32 row index.
        procedure, private :: col_set_f64v_i64 !! %set specific, f64v value, int64 row index.
        procedure, private :: col_set_boolv_i32 !! %set specific, boolv value, int32 row index.
        procedure, private :: col_set_boolv_i64 !! %set specific, boolv value, int64 row index.
        procedure, private :: col_set_strv_i32 !! %set specific, strv value, int32 row index.
        procedure, private :: col_set_strv_i64 !! %set specific, strv value, int64 row index.
        procedure, private :: col_set_datev_i32 !! %set specific, datev value, int32 row index.
        procedure, private :: col_set_datev_i64 !! %set specific, datev value, int64 row index.
        procedure, private :: col_set_timev_i32 !! %set specific, timev value, int32 row index.
        procedure, private :: col_set_timev_i64 !! %set specific, timev value, int64 row index.
        procedure, private :: col_set_tsv_i32 !! %set specific, tsv value, int32 row index.
        procedure, private :: col_set_tsv_i64 !! %set specific, tsv value, int64 row index.
        procedure, private :: col_set_i32v_e32 !! %set specific, one i32v element, int32 indices.
        procedure, private :: col_set_i32v_e64 !! %set specific, one i32v element, int64 indices.
        procedure, private :: col_set_i64v_e32 !! %set specific, one i64v element, int32 indices.
        procedure, private :: col_set_i64v_e64 !! %set specific, one i64v element, int64 indices.
        procedure, private :: col_set_f32v_e32 !! %set specific, one f32v element, int32 indices.
        procedure, private :: col_set_f32v_e64 !! %set specific, one f32v element, int64 indices.
        procedure, private :: col_set_f64v_e32 !! %set specific, one f64v element, int32 indices.
        procedure, private :: col_set_f64v_e64 !! %set specific, one f64v element, int64 indices.
        procedure, private :: col_set_boolv_e32 !! %set specific, one boolv element, int32 indices.
        procedure, private :: col_set_boolv_e64 !! %set specific, one boolv element, int64 indices.
        procedure, private :: col_set_strv_e32 !! %set specific, one strv element, int32 indices.
        procedure, private :: col_set_strv_e64 !! %set specific, one strv element, int64 indices.
        procedure, private :: col_set_datev_e32 !! %set specific, one datev element, int32 indices.
        procedure, private :: col_set_datev_e64 !! %set specific, one datev element, int64 indices.
        procedure, private :: col_set_timev_e32 !! %set specific, one timev element, int32 indices.
        procedure, private :: col_set_timev_e64 !! %set specific, one timev element, int64 indices.
        procedure, private :: col_set_tsv_e32 !! %set specific, one tsv element, int32 indices.
        procedure, private :: col_set_tsv_e64 !! %set specific, one tsv element, int64 indices.
        !> Writes one row's value. The kind must match the column's exactly (a write never
        !! widens), and writing a value CLEARS that row's null. The TABLE is updated -- a
        !! handle is a view of it, not a copy. Given `e` as well, writes ONE ELEMENT and
        !! clears that element's null rather than the whole row's.
        generic :: set => col_set_i32_i32, col_set_i32_i64, col_set_i64_i32, col_set_i64_i64, col_set_f32_i32, col_set_f32_i64, &
            col_set_f64_i32, col_set_f64_i64, col_set_bool_i32, col_set_bool_i64, col_set_str_i32, col_set_str_i64, &
            col_set_date_i32, col_set_date_i64, col_set_time_i32, col_set_time_i64, col_set_ts_i32, col_set_ts_i64, &
            col_set_i32v_i32, col_set_i32v_i64, col_set_i64v_i32, col_set_i64v_i64, col_set_f32v_i32, col_set_f32v_i64, &
            col_set_f64v_i32, col_set_f64v_i64, col_set_boolv_i32, col_set_boolv_i64, col_set_strv_i32, col_set_strv_i64, &
            col_set_datev_i32, col_set_datev_i64, col_set_timev_i32, col_set_timev_i64, col_set_tsv_i32, col_set_tsv_i64, &
            col_set_i32v_e32, col_set_i32v_e64, col_set_i64v_e32, col_set_i64v_e64, col_set_f32v_e32, col_set_f32v_e64, &
            col_set_f64v_e32, col_set_f64v_e64, col_set_boolv_e32, col_set_boolv_e64, col_set_strv_e32, col_set_strv_e64, &
            col_set_datev_e32, col_set_datev_e64, col_set_timev_e32, col_set_timev_e64, col_set_tsv_e32, col_set_tsv_e64
        procedure, private :: col_is_null_i32  !! %is_null specific, whole row, int32 index.
        procedure, private :: col_is_null_i64  !! %is_null specific, whole row, int64 index.
        procedure, private :: col_is_null_e32  !! %is_null specific, one element, int32 indices.
        procedure, private :: col_is_null_e64  !! %is_null specific, one element, int64 indices.
        !> Whether row `i` of this column is null, or -- given `e` as well -- element `e` of it.
        !! On a *_VEC column the row form answers "ANY element of the row is null".
        generic :: is_null => col_is_null_i32, col_is_null_i64, col_is_null_e32, col_is_null_e64
        procedure, private :: col_set_null_i32  !! %set_null specific, whole row, int32 index.
        procedure, private :: col_set_null_i64  !! %set_null specific, whole row, int64 index.
        procedure, private :: col_set_null_e32  !! %set_null specific, one element, int32 indices.
        procedure, private :: col_set_null_e64  !! %set_null specific, one element, int64 indices.
        !> Marks row `i` null, or -- given `e` -- element `e` of it. Naming only a row marks
        !! every element of it, exactly as the table's own %set_null does.
        generic :: set_null => col_set_null_i32, col_set_null_i64, col_set_null_e32, col_set_null_e64
        procedure, private :: col_clear_null_i32  !! %clear_null specific, whole row, int32 index.
        procedure, private :: col_clear_null_i64  !! %clear_null specific, whole row, int64 index.
        procedure, private :: col_clear_null_e32  !! %clear_null specific, one element, int32 indices.
        procedure, private :: col_clear_null_e64  !! %clear_null specific, one element, int64 indices.
        !> Clears row `i`'s null, or -- given `e` -- element `e` of it. The stored VALUE is
        !! whatever was there; clearing a null does not write one.
        generic :: clear_null => col_clear_null_i32, col_clear_null_i64, col_clear_null_e32, col_clear_null_e64


        procedure, private :: col_ref_i32 !! %ref specific, i32 storage.
        procedure, private :: col_ref_i64 !! %ref specific, i64 storage.
        procedure, private :: col_ref_f32 !! %ref specific, f32 storage.
        procedure, private :: col_ref_f64 !! %ref specific, f64 storage.
        procedure, private :: col_ref_bool !! %ref specific, bool storage.
        procedure, private :: col_ref_date !! %ref specific, date storage.
        procedure, private :: col_ref_time !! %ref specific, time storage.
        procedure, private :: col_ref_ts !! %ref specific, ts storage.
        procedure, private :: col_ref_i32v !! %ref specific, i32v storage.
        procedure, private :: col_ref_i64v !! %ref specific, i64v storage.
        procedure, private :: col_ref_f32v !! %ref specific, f32v storage.
        procedure, private :: col_ref_f64v !! %ref specific, f64v storage.
        procedure, private :: col_ref_boolv !! %ref specific, boolv storage.
        procedure, private :: col_ref_datev !! %ref specific, datev storage.
        procedure, private :: col_ref_timev !! %ref specific, timev storage.
        procedure, private :: col_ref_tsv !! %ref specific, tsv storage.
        procedure, private :: col_ref_strcol !! %ref specific, the packed string store.
        !> Points `p` at this column's live storage -- the `%col` pointer, without the name
        !! lookup. Same rules: the kind must match exactly (a pointer never widens), a write
        !! through `p` changes the table, and REORDERING one column through its pointer
        !! breaks the table's row alignment with nothing to report it.
        generic :: ref => col_ref_i32, col_ref_i64, col_ref_f32, col_ref_f64, col_ref_bool, col_ref_date, col_ref_time, &
            col_ref_ts, col_ref_i32v, col_ref_i64v, col_ref_f32v, col_ref_f64v, col_ref_boolv, col_ref_datev, col_ref_timev, &
            col_ref_tsv, col_ref_strcol
        procedure :: is_valid => col_is_valid   !! Whether the handle is attached AND still current.
        procedure :: index => col_index         !! This column's 1-based position in the table.
        procedure :: kind => col_kind           !! This column's PK_* kind.
        procedure :: name => col_name           !! This column's name.
        procedure :: width => col_width         !! This column's values per row (1 for a scalar kind).
        procedure :: unit => col_unit           !! This column's unit string, or "".
        procedure :: residency => col_residency !! Whether this column is RES_EMPTY/RES_PARTIAL/RES_FULL.
        procedure :: set_user_populated => col_set_user_populated !! Claim this column's values as the caller's own, or unclaim.
        procedure :: is_user_populated => col_is_user_populated !! Whether this column is claimed as holding the caller's values.
        ! NO `final` -- see the type's own doc-comment. This is a decision, not an omission.
    end type parquet_table_col
    !
    ! ---- Lifecycle (parquet_tables_lifecycle) ----
    interface
        !> Opens `filename` and reads every supported column into `table`, freeing each column's
        !! Arrow buffers as it goes. Columns whose physical type this library cannot read (a
        !! foreign decimal/uint32 column, a LIST or MAP) do NOT stop the open: their slot is
        !! created and marked unsupported, they still appear in %column_names, and only an
        !! attempt to read one is an error. `table` is intent(out), so reopening the same
        !! variable frees the previous table first.
        !!
        !! `filter=`, `sort=` and `qc=` name columns in the table's own INTERNAL vocabulary -- the
        !! names %col/%get use -- which for a remapped column is NOT what the file calls it. They
        !! are translated to file names, and merged with whatever `maml=` declares, before the
        !! reader is opened. A MAML's own `extra: filter:`/`extra: sort:`/`fields: qc:` are in FILE
        !! names, because a read-in MAML describes the physical file and travels with it.
        module subroutine open_table_full(table, filename, maml, filter, sort, qc, qc_soft, use_threads, &
                sample_fraction, sample_seed)
            type(parquet_table), intent(out) :: table !! the table to fill.
            character(len=*), intent(in) :: filename  !! parquet file to open.
            character(len=*), intent(in), optional :: maml !! read-in (Role-B) MAML file describing `filename`.
            type(parquet_filter), intent(in), optional :: filter !! row filter, in INTERNAL column names.
            type(parquet_sortkey), intent(in), optional :: sort !! sort keys, in INTERNAL column names.
            type(parquet_read_qc), intent(in), optional :: qc !! read-time qc, in INTERNAL column names.
            logical, intent(in), optional :: qc_soft !! warn on a qc violation instead of aborting.
            logical, intent(in), optional :: use_threads !! forwarded to parquet_open_reader.
            real(real64), intent(in), optional :: sample_fraction !! keep each row with this probability.
            integer(int64), intent(in), optional :: sample_seed !! seed for that draw; omitted = nondeterministic.
            !! `integer(int64)` only, as everywhere in this library: a literal is `42_int64`.
        end subroutine open_table_full
        !> Slice-regime open, int32 row bounds -- see the `parquet_open_table` generic above, which
        !! also explains why there is no `sort` argument here and what `filter=`/`sample_fraction=`
        !! do to the slice's row count.
        module subroutine open_table_slice_i32(table, filename, row_lo, row_hi, maml, filter, qc, &
                qc_soft, use_threads, sample_fraction, sample_seed)
            type(parquet_table), intent(out) :: table !! the table to fill.
            character(len=*), intent(in) :: filename  !! parquet file to open.
            integer(int32), intent(in) :: row_lo      !! first file row to cover (1-based).
            integer(int32), intent(in) :: row_hi      !! last file row to cover (inclusive).
            character(len=*), intent(in), optional :: maml !! read-in (Role-B) MAML file describing `filename`.
            type(parquet_filter), intent(in), optional :: filter !! row filter, in INTERNAL column names.
            type(parquet_read_qc), intent(in), optional :: qc !! read-time qc, in INTERNAL column names.
            logical, intent(in), optional :: qc_soft !! warn on a qc violation instead of aborting.
            logical, intent(in), optional :: use_threads !! forwarded to parquet_open_reader.
            real(real64), intent(in), optional :: sample_fraction !! keep each row with this probability.
            integer(int64), intent(in), optional :: sample_seed !! seed for that draw; omitted = nondeterministic.
            !! `integer(int64)` only, as everywhere in this library: a literal is `42_int64`.
        end subroutine open_table_slice_i32
        !> Slice-regime open, int64 row bounds -- see the `parquet_open_table` generic above.
        module subroutine open_table_slice_i64(table, filename, row_lo, row_hi, maml, filter, qc, &
                qc_soft, use_threads, sample_fraction, sample_seed)
            type(parquet_table), intent(out) :: table !! the table to fill.
            character(len=*), intent(in) :: filename  !! parquet file to open.
            integer(int64), intent(in) :: row_lo      !! first file row to cover (1-based).
            integer(int64), intent(in) :: row_hi      !! last file row to cover (inclusive).
            character(len=*), intent(in), optional :: maml !! read-in (Role-B) MAML file describing `filename`.
            type(parquet_filter), intent(in), optional :: filter !! row filter, in INTERNAL column names.
            type(parquet_read_qc), intent(in), optional :: qc !! read-time qc, in INTERNAL column names.
            logical, intent(in), optional :: qc_soft !! warn on a qc violation instead of aborting.
            logical, intent(in), optional :: use_threads !! forwarded to parquet_open_reader.
            real(real64), intent(in), optional :: sample_fraction !! keep each row with this probability.
            integer(int64), intent(in), optional :: sample_seed !! seed for that draw; omitted = nondeterministic.
            !! `integer(int64)` only, as everywhere in this library: a literal is `42_int64`.
        end subroutine open_table_slice_i64
        !> Opens a reader on `filename` with whatever read-time transform the table carries in its
        !! `read_*` components already attached -- `cache%reader` itself, or, when `rdr` is given,
        !! some other reader over the same file. One helper rather than several open calls, so that
        !! %clone's reopen and the parallel prefetch's per-thread readers cannot drift from
        !! parquet_open_table's own. That is what makes the prefetch gate's central claim -- *a
        !! freshly opened reader sees exactly what the table's reader sees* -- true by construction
        !! rather than by argument.
        !!
        !! **`rdr` is optional rather than required, and that is forced rather than chosen.**
        !! Passing `t%cache` and `t%cache%reader` to one call would associate two dummy arguments
        !! with overlapping storage, which Fortran forbids as soon as either is defined. So the
        !! cache's own reader is reached through the cache, and `rdr` names any OTHER reader.
        !!
        !! **`cache%rg_bounds` is written only when `rdr` is absent**, and the two conditions are
        !! deliberately the same one rather than a separate argument. Those bounds describe the
        !! cache's own reader, so only the open that creates that reader may write them; a
        !! per-thread open must not, both because the bounds are already correct and because
        !! several threads writing one component of a shared cache is a data race. Deriving it from
        !! `present(rdr)` rather than taking a `set_bounds` flag removes the only way to get it
        !! wrong.
        !!
        !! Each half is passed through an ALLOCATABLE local left unallocated when that half is
        !! empty: an unallocated allocatable actual makes an optional dummy absent (F2018
        !! 15.5.2.12), so one unconditional call covers every combination -- and a table with no
        !! transform at all reaches parquet_open_reader with exactly the arguments it always did.
        !!
        !! **A masked slice is the one case where the filter is NOT a constructor argument**: it
        !! carries the slice's own row range, which only `parquet_reader_set_filter` can express,
        !! so it is attached immediately after the open instead. That whole sequence lives here
        !! rather than in the caller for the same reason the rest does -- so `%clone`'s reopen
        !! cannot produce a reader in a different state from the one `parquet_open_table` built.
        !! Requires `cache%slice_row_lo`/`slice_row_hi` and `cache%rg_bounds_physical` to be set
        !! already, and leaves `cache%rg_bounds` holding the resulting per-row-group survivor
        !! counts.
        module subroutine table_open_reader_with_transform(cache, filename, rdr, use_threads)
            type(parquet_table_cache), intent(inout), target :: cache !! the table's store, holding the transform.
            character(len=*), intent(in) :: filename    !! parquet file to open.
            type(parquet_reader), intent(inout), optional, target :: rdr !! reader to open instead of cache%reader.
            logical, intent(in), optional :: use_threads !! forwarded to parquet_open_reader.
        end subroutine table_open_reader_with_transform
        !> The inclusive 1-based row-group range covering rows `row_lo..row_hi` of `bounds`, or
        !! 0/0 when no row group intersects that range at all.
        !!
        !! One helper rather than a scan written out at each site: the same walk decides which row
        !! groups a slice's filter is scoped to and which ones a deferred column's width is
        !! measured over, and the two must agree.
        module subroutine rg_covering_range(bounds, row_lo, row_hi, rg_lo, rg_hi)
            integer(int64), intent(in) :: bounds(:,:) !! (2, nrg) row ranges, in the same coordinates as row_lo/hi.
            integer(int64), intent(in) :: row_lo      !! first row wanted.
            integer(int64), intent(in) :: row_hi      !! last row wanted.
            integer(int64), intent(out) :: rg_lo      !! first covering row group, or 0 for none.
            integer(int64), intent(out) :: rg_hi      !! last covering row group, or 0 for none.
        end subroutine rg_covering_range
        !> Prepares an empty in-memory table with no columns and no rows. The first %add_column
        !! fixes the row count; every later one must match it.
        module subroutine parquet_new_table(table)
            type(parquet_table), intent(out) :: table !! the table to initialize.
        end subroutine parquet_new_table
        !> Reports each row group of `filename` as the inclusive 1-based row range it covers:
        !! `bounds(1, rg)` is its first row and `bounds(2, rg)` its last. Together they partition
        !! 1..nrows exactly.
        !!
        !! Standalone on purpose: this is the PLANNING call, made before any table exists, so
        !! that each thread can work out which slice to open. It opens and closes a reader
        !! internally, which costs only a footer read -- no column data is touched.
        module subroutine parquet_table_row_group_bounds(filename, bounds)
            character(len=*), intent(in) :: filename                     !! parquet file to inspect.
            integer(int64), allocatable, intent(out) :: bounds(:,:)      !! (2, num_row_groups).
        end subroutine parquet_table_row_group_bounds
        !> The same row ranges for an already-open table, without reopening the file, in THIS
        !! TABLE's own row numbering -- or, with `physical=.true.`, in the file's.
        !!
        !! The two differ only when the table does not hold every row of the file: a slice, a
        !! filter, a sample. Then `bounds(1, rg)`/`bounds(2, rg)` are the rows OF THIS TABLE that
        !! came from row group `rg`, which is what relates a row index in hand to the row group it
        !! was read from; `physical=.true.` answers in the file's numbering instead, which is what
        !! `parquet_table_row_group_bounds` and every slice bound are expressed in, and so is what
        !! to use for planning the next slice.
        !!
        !! Both forms have ONE ENTRY PER PHYSICAL ROW GROUP and are index-aligned, so the two can be
        !! read side by side ("row group 7 holds file rows A..B, which are my rows C..D"). A row
        !! group contributing no rows to this table -- outside the slice, or filtered away entirely
        !! -- is reported as an EMPTY range, `bounds(1, rg) > bounds(2, rg)`, rather than dropped;
        !! dropping it would break the alignment that makes the pairing possible.
        module subroutine table_row_group_bounds(self, bounds, physical)
            class(parquet_table), intent(in) :: self                     !! the table.
            integer(int64), allocatable, intent(out) :: bounds(:,:)      !! (2, num_row_groups).
            logical, intent(in), optional :: physical                    !! .true. for the file's own row numbering.
        end subroutine table_row_group_bounds
        !> Fills `bounds` from an open reader: the shared walk both public forms sit on.
        module subroutine reader_row_group_bounds(reader, bounds)
            type(parquet_reader), intent(in) :: reader                   !! open reader.
            integer(int64), allocatable, intent(out) :: bounds(:,:)      !! (2, num_row_groups).
        end subroutine reader_row_group_bounds
        !> Always error stops: see the `assignment(=)` binding.
        !!
        !! `rhs` is `class`, not `type`, so that an EXTENDING type's own `b = a` reaches this guard
        !! too. With a `type(parquet_table)` dummy the match relies on generic resolution against a
        !! non-polymorphic dummy for an actual of an extended type; gfortran 15.2 and flang 22.1
        !! both resolve it, but the polymorphic form removes the question -- and the failure it
        !! would hide is the worst kind, two tables sharing one store and double-freeing it.
        module subroutine table_assign_guard(lhs, rhs)
            class(parquet_table), intent(out) :: lhs !! unused -- this procedure never returns.
            class(parquet_table), intent(in) :: rhs  !! unused -- this procedure never returns.
        end subroutine table_assign_guard
        !> Moves one descriptor slot's contents into another, leaving the source slot empty.
        !!
        !! The metadata fields are plain scalars and short allocatable strings, so they are
        !! assigned; `values` is handed over with `%move_from`, which is the whole reason this
        !! exists -- intrinsic assignment on a `parquet_table_column` deep-copies the column's
        !! entire storage, so relocating a slot used to cost a full copy of its data.
        module subroutine move_table_column(dst, src)
            type(parquet_table_column), intent(inout) :: dst !! the slot receiving the column.
            type(parquet_table_column), intent(inout) :: src !! the slot giving it up.
        end subroutine move_table_column
        !> Appends an empty slot named `name` and returns its index, growing `cols(:)` if the
        !! headroom is used up. error stops if the name is already taken and `force` is absent.
        module subroutine table_new_slot(self, name, force, idx)
            !> Deliberately `intent(in)`, not `intent(inout)`: every change it makes is to
            !! `self%cache`, which is a POINTER component, so it needs no more than this -- and
            !! the automatic `parquet_row_index` column has to be created from the lazy read
            !! path, where `self` is `intent(in)` like every other first-touch entry point.
            class(parquet_table), intent(in) :: self
            character(len=*), intent(in) :: name        !! the new column's name.
            logical, intent(in), optional :: force      !! .true. replaces an existing same-named column.
            integer, intent(out) :: idx                 !! 1-based index of the slot to fill.
        end subroutine table_new_slot
        !> Fixes or checks the table's row count when a column of `n` rows is added: the first
        !! column sets it, every later one must match. error stops on a mismatch.
        module subroutine table_fix_nrows(self, name, n)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column being added (for the message).
            integer(int64), intent(in) :: n             !! that column's row count.
        end subroutine table_fix_nrows
    end interface
    !
    ! ---- Introspection (parquet_tables_query) ----
    interface
        !> This table's row scope and detach state, as the single value a first touch takes.
        pure module function table_scope_of(self) result(sc)
            class(parquet_table), intent(in) :: self !! the table.
            type(table_scope) :: sc                  !! its scope.
        end function table_scope_of
        !> Number of rows every column of this table holds.
        module function table_nrows(self) result(n)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64) :: n                      !! row count.
        end function table_nrows
        !> Rows this table covers BEFORE `filter=`/`sample_fraction=` removed any -- the slice's
        !! own length in the slice regime, and the file's row count for a whole-file table.
        !!
        !! `%nrows()` is what the table holds; this is what it was cut from, which nothing else
        !! can report once a transform is active: a filtered reader answers in survivors, so
        !! asking it afterwards gives the same number `%nrows()` already gave. Captured when the
        !! table opens, so it keeps answering after a row mutation has detached the table.
        !!
        !! Equal to `%nrows()` when no filter or sample is in play. 0 for a table built in memory,
        !! which was not cut from anything.
        module function table_nrows_unfiltered(self) result(n)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64) :: n                      !! rows before the transform.
        end function table_nrows_unfiltered
        !> Rows in the row groups this table covers, which is what reading it actually costs.
        !!
        !! A slice is read by row group, so a slice of 10 rows straddling two 100k-row groups
        !! decodes 200k rows to produce them. This reports that number, so a caller choosing
        !! slice boundaries can see when a slice is not paying for itself.
        !!
        !! Equal to `%nrows_unfiltered()` when the slice lines up with row-group boundaries, and
        !! for a whole-file table always. 0 for a table built in memory.
        module function table_row_group_extent(self) result(n)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64) :: n                      !! rows in the covering row groups.
        end function table_row_group_extent
        !> Number of columns this table has, or only the resident ones with `resident_only`.
        module function table_ncols(self, resident_only) result(n)
            class(parquet_table), intent(in) :: self !! the table.
            logical, intent(in), optional :: resident_only !! .true.: count only columns already read.
            integer :: n                             !! column count.
        end function table_ncols
        !> Copies out every column's name, in file/insertion order, blank-padded to the longest.
        !!
        !! `resident_only=.true.` reports only the columns that have been read, in the same order,
        !! which is how a caller finds out what a lazy table is actually holding.
        module subroutine table_column_names(self, names, resident_only)
            class(parquet_table), intent(in) :: self                  !! the table.
            character(len=:), allocatable, intent(out) :: names(:)    !! one entry per column.
            logical, intent(in), optional :: resident_only            !! .true.: only columns already read.
        end subroutine table_column_names
        !> Whether a column holds any null value.
        !!
        !! Answered as cheaply as the column's state allows, which is the point of having it: a
        !! RESIDENT column answers from its own validity state, and a non-resident file-backed one
        !! answers from the FILE'S FOOTER STATISTICS, reading no column data at all. The footer
        !! answer is conservative -- `.false.` is a guarantee, `.true.` means "may have nulls",
        !! since a file written without statistics cannot say -- and reading the column afterwards
        !! may therefore turn a `.true.` into a `.false.`.
        module function table_has_nulls(self, name, found) result(any_null)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            logical :: any_null                       !! .true. if it holds (or may hold) a null.
        end function table_has_nulls
        !> Whether a column holds (or may hold) nulls, by 1-based position.
        module function table_has_nulls_at(self, j, found) result(any_null)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(in) :: j                  !! 1-based column position.
            logical, intent(out), optional :: found   !! present: report an out-of-range j instead of aborting.
            logical :: any_null                       !! .true. if it holds (or may hold) a null.
        end function table_has_nulls_at
        !> Copies out a column's per-ROW validity as a plain logical array: .true. where the row
        !! holds a value, .false. where it is null.
        !!
        !! One entry per row of the table. On a *_VEC column this is the SUMMARY -- a row is
        !! `.false.` when any element of it is null -- which is a genuinely useful question
        !! ("which rows are complete?") and is why the rank-1 form is kept alongside the rank-2
        !! one. For the true per-element state, declare `mask` rank-2 instead.
        !!
        !! A column with no nulls at all comes back all `.true.` rather than unallocated, so a
        !! caller never has to test for that case. Triggers the same lazy first touch any other
        !! value access does.
        module subroutine table_get_valid_mask(self, name, mask, found)
            class(parquet_table), intent(in) :: self               !! the table (fills through %cache).
            character(len=*), intent(in) :: name                   !! column name.
            logical, allocatable, intent(out) :: mask(:)           !! one entry per row; .true. = value.
            logical, intent(out), optional :: found                !! present: report a miss instead of aborting.
        end subroutine table_get_valid_mask
        !> Copies out a column's per-ELEMENT validity as a `(width, nrows)` logical array.
        !!
        !! The rank-2 counterpart of the above, and the column's actual state rather than a row
        !! summary. On a scalar column `width` is 1, so the two agree.
        !!
        !! **Note the memory**: `LOGICAL` is 4 bytes under gfortran, so a wide column's mask is
        !! `4*width*nrows` bytes. Use `%is_null(name, i, e)` to ask about a few elements.
        module subroutine table_get_valid_mask_elem(self, name, mask, found)
            class(parquet_table), intent(in) :: self               !! the table (fills through %cache).
            character(len=*), intent(in) :: name                   !! column name.
            logical, allocatable, intent(out) :: mask(:,:)         !! (element, row); .true. = value.
            logical, intent(out), optional :: found                !! present: report a miss instead of aborting.
        end subroutine table_get_valid_mask_elem
        !> A counter bumped by every structural change to this table, for a caller holding a
        !! pointer across a call that might have invalidated it.
        !!
        !! **A stale `%col` pointer cannot be detected by Fortran, and this library cannot detect
        !! it either.** What it can do is tell you whether anything structural happened: take the
        !! generation before, compare it after, and re-fetch the pointer if it moved. The counter
        !! is deliberately conservative -- every column- and row-structural entry point bumps it,
        !! whether or not that particular call actually relocated anything -- because a missed bump
        !! would give false confidence, while a spare one only costs a re-fetch. A call that
        !! changes nothing at all (see the no-detach rule) does not bump it.
        module function table_generation(self) result(g)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64) :: g                      !! current generation.
        end function table_generation
        !> Whether a column of this name exists (supported or not).
        module function table_has_column(self, name) result(found)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            logical :: found                         !! .true. if the table has it.
        end function table_has_column
        !> Which of `names` the table does not have, as a packed array sized to the longest one
        !! reported -- zero-size, `len=1`, when nothing is missing, so `size(absent) == 0` is the
        !! test. Reads nothing: this is a metadata query, and a column's existence is a property
        !! of the descriptor.
        !!
        !! Matching is EXACT, as `%has_column`'s is, never by struct-path prefix the way
        !! `%prefetch` accepts one -- a required column is a specific column. Name a struct leaf
        !! in full, or give it an internal name with a MAML `col_map:` remap.
        !!
        !! `parquet_row_index` counts as present on a file-backed table even before anything has
        !! asked for it, exactly as `%has_column` reports it: the question is "can I use this
        !! name?", and the answer is yes until the table detaches.
        module subroutine missing_columns_array(self, names, absent)
            class(parquet_table), intent(in) :: self               !! the table.
            character(len=*), intent(in) :: names(:)               !! column names to look for.
            character(len=:), allocatable, intent(out) :: absent(:) !! the ones that are not there.
        end subroutine missing_columns_array
        !> %missing_columns over one string of names separated by commas and/or semicolons
        !! ("ra;dec, mag"), split by the same tokenizer %prefetch and parquet_prefetch_columns use.
        module subroutine missing_columns_string(self, names, absent)
            class(parquet_table), intent(in) :: self               !! the table.
            character(len=*), intent(in) :: names                  !! names, comma/semicolon separated.
            character(len=:), allocatable, intent(out) :: absent(:) !! the ones that are not there.
        end subroutine missing_columns_string
        !> Aborts unless the table has every one of `names`, naming **every** missing column
        !! rather than only the first -- which is the whole point, since a hand-written
        !! `%has_column` loop reports one name per run and a caller then fixes them one at a time.
        !!
        !! The message also echoes what was ASKED for, which is how it carries context without
        !! taking a caller-supplied message string; both lists are truncated to a preview, because
        !! interpolating unbounded caller text into an `error stop` corrupts the heap on ifx.
        !!
        !! Same exact matching and same `parquet_row_index` rule as `%missing_columns`, which does
        !! the work.
        module subroutine require_columns_array(self, names)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: names(:) !! column names that must all exist.
        end subroutine require_columns_array
        !> %require_columns over one string of names separated by commas and/or semicolons.
        module subroutine require_columns_string(self, names)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: names    !! names, comma/semicolon separated.
        end subroutine require_columns_string
        !> A column's 1-based position among the table's columns, or 0 when there is no such
        !! column. The inverse of `%column_name`, and the cheap way to hoist a lookup out of a
        !! loop that then queries the same column by position.
        module function table_column_index(self, name, found) result(j)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            integer :: j                              !! 1-based position, or 0 on a reported miss.
        end function table_column_index
        !> Copies out the name of the column at 1-based position `j`. The inverse of
        !! `%column_index`, and what makes a `do j = 1, t%ncols()` sweep able to report itself.
        module subroutine table_column_name(self, j, nm, found)
            class(parquet_table), intent(in) :: self             !! the table.
            integer, intent(in) :: j                             !! 1-based column position.
            character(len=:), allocatable, intent(out) :: nm     !! the column's name, or "".
            logical, intent(out), optional :: found              !! present: report an out-of-range j instead of aborting.
        end subroutine table_column_name
        !> A column's PK_* kind discriminator (PK_NONE for an unsupported column).
        module function table_column_kind(self, name, found) result(k)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            integer :: k                              !! the PK_* constant.
        end function table_column_kind
        !> A column's PK_* kind discriminator, by 1-based position.
        module function table_column_kind_at(self, j, found) result(k)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(in) :: j                  !! 1-based column position.
            logical, intent(out), optional :: found   !! present: report an out-of-range j instead of aborting.
            integer :: k                              !! the PK_* constant.
        end function table_column_kind_at
        !> A column's values-per-row: 1 for a scalar kind, the vector width for a *_VEC kind.
        module function table_column_width(self, name, found) result(wdt)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            integer :: wdt                            !! values per row.
        end function table_column_width
        !> A column's values-per-row, by 1-based position.
        module function table_column_width_at(self, j, found) result(wdt)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(in) :: j                  !! 1-based column position.
            logical, intent(out), optional :: found   !! present: report an out-of-range j instead of aborting.
            integer :: wdt                            !! values per row.
        end function table_column_width_at
        !> Copies out a column's unit string ("" when it has none).
        module subroutine table_column_unit(self, name, u, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            character(len=:), allocatable, intent(out) :: u      !! the unit, or "".
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine table_column_unit
        !> Copies out a column's unit string, by 1-based position.
        module subroutine table_column_unit_at(self, j, u, found)
            class(parquet_table), intent(in) :: self             !! the table.
            integer, intent(in) :: j                             !! 1-based column position.
            character(len=:), allocatable, intent(out) :: u      !! the unit, or "".
            logical, intent(out), optional :: found              !! present: report an out-of-range j instead of aborting.
        end subroutine table_column_unit_at
        !> A column's residency: RES_FULL once read, RES_EMPTY for an unsupported column.
        module function table_column_residency(self, name, found) result(r)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            integer :: r                              !! the RES_* constant.
        end function table_column_residency
        !> A column's RES_* residency state, by 1-based position.
        module function table_column_residency_at(self, j, found) result(r)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(in) :: j                  !! 1-based column position.
            logical, intent(out), optional :: found   !! present: report an out-of-range j instead of aborting.
            integer :: r                              !! the RES_* constant.
        end function table_column_residency_at
        !> Whether a column's physical TYPE is one this library can read. It answers from the
        !> schema alone and reads no data, so for a plain LIST/LARGE_LIST column it is a
        !> statement about the element type, not a promise that the read will succeed: such a
        !> column carries no width in the schema, and whether one uniform width exists is a
        !> property of the DATA. A `list<int32>` whose rows all hold 3 elements is an ordinary
        !> vector column of width 3; one whose rows differ in length is rejected when it is read.
        !> Both answer .true. here. Every other type is settled by the schema, so for them this
        !> does predict the read. Use %width (which resolves a deferred width for real) when the
        !> distinction matters.
        module function table_is_supported(self, name, found) result(ok)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            logical :: ok                             !! .true. if the column's TYPE is readable.
        end function table_is_supported
        !> Whether a column's type can be read, by 1-based position -- see table_is_supported
        !> above for the plain-LIST caveat.
        module function table_is_supported_at(self, j, found) result(ok)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(in) :: j                  !! 1-based column position.
            logical, intent(out), optional :: found   !! present: report an out-of-range j instead of aborting.
            logical :: ok                             !! .true. if the column's TYPE is readable.
        end function table_is_supported_at
        !> Whether the table has been detached from its file by a row-structural mutation.
        module function table_is_detached(self) result(d)
            class(parquet_table), intent(in) :: self !! the table.
            logical :: d                             !! .true. once detached.
        end function table_is_detached
        !> Copies out the file this table was opened from ("" for an in-memory table).
        module subroutine table_filename(self, fname)
            class(parquet_table), intent(in) :: self            !! the table.
            character(len=:), allocatable, intent(out) :: fname !! the file name, or "".
        end subroutine table_filename
        !> Reads one key from the source file's table metadata. `found` reports a missing key.
        module subroutine table_get_file_metadata(self, key, value, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: key                  !! metadata key.
            character(len=:), allocatable, intent(out) :: value  !! the value, or "".
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine table_get_file_metadata
        !> Whether row `i` of a column is null (int32 row index).
        module function table_is_null_i32(self, name, i, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer(int32), intent(in) :: i          !! 1-based row index.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
            logical :: isnull                        !! .true. if that row is null (.false. on a miss).
        end function table_is_null_i32
        !> Whether element `e` of row `i` is null (int32 indices).
        module function table_is_null_e32(self, name, i, e, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer(int32), intent(in) :: i          !! 1-based row index.
            integer(int32), intent(in) :: e          !! 1-based element index within the row.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
            logical :: isnull                        !! .true. if that element is null (.false. on a miss).
        end function table_is_null_e32
        !> Whether element `e` of row `i` is null (int64 indices).
        module function table_is_null_e64(self, name, i, e, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer(int64), intent(in) :: i          !! 1-based row index.
            integer(int64), intent(in) :: e          !! 1-based element index within the row.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
            logical :: isnull                        !! .true. if that element is null (.false. on a miss).
        end function table_is_null_e64
        !> Whether row `i` of a column is null (int64 row index).
        module function table_is_null_i64(self, name, i, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer(int64), intent(in) :: i          !! 1-based row index.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
            logical :: isnull                        !! .true. if that row is null (.false. on a miss).
        end function table_is_null_i64
        !> Whether row `i` of the column at 1-based position `j` is null.
        module function table_is_null_at_i32(self, j, i, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: j                 !! 1-based column position.
            integer(int32), intent(in) :: i          !! 1-based row index.
            logical, intent(out), optional :: found  !! present: report an out-of-range j instead of aborting.
            logical :: isnull                        !! .true. if null (.false. on a miss).
        end function table_is_null_at_i32
        !> Whether row `i` of the column at 1-based position `j` is null.
        module function table_is_null_at_i64(self, j, i, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: j                 !! 1-based column position.
            integer(int64), intent(in) :: i          !! 1-based row index.
            logical, intent(out), optional :: found  !! present: report an out-of-range j instead of aborting.
            logical :: isnull                        !! .true. if null (.false. on a miss).
        end function table_is_null_at_i64
        !> Whether element `e` of row `i` of the column at 1-based position `j` is null.
        module function table_is_null_at_e32(self, j, i, e, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: j                 !! 1-based column position.
            integer(int32), intent(in) :: i          !! 1-based row index.
            integer(int32), intent(in) :: e          !! 1-based element index within the row.
            logical, intent(out), optional :: found  !! present: report an out-of-range j instead of aborting.
            logical :: isnull                        !! .true. if null (.false. on a miss).
        end function table_is_null_at_e32
        !> Whether element `e` of row `i` of the column at 1-based position `j` is null.
        module function table_is_null_at_e64(self, j, i, e, found) result(isnull)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: j                 !! 1-based column position.
            integer(int64), intent(in) :: i          !! 1-based row index.
            integer(int64), intent(in) :: e          !! 1-based element index within the row.
            logical, intent(out), optional :: found  !! present: report an out-of-range j instead of aborting.
            logical :: isnull                        !! .true. if null (.false. on a miss).
        end function table_is_null_at_e64
        !> error stops unless `i` is a valid 1-based row index for this table. Shared by every
        !! per-row entry point so they all report the same way.
        module subroutine table_require_row(self, i, proc)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64), intent(in) :: i          !! the row index to check.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_require_row
        !> Shared front half of every soft-failing query: resolves `name`, honouring `found=` and
        !! otherwise aborting. Unlike `table_resolve` this does NOT require the column to hold
        !! values -- asking a column's kind, or dropping it, must work precisely when it has none.
        module subroutine table_lookup_or_fail(self, name, proc, idx, found)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
            integer, intent(out) :: idx              !! slot index, or 0 on a reported miss.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
        end subroutine table_lookup_or_fail
        !> A resolved handle on the named column. Honours `found=` exactly as every other
        !! name-taking query does; on a reported miss the handle comes back detached, so
        !! `%is_valid()` is `.false.` and using it aborts.
        module subroutine column_by_name(self, name, c, found)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: name              !! column name.
            type(parquet_table_col), intent(out) :: c         !! the resolved handle.
            logical, intent(out), optional :: found           !! present: report a miss instead of aborting.
        end subroutine column_by_name
        !> A resolved handle on the column at 1-based position `j`.
        module subroutine column_by_index(self, j, c, found)
            class(parquet_table), intent(in) :: self          !! the table.
            integer, intent(in) :: j                          !! 1-based column position.
            type(parquet_table_col), intent(out) :: c         !! the resolved handle.
            logical, intent(out), optional :: found           !! present: report an out-of-range j instead of aborting.
        end subroutine column_by_index
        !> Resolves `name` straight into a handle, carrying the CALLER's procedure name so the
        !! messages a delegating `%get_element` produces are the ones it always produced. This is
        !! the entry `%column` itself uses, with `proc` = "column".
        module subroutine table_resolve_to_handle(self, name, proc, c, found)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: name              !! column name.
            character(len=*), intent(in) :: proc              !! calling procedure, for the message.
            type(parquet_table_col), intent(out) :: c         !! the resolved handle.
            logical, intent(out), optional :: found           !! present: report a miss instead of aborting.
        end subroutine table_resolve_to_handle
        !> Whether the handle is attached to a table AND still current -- one predicate, because
        !! a caller can do nothing useful with a handle that is one and not the other.
        module function col_is_valid(self) result(ok)
            class(parquet_table_col), intent(in) :: self !! the handle.
            logical :: ok                                !! .true. when it can still be used.
        end function col_is_valid
        !> This column's 1-based position in the table.
        module function col_index(self) result(j)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer :: j                                 !! 1-based position.
        end function col_index
        !> This column's PK_* kind, as resolved when the handle was made.
        module function col_kind(self) result(k)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer :: k                                 !! the PK_* constant.
        end function col_kind
        !> Whether that row is null (handle form, i32 indices).
        module function col_is_null_i32(self, i) result(isnull)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            logical :: isnull                        !! .true. if null.
        end function col_is_null_i32
        !> Whether that row is null (handle form, i64 indices).
        module function col_is_null_i64(self, i) result(isnull)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            logical :: isnull                        !! .true. if null.
        end function col_is_null_i64
        !> Whether that element is null (handle form, e32 indices).
        module function col_is_null_e32(self, i, e) result(isnull)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            logical :: isnull                        !! .true. if null.
        end function col_is_null_e32
        !> Whether that element is null (handle form, e64 indices).
        module function col_is_null_e64(self, i, e) result(isnull)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            logical :: isnull                        !! .true. if null.
        end function col_is_null_e64
        !> Marks that row's null (handle form, i32 indices).
        module subroutine col_set_null_i32(self, i)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
        end subroutine col_set_null_i32
        !> Marks that row's null (handle form, i64 indices).
        module subroutine col_set_null_i64(self, i)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
        end subroutine col_set_null_i64
        !> Marks that element's null (handle form, e32 indices).
        module subroutine col_set_null_e32(self, i, e)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
        end subroutine col_set_null_e32
        !> Marks that element's null (handle form, e64 indices).
        module subroutine col_set_null_e64(self, i, e)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
        end subroutine col_set_null_e64
        !> Clears that row's null (handle form, i32 indices).
        module subroutine col_clear_null_i32(self, i)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
        end subroutine col_clear_null_i32
        !> Clears that row's null (handle form, i64 indices).
        module subroutine col_clear_null_i64(self, i)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
        end subroutine col_clear_null_i64
        !> Clears that element's null (handle form, e32 indices).
        module subroutine col_clear_null_e32(self, i, e)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
        end subroutine col_clear_null_e32
        !> Clears that element's null (handle form, e64 indices).
        module subroutine col_clear_null_e64(self, i, e)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
        end subroutine col_clear_null_e64
        !> Aborts unless the handle is attached and current, naming the remedy -- the cause of a
        !! stale handle is usually several statements away from where it is noticed.
        module subroutine col_resolve(self, proc)
            class(parquet_table_col), intent(in) :: self !! the handle.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
        end subroutine col_resolve
        !> This column's name.
        module subroutine col_name(self, nm)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            character(len=:), allocatable, intent(out) :: nm !! receives the name.
        end subroutine col_name
        !> This column's values per row -- 1 for a scalar kind, the vector length otherwise.
        module function col_width(self) result(wdt)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer :: wdt                               !! values per row.
        end function col_width
        !> This column's unit string, or "" when it has none.
        module subroutine col_unit(self, u)
            class(parquet_table_col), intent(in) :: self    !! the handle.
            character(len=:), allocatable, intent(out) :: u !! receives the unit, or "".
        end subroutine col_unit
        !> Whether this column is RES_EMPTY, RES_PARTIAL or RES_FULL.
        module function col_residency(self) result(r)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer :: r                                 !! one of the RES_* constants.
        end function col_residency
        !> Marks this column as holding values the CALLER wrote, or clears that mark.
        !!
        !! The handle form of `%set_user_populated`, and the one the case it exists for actually
        !! reaches for: `%ref` is a handle method, so a caller who edits through the pointer it
        !! hands out already has the handle. See parquet_table%set_user_populated for the rule.
        module subroutine col_set_user_populated(self, flag)
            class(parquet_table_col), intent(in) :: self !! the handle.
            logical, intent(in) :: flag                  !! .true. = the caller's own values; .false. = the file's.
        end subroutine col_set_user_populated
        !> Whether this column is marked as holding values the caller wrote rather than the file's.
        module function col_is_user_populated(self) result(ok)
            class(parquet_table_col), intent(in) :: self !! the handle.
            logical :: ok                                !! .true. when the slot holds the caller's own writes.
        end function col_is_user_populated
        !> Aborts unless `i` is a valid 1-based row index for the handle's own row scope.
        module subroutine col_require_row(self, i, proc)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! the row index to check.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
        end subroutine col_require_row
        !> Aborts unless `e` is a valid 1-based element index within one of this column's rows.
        !!
        !! Not declared here on purpose: `parquet_column%get_elem`/`%set_elem` run `check_element`
        !! themselves, and its message already names the ELEMENT axis rather than reading like an
        !! out-of-range row. A second check in this layer would be a second copy of the column's
        !! own width rule.
        !> Reports that this column's kind cannot serve the caller's variable.
        module subroutine col_kind_error(self, want, proc)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer, intent(in) :: want                  !! the PK_* the caller asked for.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
        end subroutine col_kind_error

        !> The shared i32 body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_i32(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int32), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_i32
        !> The shared i32 body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_i32(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int32), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_i32
        !> The shared i64 body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_i64(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int64), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_i64
        !> The shared i64 body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_i64(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int64), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_i64
        !> The shared f32 body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_f32(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real32), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_f32
        !> The shared f32 body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_f32(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real32), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_f32
        !> The shared f64 body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_f64(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real64), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_f64
        !> The shared f64 body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_f64(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real64), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_f64
        !> The shared bool body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_bool(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            logical, intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_bool
        !> The shared bool body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_bool(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            logical, intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_bool
        !> The shared str body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_str(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            character(len=:), allocatable, intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_str
        !> The shared str body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_str(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            character(len=*), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_str
        !> The shared date body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_date(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_date), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_date
        !> The shared date body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_date(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_date), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_date
        !> The shared time body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_time(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_time), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_time
        !> The shared time body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_time(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_time), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_time
        !> The shared ts body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_ts(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_timestamp), intent(out) :: value !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_ts
        !> The shared ts body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_ts(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_timestamp), intent(in) :: value !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_ts
        !> The shared i32v body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_i32v(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int32), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_i32v
        !> The shared i32v body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_i32v(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int32), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_i32v
        !> The shared i64v body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_i64v(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int64), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_i64v
        !> The shared i64v body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_i64v(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            integer(int64), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_i64v
        !> The shared f32v body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_f32v(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real32), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_f32v
        !> The shared f32v body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_f32v(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real32), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_f32v
        !> The shared f64v body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_f64v(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real64), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_f64v
        !> The shared f64v body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_f64v(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            real(real64), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_f64v
        !> The shared boolv body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_boolv(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            logical, allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_boolv
        !> The shared boolv body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_boolv(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            logical, intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_boolv
        !> The shared strv body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_strv(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in), target :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            character(len=:), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_strv
        !> The shared strv body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_strv(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            character(len=*), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_strv
        !> The shared datev body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_datev(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_datev
        !> The shared datev body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_datev(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_date), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_datev
        !> The shared timev body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_timev(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_timev
        !> The shared timev body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_timev(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_time), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_timev
        !> The shared tsv body behind both `%get_element(name, i, v)` and a column
        !! handle's `%get(i, v)`: the widening set, the kind error and the null rule,
        !! once. Takes the resolved pieces rather than a handle -- building one purely
        !! to pass it measured +16.3% on `%get_element`. `proc` is the CALLER's name, so
        !! each entry point keeps the messages it always produced.
        module subroutine col_fetch_tsv(cache, slot, colkind, i, value, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer, intent(in) :: colkind                 !! that slot's PK_* kind.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives the value.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_fetch_tsv
        !> The shared tsv body behind both `%set_element(name, i, v)` and a column
        !! handle's `%set(i, v)`. Exact kind, never widening -- a write that silently
        !! converted would lose information the caller did not agree to lose.
        module subroutine col_store_tsv(cache, slot, i, value, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the table's column store.
            integer, intent(in) :: slot                    !! validated slot index.
            integer(int64), intent(in) :: i                !! validated 1-based row index.
            type(parquet_timestamp), intent(in) :: value(:) !! the value to write.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine col_store_tsv
        !> Writes one row's i32 value through a handle (i32 row index).
        module subroutine col_set_i32_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            integer(int32), intent(in) :: value !! the value to write.
        end subroutine col_set_i32_i32
        !> Writes one row's i32 value through a handle (i64 row index).
        module subroutine col_set_i32_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            integer(int32), intent(in) :: value !! the value to write.
        end subroutine col_set_i32_i64
        !> Writes one row's i64 value through a handle (i32 row index).
        module subroutine col_set_i64_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            integer(int64), intent(in) :: value !! the value to write.
        end subroutine col_set_i64_i32
        !> Writes one row's i64 value through a handle (i64 row index).
        module subroutine col_set_i64_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            integer(int64), intent(in) :: value !! the value to write.
        end subroutine col_set_i64_i64
        !> Writes one row's f32 value through a handle (i32 row index).
        module subroutine col_set_f32_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            real(real32), intent(in) :: value !! the value to write.
        end subroutine col_set_f32_i32
        !> Writes one row's f32 value through a handle (i64 row index).
        module subroutine col_set_f32_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            real(real32), intent(in) :: value !! the value to write.
        end subroutine col_set_f32_i64
        !> Writes one row's f64 value through a handle (i32 row index).
        module subroutine col_set_f64_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            real(real64), intent(in) :: value !! the value to write.
        end subroutine col_set_f64_i32
        !> Writes one row's f64 value through a handle (i64 row index).
        module subroutine col_set_f64_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            real(real64), intent(in) :: value !! the value to write.
        end subroutine col_set_f64_i64
        !> Writes one row's bool value through a handle (i32 row index).
        module subroutine col_set_bool_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            logical, intent(in) :: value !! the value to write.
        end subroutine col_set_bool_i32
        !> Writes one row's bool value through a handle (i64 row index).
        module subroutine col_set_bool_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            logical, intent(in) :: value !! the value to write.
        end subroutine col_set_bool_i64
        !> Writes one row's str value through a handle (i32 row index).
        module subroutine col_set_str_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            character(len=*), intent(in) :: value !! the value to write.
        end subroutine col_set_str_i32
        !> Writes one row's str value through a handle (i64 row index).
        module subroutine col_set_str_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            character(len=*), intent(in) :: value !! the value to write.
        end subroutine col_set_str_i64
        !> Writes one row's date value through a handle (i32 row index).
        module subroutine col_set_date_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_date), intent(in) :: value !! the value to write.
        end subroutine col_set_date_i32
        !> Writes one row's date value through a handle (i64 row index).
        module subroutine col_set_date_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_date), intent(in) :: value !! the value to write.
        end subroutine col_set_date_i64
        !> Writes one row's time value through a handle (i32 row index).
        module subroutine col_set_time_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_time), intent(in) :: value !! the value to write.
        end subroutine col_set_time_i32
        !> Writes one row's time value through a handle (i64 row index).
        module subroutine col_set_time_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_time), intent(in) :: value !! the value to write.
        end subroutine col_set_time_i64
        !> Writes one row's ts value through a handle (i32 row index).
        module subroutine col_set_ts_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value !! the value to write.
        end subroutine col_set_ts_i32
        !> Writes one row's ts value through a handle (i64 row index).
        module subroutine col_set_ts_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value !! the value to write.
        end subroutine col_set_ts_i64
        !> Writes one row's i32v value through a handle (i32 row index).
        module subroutine col_set_i32v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            integer(int32), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_i32v_i32
        !> Writes one row's i32v value through a handle (i64 row index).
        module subroutine col_set_i32v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            integer(int32), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_i32v_i64
        !> Writes one row's i64v value through a handle (i32 row index).
        module subroutine col_set_i64v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            integer(int64), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_i64v_i32
        !> Writes one row's i64v value through a handle (i64 row index).
        module subroutine col_set_i64v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            integer(int64), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_i64v_i64
        !> Writes one row's f32v value through a handle (i32 row index).
        module subroutine col_set_f32v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            real(real32), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_f32v_i32
        !> Writes one row's f32v value through a handle (i64 row index).
        module subroutine col_set_f32v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            real(real32), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_f32v_i64
        !> Writes one row's f64v value through a handle (i32 row index).
        module subroutine col_set_f64v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            real(real64), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_f64v_i32
        !> Writes one row's f64v value through a handle (i64 row index).
        module subroutine col_set_f64v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            real(real64), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_f64v_i64
        !> Writes one row's boolv value through a handle (i32 row index).
        module subroutine col_set_boolv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            logical, intent(in) :: value(:) !! the value to write.
        end subroutine col_set_boolv_i32
        !> Writes one row's boolv value through a handle (i64 row index).
        module subroutine col_set_boolv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            logical, intent(in) :: value(:) !! the value to write.
        end subroutine col_set_boolv_i64
        !> Writes one row's strv value through a handle (i32 row index).
        module subroutine col_set_strv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            character(len=*), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_strv_i32
        !> Writes one row's strv value through a handle (i64 row index).
        module subroutine col_set_strv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            character(len=*), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_strv_i64
        !> Writes one row's datev value through a handle (i32 row index).
        module subroutine col_set_datev_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_date), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_datev_i32
        !> Writes one row's datev value through a handle (i64 row index).
        module subroutine col_set_datev_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_date), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_datev_i64
        !> Writes one row's timev value through a handle (i32 row index).
        module subroutine col_set_timev_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_time), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_timev_i32
        !> Writes one row's timev value through a handle (i64 row index).
        module subroutine col_set_timev_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_time), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_timev_i64
        !> Writes one row's tsv value through a handle (i32 row index).
        module subroutine col_set_tsv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i               !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_tsv_i32
        !> Writes one row's tsv value through a handle (i64 row index).
        module subroutine col_set_tsv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i               !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value(:) !! the value to write.
        end subroutine col_set_tsv_i64


        !> One row's i32 value through a handle (i32 row index).
        module subroutine col_get_i32_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(out) :: value !! receives the value.
        end subroutine col_get_i32_i32
        !> One row's i32 value through a handle (i64 row index).
        module subroutine col_get_i32_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(out) :: value !! receives the value.
        end subroutine col_get_i32_i64
        !> One row's i64 value through a handle (i32 row index).
        module subroutine col_get_i64_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(out) :: value !! receives the value.
        end subroutine col_get_i64_i32
        !> One row's i64 value through a handle (i64 row index).
        module subroutine col_get_i64_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(out) :: value !! receives the value.
        end subroutine col_get_i64_i64
        !> One row's f32 value through a handle (i32 row index).
        module subroutine col_get_f32_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            real(real32), intent(out) :: value !! receives the value.
        end subroutine col_get_f32_i32
        !> One row's f32 value through a handle (i64 row index).
        module subroutine col_get_f32_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            real(real32), intent(out) :: value !! receives the value.
        end subroutine col_get_f32_i64
        !> One row's f64 value through a handle (i32 row index).
        module subroutine col_get_f64_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            real(real64), intent(out) :: value !! receives the value.
        end subroutine col_get_f64_i32
        !> One row's f64 value through a handle (i64 row index).
        module subroutine col_get_f64_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            real(real64), intent(out) :: value !! receives the value.
        end subroutine col_get_f64_i64
        !> One row's bool value through a handle (i32 row index).
        module subroutine col_get_bool_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            logical, intent(out) :: value !! receives the value.
        end subroutine col_get_bool_i32
        !> One row's bool value through a handle (i64 row index).
        module subroutine col_get_bool_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            logical, intent(out) :: value !! receives the value.
        end subroutine col_get_bool_i64
        !> One row's str value through a handle (i32 row index).
        module subroutine col_get_str_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value !! receives the value.
        end subroutine col_get_str_i32
        !> One row's str value through a handle (i64 row index).
        module subroutine col_get_str_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value !! receives the value.
        end subroutine col_get_str_i64
        !> One row's date value through a handle (i32 row index).
        module subroutine col_get_date_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_date), intent(out) :: value !! receives the value.
        end subroutine col_get_date_i32
        !> One row's date value through a handle (i64 row index).
        module subroutine col_get_date_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_date), intent(out) :: value !! receives the value.
        end subroutine col_get_date_i64
        !> One row's time value through a handle (i32 row index).
        module subroutine col_get_time_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_time), intent(out) :: value !! receives the value.
        end subroutine col_get_time_i32
        !> One row's time value through a handle (i64 row index).
        module subroutine col_get_time_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_time), intent(out) :: value !! receives the value.
        end subroutine col_get_time_i64
        !> One row's ts value through a handle (i32 row index).
        module subroutine col_get_ts_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_timestamp), intent(out) :: value !! receives the value.
        end subroutine col_get_ts_i32
        !> One row's ts value through a handle (i64 row index).
        module subroutine col_get_ts_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_timestamp), intent(out) :: value !! receives the value.
        end subroutine col_get_ts_i64
        !> One row's i32v value through a handle (i32 row index).
        module subroutine col_get_i32v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_i32v_i32
        !> One row's i32v value through a handle (i64 row index).
        module subroutine col_get_i32v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_i32v_i64
        !> One row's i64v value through a handle (i32 row index).
        module subroutine col_get_i64v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_i64v_i32
        !> One row's i64v value through a handle (i64 row index).
        module subroutine col_get_i64v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_i64v_i64
        !> One row's f32v value through a handle (i32 row index).
        module subroutine col_get_f32v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            real(real32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_f32v_i32
        !> One row's f32v value through a handle (i64 row index).
        module subroutine col_get_f32v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            real(real32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_f32v_i64
        !> One row's f64v value through a handle (i32 row index).
        module subroutine col_get_f64v_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            real(real64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_f64v_i32
        !> One row's f64v value through a handle (i64 row index).
        module subroutine col_get_f64v_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            real(real64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_f64v_i64
        !> One row's boolv value through a handle (i32 row index).
        module subroutine col_get_boolv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            logical, allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_boolv_i32
        !> One row's boolv value through a handle (i64 row index).
        module subroutine col_get_boolv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            logical, allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_boolv_i64
        !> One row's strv value through a handle (i32 row index).
        module subroutine col_get_strv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_strv_i32
        !> One row's strv value through a handle (i64 row index).
        module subroutine col_get_strv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_strv_i64
        !> One row's datev value through a handle (i32 row index).
        module subroutine col_get_datev_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_datev_i32
        !> One row's datev value through a handle (i64 row index).
        module subroutine col_get_datev_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_datev_i64
        !> One row's timev value through a handle (i32 row index).
        module subroutine col_get_timev_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_timev_i32
        !> One row's timev value through a handle (i64 row index).
        module subroutine col_get_timev_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_timev_i64
        !> One row's tsv value through a handle (i32 row index).
        module subroutine col_get_tsv_i32(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_tsv_i32
        !> One row's tsv value through a handle (i64 row index).
        module subroutine col_get_tsv_i64(self, i, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine col_get_tsv_i64
        !> ONE ELEMENT of one row of a i32v column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_i32v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            integer(int32), intent(out) :: value             !! receives the value.
        end subroutine col_get_i32v_e32
        !> Writes ONE ELEMENT of one row of a i32v column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_i32v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            integer(int32), intent(in) :: value              !! the value to write.
        end subroutine col_set_i32v_e32
        !> ONE ELEMENT of one row of a i32v column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_i32v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            integer(int32), intent(out) :: value             !! receives the value.
        end subroutine col_get_i32v_e64
        !> Writes ONE ELEMENT of one row of a i32v column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_i32v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            integer(int32), intent(in) :: value              !! the value to write.
        end subroutine col_set_i32v_e64
        !> ONE ELEMENT of one row of a i64v column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_i64v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            integer(int64), intent(out) :: value             !! receives the value.
        end subroutine col_get_i64v_e32
        !> Writes ONE ELEMENT of one row of a i64v column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_i64v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            integer(int64), intent(in) :: value              !! the value to write.
        end subroutine col_set_i64v_e32
        !> ONE ELEMENT of one row of a i64v column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_i64v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            integer(int64), intent(out) :: value             !! receives the value.
        end subroutine col_get_i64v_e64
        !> Writes ONE ELEMENT of one row of a i64v column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_i64v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            integer(int64), intent(in) :: value              !! the value to write.
        end subroutine col_set_i64v_e64
        !> ONE ELEMENT of one row of a f32v column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_f32v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            real(real32), intent(out) :: value             !! receives the value.
        end subroutine col_get_f32v_e32
        !> Writes ONE ELEMENT of one row of a f32v column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_f32v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            real(real32), intent(in) :: value              !! the value to write.
        end subroutine col_set_f32v_e32
        !> ONE ELEMENT of one row of a f32v column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_f32v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            real(real32), intent(out) :: value             !! receives the value.
        end subroutine col_get_f32v_e64
        !> Writes ONE ELEMENT of one row of a f32v column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_f32v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            real(real32), intent(in) :: value              !! the value to write.
        end subroutine col_set_f32v_e64
        !> ONE ELEMENT of one row of a f64v column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_f64v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            real(real64), intent(out) :: value             !! receives the value.
        end subroutine col_get_f64v_e32
        !> Writes ONE ELEMENT of one row of a f64v column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_f64v_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            real(real64), intent(in) :: value              !! the value to write.
        end subroutine col_set_f64v_e32
        !> ONE ELEMENT of one row of a f64v column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_f64v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            real(real64), intent(out) :: value             !! receives the value.
        end subroutine col_get_f64v_e64
        !> Writes ONE ELEMENT of one row of a f64v column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_f64v_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            real(real64), intent(in) :: value              !! the value to write.
        end subroutine col_set_f64v_e64
        !> ONE ELEMENT of one row of a boolv column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_boolv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            logical, intent(out) :: value             !! receives the value.
        end subroutine col_get_boolv_e32
        !> Writes ONE ELEMENT of one row of a boolv column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_boolv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            logical, intent(in) :: value              !! the value to write.
        end subroutine col_set_boolv_e32
        !> ONE ELEMENT of one row of a boolv column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_boolv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            logical, intent(out) :: value             !! receives the value.
        end subroutine col_get_boolv_e64
        !> Writes ONE ELEMENT of one row of a boolv column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_boolv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            logical, intent(in) :: value              !! the value to write.
        end subroutine col_set_boolv_e64
        !> ONE ELEMENT of one row of a strv column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_strv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            character(len=:), allocatable, intent(out) :: value             !! receives the value.
        end subroutine col_get_strv_e32
        !> Writes ONE ELEMENT of one row of a strv column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_strv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            character(len=*), intent(in) :: value              !! the value to write.
        end subroutine col_set_strv_e32
        !> ONE ELEMENT of one row of a strv column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_strv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            character(len=:), allocatable, intent(out) :: value             !! receives the value.
        end subroutine col_get_strv_e64
        !> Writes ONE ELEMENT of one row of a strv column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_strv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            character(len=*), intent(in) :: value              !! the value to write.
        end subroutine col_set_strv_e64
        !> ONE ELEMENT of one row of a datev column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_datev_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_date), intent(out) :: value             !! receives the value.
        end subroutine col_get_datev_e32
        !> Writes ONE ELEMENT of one row of a datev column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_datev_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_date), intent(in) :: value              !! the value to write.
        end subroutine col_set_datev_e32
        !> ONE ELEMENT of one row of a datev column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_datev_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_date), intent(out) :: value             !! receives the value.
        end subroutine col_get_datev_e64
        !> Writes ONE ELEMENT of one row of a datev column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_datev_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_date), intent(in) :: value              !! the value to write.
        end subroutine col_set_datev_e64
        !> ONE ELEMENT of one row of a timev column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_timev_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_time), intent(out) :: value             !! receives the value.
        end subroutine col_get_timev_e32
        !> Writes ONE ELEMENT of one row of a timev column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_timev_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_time), intent(in) :: value              !! the value to write.
        end subroutine col_set_timev_e32
        !> ONE ELEMENT of one row of a timev column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_timev_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_time), intent(out) :: value             !! receives the value.
        end subroutine col_get_timev_e64
        !> Writes ONE ELEMENT of one row of a timev column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_timev_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_time), intent(in) :: value              !! the value to write.
        end subroutine col_set_timev_e64
        !> ONE ELEMENT of one row of a tsv column, without materialising the
        !! row (e32 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_tsv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_timestamp), intent(out) :: value             !! receives the value.
        end subroutine col_get_tsv_e32
        !> Writes ONE ELEMENT of one row of a tsv column (e32 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_tsv_e32(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int32), intent(in) :: i              !! 1-based row index.
            integer(int32), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_timestamp), intent(in) :: value              !! the value to write.
        end subroutine col_set_tsv_e32
        !> ONE ELEMENT of one row of a tsv column, without materialising the
        !! row (e64 indices). The table has no name-taking counterpart -- reading
        !! a single element of a vector row is new capability, not a faster spelling.
        module subroutine col_get_tsv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_timestamp), intent(out) :: value             !! receives the value.
        end subroutine col_get_tsv_e64
        !> Writes ONE ELEMENT of one row of a tsv column (e64 indices). Exact
        !! kind, and the write clears that element's null -- not the whole row's.
        module subroutine col_set_tsv_e64(self, i, e, value)
            class(parquet_table_col), intent(in) :: self !! the handle.
            integer(int64), intent(in) :: i              !! 1-based row index.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            type(parquet_timestamp), intent(in) :: value              !! the value to write.
        end subroutine col_set_tsv_e64

        !> The TAIL of `table_resolve`, on a slot that is already known good: the unsupported-type
        !! refusal, the lazy first touch, and the shared-write rule. Split out so that a caller
        !! holding a slot index -- `table_resolve` after its name lookup, and anything reaching a
        !! column by position -- runs exactly the same checks in exactly the same order, without a
        !! second copy of them existing to drift.
        !!
        !! **Assumes `table_check_open` and `table_check_no_append` have already run** and that
        !! `idx` is in 1..ncols. Those live in the callers because `table_resolve` must do them
        !! before its lookup, and repeating them here would put a redundant test on the per-cell
        !! path this split exists not to slow down.
        module subroutine table_resolve_slot(self, idx, proc, found, writing)
            class(parquet_table), intent(in) :: self  !! the table.
            integer, intent(inout) :: idx             !! validated slot index in; 0 out on a reported refusal.
            character(len=*), intent(in) :: proc      !! calling procedure, for the message.
            logical, intent(out), optional :: found   !! present: report a refusal instead of aborting.
            logical, intent(in), optional :: writing  !! .true. when the caller is about to write.
        end subroutine table_resolve_slot
        !> The by-POSITION twin of `table_lookup_or_fail`: validates that `j` is a 1-based column
        !! position this table has, honouring `found=` and otherwise aborting. Shared by every
        !! index-form introspection query so they all bounds-check and report the same way.
        module subroutine table_slot_or_fail(self, j, proc, idx, found)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: j                 !! 1-based column position to validate.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
            integer, intent(out) :: idx              !! the slot index (== j), or 0 on a reported miss.
            logical, intent(out), optional :: found  !! present: report an out-of-range j instead of aborting.
        end subroutine table_slot_or_fail
        !> Resolves `name` to its 1-based slot index, or 0 when absent. The single lookup every
        !! accessor goes through, so a rename or remap only has to change one place.
        module function table_find(self, name) result(idx)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer :: idx                           !! slot index, or 0.
        end function table_find
        !> Resolves `name` for a value access: aborts (or reports through `found`) when the
        !! column is missing, unsupported or not resident.
        module subroutine table_resolve(self, name, proc, idx, found, writing)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column name.
            character(len=*), intent(in) :: proc      !! calling procedure, for the message.
            integer, intent(out) :: idx               !! slot index, or 0 when `found` is present.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            !> .true. when the caller is about to WRITE values into the column, which brings
            !! `table_check_shared_write`'s string-column rule with it. Passed by every %set and
            !! %set_element specific, so a new one inherits the rule by copying its neighbour. The
            !! null-writing entry points call that guard directly instead, with `nulling=.true.`,
            !! since only they can trigger the lazy validity allocation.
            logical, intent(in), optional :: writing
        end subroutine table_resolve
        !> Builds the "(file 'x.parquet', column 'y')" suffix every error message carries.
        !! Takes the cache rather than the table so that a `parquet_table_row` handle, which
        !! holds nothing else, can raise messages with the same context.
        module subroutine table_context_suffix(cache, name, suffix)
            type(parquet_table_cache), intent(in) :: cache        !! the column store.
            character(len=*), intent(in) :: name                  !! column name ("" to omit it).
            character(len=:), allocatable, intent(out) :: suffix  !! the message suffix.
        end subroutine table_context_suffix
        !> error stops unless `self%cache` is associated -- the guard every accessor runs first.
        module subroutine table_check_open(self, proc)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_check_open
        !> error stops when the table has been detached from its file by a row-structural change.
        !! The guard every path that would READ from the file runs first: once the row set has
        !! changed, a column still in the file can never be lined up with the columns already in
        !! memory, so reading one would hand back silently misaligned data.
        !!
        !! Takes the cache and the scope rather than the table, so a `parquet_table_row` handle
        !! and the internal read helpers can run the identical guard.
        module subroutine table_check_not_detached(cache, sc, name, proc)
            type(parquet_table_cache), intent(in) :: cache !! the column store, for the message.
            type(table_scope), intent(in) :: sc            !! rows this table covers; carries `detached`.
            character(len=*), intent(in) :: name           !! column name ("" to omit it).
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine table_check_not_detached
        !> Fills `mask` with slot `idx`'s per-ROW validity: one entry per row, .true. where the
        !! row holds a value. Always allocated, even for a column with no nulls at all, so a
        !! caller never has to test allocated() before using it.
        !!
        !! `cache` is intent(inout) only because `parquet_column%row_validity` is: a temporal
        !! kind refreshes its cached null flag while answering. Nothing here changes the store.
        module subroutine table_valid_mask_of(cache, idx, mask)
            type(parquet_table_cache), intent(inout) :: cache !! the table's store.
            integer, intent(in) :: idx                     !! slot index.
            logical, allocatable, intent(out) :: mask(:)   !! one entry per row.
        end subroutine table_valid_mask_of
        !> The same, for an arbitrary list of rows -- what `%get_slice(is_valid=)` needs, since a
        !! selection may be strided, reversed or repeated.
        module subroutine table_valid_mask_rows(cache, idx, rows, mask)
            type(parquet_table_cache), intent(in) :: cache !! the table's store.
            integer, intent(in) :: idx                     !! slot index.
            integer(int64), intent(in) :: rows(:)          !! the selected rows, in order.
            logical, allocatable, intent(out) :: mask(:)   !! one entry per selected row.
        end subroutine table_valid_mask_rows
        !> Applies a caller-supplied validity mask to slot `idx`: every row marked .false. becomes
        !! null. Only ever ADDS nulls -- a .true. entry says nothing about a row that is already
        !! null, and clearing it would invert a call the caller did not make. A wrong-length mask
        !! is an error naming both counts.
        module subroutine table_apply_valid(self, idx, is_valid, name, proc)
            class(parquet_table), intent(inout) :: self !! the table.
            integer, intent(in) :: idx                  !! slot index.
            logical, intent(in) :: is_valid(:)          !! one entry per row; .false. marks it null.
            character(len=*), intent(in) :: name        !! column name, for the message.
            character(len=*), intent(in) :: proc        !! calling procedure, for the message.
        end subroutine table_apply_valid
        !> Applies a caller-supplied validity mask to a SELECTION of rows: every selected row
        !! whose entry is .false. becomes null. Only ever adds nulls, exactly as the whole-column
        !! form does. A mask whose length is not the selection's is an error.
        module subroutine table_apply_valid_rows(self, idx, rows, is_valid, name, proc)
            class(parquet_table), intent(inout) :: self !! the table.
            integer, intent(in) :: idx                  !! slot index.
            integer(int64), intent(in) :: rows(:)       !! the selected rows, in order.
            logical, intent(in) :: is_valid(:)          !! one entry per selected row.
            character(len=*), intent(in) :: name        !! column name, for the message.
            character(len=*), intent(in) :: proc        !! calling procedure, for the message.
        end subroutine table_apply_valid_rows
        !> Fills `mask` with slot `idx`'s per-ELEMENT validity, shaped (width, nrows).
        !!
        !! The rank-2 counterpart of `table_valid_mask_of`, and what a vector column's
        !! `%get`/`%col`/`%get_slice` hand back: a vector column's validity is per element, so
        !! summarising it to one bit per row would be a different (and lossier) answer.
        !!
        !! `cache` is intent(inout) for the same reason `table_valid_mask_of`'s is.
        module subroutine table_valid_mask_of_elem(cache, idx, mask)
            type(parquet_table_cache), intent(inout) :: cache !! the table's store.
            integer, intent(in) :: idx                      !! slot index.
            logical, allocatable, intent(out) :: mask(:,:)  !! (element, row).
        end subroutine table_valid_mask_of_elem
        !> The same, for an arbitrary list of rows -- the rank-2 `%get_slice(is_valid=)` form.
        module subroutine table_valid_mask_rows_elem(cache, idx, rows, mask)
            type(parquet_table_cache), intent(in) :: cache  !! the table's store.
            integer, intent(in) :: idx                      !! slot index.
            integer(int64), intent(in) :: rows(:)           !! the selected rows, in order.
            logical, allocatable, intent(out) :: mask(:,:)  !! (element, selected row).
        end subroutine table_valid_mask_rows_elem
        !> Applies a caller-supplied per-ELEMENT validity mask to slot `idx`: every element marked
        !! .false. becomes null. Only ever ADDS nulls, exactly as the row form does. A mask whose
        !! shape is not (width, nrows) is an error naming both shapes.
        module subroutine table_apply_valid_elem(self, idx, is_valid, name, proc)
            class(parquet_table), intent(inout) :: self !! the table.
            integer, intent(in) :: idx                  !! slot index.
            logical, intent(in) :: is_valid(:,:)        !! (element, row); .false. marks it null.
            character(len=*), intent(in) :: name        !! column name, for the message.
            character(len=*), intent(in) :: proc        !! calling procedure, for the message.
        end subroutine table_apply_valid_elem
        !> Applies a per-ELEMENT validity mask to a SELECTION of rows. Only ever adds nulls.
        module subroutine table_apply_valid_rows_elem(self, idx, rows, is_valid, name, proc)
            class(parquet_table), intent(inout) :: self !! the table.
            integer, intent(in) :: idx                  !! slot index.
            integer(int64), intent(in) :: rows(:)       !! the selected rows, in order.
            logical, intent(in) :: is_valid(:,:)        !! (element, selected row).
            character(len=*), intent(in) :: name        !! column name, for the message.
            character(len=*), intent(in) :: proc        !! calling procedure, for the message.
        end subroutine table_apply_valid_rows_elem
        !> error stops unless an array being written into a row selection has one value per
        !! selected row. Its own procedure because the two counts come from different places --
        !! the caller's array and the resolved selection -- and naming both is what makes the
        !! message useful.
        module subroutine table_require_slice_size(self, n_arr, n_rows, name, proc)
            class(parquet_table), intent(in) :: self !! the table.
            integer(int64), intent(in) :: n_arr      !! values the caller supplied.
            integer(int64), intent(in) :: n_rows     !! rows the selection resolves to.
            character(len=*), intent(in) :: name     !! column name, for the message.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_require_slice_size
        !> A column's min and max as display text, for %print_stat.
        !!
        !! Over the VALUES only: a null row contributes nothing and is counted separately, which
        !! is the sort engine's rule too. An all-null column has neither, and both come back as
        !! "-". A `logical` column reports true/false counts instead of an ordering, and a vector
        !! column's statistic is over all of its elements, flattened.
        module subroutine table_column_stat_text(values, min_s, max_s)
            type(parquet_column), intent(in) :: values          !! the column, which must be resident.
            character(len=:), allocatable, intent(out) :: min_s !! smallest value as text, or "-".
            character(len=:), allocatable, intent(out) :: max_s !! largest value as text, or "-".
        end subroutine table_column_stat_text
        !> error stops unless slot `idx` holds exactly `kind`. The exact-kind rule the pointer
        !! path and the copy-back path both enforce (the copy-OUT path widens instead).
        module subroutine table_require_kind(self, idx, kind, proc)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: idx               !! slot index.
            integer, intent(in) :: kind              !! required PK_* discriminator.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_require_kind
        !> The CACHE-and-slot twin of `table_check_shared_write`, for callers holding a resolved
        !! slot rather than a table. `table_check_shared_write` delegates to it, so a handle and a
        !! name enforce the same two concurrency rules -- the string-store rule and the
        !! first-null-allocates-validity rule -- from one body.
        module subroutine cache_check_shared_write(cache, idx, proc, nulling)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: idx               !! slot index.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
            logical, intent(in) :: nulling           !! .true. when the write would create a null.
        end subroutine cache_check_shared_write
        !> The CACHE-and-slot twin of `table_require_kind`, for callers that hold a resolved slot
        !! rather than a table -- a column handle, and every shared `col_fetch_*`/`col_store_*`
        !! body. `table_require_kind` delegates to it, so there is exactly one wording of this
        !! message: a handle and a name must report a kind mismatch identically or the shared body
        !! has not actually stopped the two forms diverging.
        module subroutine cache_require_kind(cache, idx, kind, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: idx               !! slot index.
            integer, intent(in) :: kind              !! required PK_* discriminator.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine cache_require_kind
        !> Aborts unless slot `idx`'s STORED kind is exactly `kind`, with the POINTER path's own
        !! message rather than `cache_require_kind`'s.
        !!
        !! Separate from `cache_require_kind` because the remedy is different and worth saying: a
        !! copying accessor would have widened here, and the caller reaching for a pointer needs to
        !! be told that is why this one will not. Shared by the table's `%col` and a column
        !! handle's `%ref`, which are the same operation reached two ways.
        module subroutine cache_require_ptr_kind(cache, idx, kind, proc)
            type(parquet_table_cache), intent(in) :: cache !! the table's column store.
            integer, intent(in) :: idx                     !! validated slot index.
            integer, intent(in) :: kind                    !! the PK_* the pointer requires.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine cache_require_ptr_kind
        !> error stops unless slot `idx` holds exactly `n` rows -- a %set replaces values, never
        !! the row set, so a different length is a row-structural change and not allowed here.
        module subroutine table_require_length(self, idx, n, proc)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: idx               !! slot index.
            integer(int64), intent(in) :: n          !! the caller's array length.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_require_length
    end interface
    !
    ! ---- Read-in (Role-B) MAML (parquet_tables_maml) ----
    interface
        !> Parses a read-in MAML's `extra: remap:` block into two parallel name arrays:
        !! `internal(i)` is the table-facing name the caller will use, `physical(i)` the column it
        !! actually reads from the file. Both are returned trimmed and unquoted, in declaration
        !! order; `n` is 0 (and the arrays are allocated empty) when the MAML declares no remapping.
        !! A repeated internal name is rejected here, since that is malformed MAML on its own terms
        !! and needs no parquet file to detect.
        !!
        !! Takes the loaded `parquet_schema` rather than its `%maml%lines` so that callers can pass
        !! `parquet_load_qc_maml_file(...)` straight in as the actual argument, which is how every
        !! other consumer of that function in this project uses it -- assigning its (derived-type,
        !! allocatable-component) result to a local variable first is a shape nothing else here
        !! relies on.
        !!
        !! A deliberately separate, narrow parser rather than a reuse of the write-side
        !! `parquet_parse_col_map`: that one is validated against a base schema a generic table does
        !! not have, and the two serve opposite directions (this relabels for READING, `col_map:`
        !! for writing). It reads only lines already loaded from disk, so the shared
        !! `parquet_read_maml_source_lines` guarantees (line-length cap, CRLF stripping) come with
        !! them via `parquet_load_qc_maml_file`.
        module subroutine parse_read_maml_remap(schema, internal, physical, n, maml_file)
            type(parquet_schema), intent(in) :: schema !! the loaded read-in MAML.
            character(len=:), allocatable, intent(out) :: internal(:) !! table-facing names, in order.
            character(len=:), allocatable, intent(out) :: physical(:) !! file column each one reads.
            integer, intent(out) :: n                !! entries found; 0 if there is no remap: block.
            character(len=*), intent(in) :: maml_file !! the MAML's own path, for error messages.
        end subroutine parse_read_maml_remap
        !> Parses one plain YAML string list nested under the MAML's `extra:` section -- the shape
        !! both `extra: filter:` and `extra: sort:` have -- into `items(1:n)`, unquoted and in list
        !! order. `n` is 0 (and `items` is allocated empty) when the MAML has no such key.
        !!
        !! One parser for both keys rather than one each: the two differ only in what the strings
        !! MEAN, and neither this procedure nor the MAML format cares. Every string is passed on to
        !! parquet_filter%add / parquet_sortkey%add verbatim, so both grammars stay defined in
        !! exactly one place.
        module subroutine parse_read_maml_string_list(schema, key, items, n)
            type(parquet_schema), intent(in) :: schema !! the loaded read-in MAML.
            character(len=*), intent(in) :: key        !! nested key to read, with its colon ("filter:").
            character(len=:), allocatable, intent(out) :: items(:) !! the list's entries, in order.
            integer, intent(out) :: n                  !! entries found; 0 if the key is absent.
        end subroutine parse_read_maml_string_list
        !> Splits an `extra: sort:` entry into the key text parquet_sortkey%add takes and the
        !! per-key null placement, consuming an optional trailing `nulls_first`/`nulls_last` word
        !! (case-insensitive). Absent, it defaults to `nulls_last`, matching %add's own default.
        !!
        !! This trailing token is the one place this stage EXTENDS an existing text grammar rather
        !! than reusing it verbatim, and it is deliberately MAML-only: a plain YAML string list has
        !! nowhere else to carry what the Fortran API expresses as %add(key, nulls_first=.true.).
        !! Anything else in that position is left in `key_text` for %add to reject with its own
        !! message, so there is still only one implementation of the direction grammar.
        module subroutine split_sort_nulls_token(entry, key_text, nulls_first)
            character(len=*), intent(in) :: entry !! one raw extra: sort: list entry.
            character(len=:), allocatable, intent(out) :: key_text !! "<column> [asc|desc]", token removed.
            logical, intent(out) :: nulls_first   !! .true. if the entry asked for nulls first.
        end subroutine split_sort_nulls_token
        !> Composes the whole read-time transform, ONCE, before the parquet file is opened: loads
        !! the read-in MAML (if any), parses its `extra: remap:`/`filter:`/`sort:` blocks, translates
        !! the caller's internal-name `filter`/`sort`/`qc` into file names using that remap, and
        !! merges each with its MAML counterpart.
        !!
        !! Composition rules, all from the stage design: filter is AND (code rules first, then the
        !! MAML's -- order is immaterial for AND); sort is ORDER-SENSITIVE, MAML keys first as the
        !! primary ones and code keys appended as tie-breakers; qc is a per-COLUMN override handled
        !! by parquet_compose_read_qc. `out_filter%n`/`out_sort%n`/`n_qc` are zero when that half of
        !! the transform is empty, which is the caller's signal not to pass it to the reader at all.
        !!
        !! Doing this before the reader opens is what lets the full regime hand everything to
        !! parquet_open_reader as constructor arguments, and it means a malformed MAML aborts with
        !! no reader -- and so no live Arrow object -- anywhere in scope.
        module subroutine compose_read_transform(sliced, maml_file, filter, sort, qc, internal, physical, &
                n_remap, out_filter, out_sort, out_qc, n_qc, unit_cols, unit_vals, n_units)
            logical, intent(in) :: sliced          !! .true. for a slice-regime open, which forbids sorting.
            character(len=*), intent(in), optional :: maml_file !! read-in MAML path, if one was given.
            type(parquet_filter), intent(in), optional :: filter !! code filter, INTERNAL names.
            type(parquet_sortkey), intent(in), optional :: sort  !! code sort keys, INTERNAL names.
            type(parquet_read_qc), intent(in), optional :: qc    !! code qc, INTERNAL names.
            character(len=:), allocatable, intent(out) :: internal(:) !! remap: table-facing names.
            character(len=:), allocatable, intent(out) :: physical(:) !! remap: file column each one reads.
            integer, intent(out) :: n_remap        !! live remap entries.
            type(parquet_filter), intent(out) :: out_filter !! composed filter, FILE names.
            type(parquet_sortkey), intent(out) :: out_sort  !! composed sort keys, FILE names.
            type(parquet_schema), intent(out) :: out_qc     !! merged qc schema, FILE names.
            integer, intent(out) :: n_qc           !! columns `out_qc` declares qc for.
            character(len=:), allocatable, intent(out) :: unit_cols(:) !! MAML fields declaring a unit, FILE names.
            character(len=:), allocatable, intent(out) :: unit_vals(:) !! the unit each one declares.
            integer, intent(out) :: n_units        !! live entries in `unit_cols`/`unit_vals`.
        end subroutine compose_read_transform
        !> Fills the cache's column slots from the file's own column names, expanding each physical
        !! column into whatever `extra: remap:` claims it (two internal names may target one
        !! physical column, so slots can outnumber the file's columns). Walks the file's order, so
        !! a struct's leaves stay adjacent for the batch release policy, and two internal names
        !! reading one column land in adjacent slots.
        !!
        !! A physical column whose own name is claimed as an INTERNAL name by some remap entry is
        !! skipped: that is the deliberate shadow, not an error.
        module subroutine table_enumerate_columns(cache, names, internal, physical, n_remap, filename)
            type(parquet_table_cache), intent(inout) :: cache !! the column store to fill.
            character(len=*), intent(in) :: names(:) !! the file's own column names, in file order.
            character(len=*), intent(in) :: internal(:) !! remap: table-facing names.
            character(len=*), intent(in) :: physical(:) !! remap: the file column each one reads.
            integer, intent(in) :: n_remap           !! live entries in internal/physical.
            character(len=*), intent(in) :: filename !! source file, for error messages.
        end subroutine table_enumerate_columns
    end interface
    !
    ! ---- Materialization orchestration (parquet_tables_read) ----
    interface
        !> Maps a file column's canonical type token and element count onto a PK_* kind.
        !! `ok` is .false. for a token this library cannot read.
        module subroutine table_kind_from_type(type_name, col_size, kind, ok)
            character(len=*), intent(in) :: type_name !! canonical token from parquet_get_column_type.
            integer, intent(in) :: col_size           !! elements per row (1 for a scalar column).
            integer, intent(out) :: kind              !! the resolved PK_* constant.
            logical, intent(out) :: ok                !! .false. if the token is not readable.
        end subroutine table_kind_from_type
        !> Settles slot `idx`'s kind, width and supported flag from the file SCHEMA alone,
        !! reading no column data. Run once per column at open time, so that %kind/%width can
        !! answer before anything has been materialized.
        module subroutine table_classify(cache, idx)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            integer, intent(in) :: idx                        !! slot to classify.
        end subroutine table_classify
        !> Reads one already-classified file column into slot `idx`'s value store and marks it
        !! RES_FULL. Does NOT release the Arrow buffers -- that is the caller's policy choice,
        !! since a struct's array is shared by all its leaves (see `table_materialize_all`).
        module subroutine table_materialize(cache, sc, idx, rdr)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            type(table_scope), intent(in) :: sc               !! rows this table covers.
            integer, intent(in) :: idx                        !! slot to fill.
            !> Reader to decode through, INSTEAD of the store's own. Present only on the
            !! internally-parallel prefetch path, where each thread must drive a reader nothing
            !! else is touching -- a shared parquet_reader entered from two threads at once is
            !! caught by the C++ ConcurrencyGuard and aborts the process. Absent everywhere else,
            !! which is the ordinary single-reader path.
            type(parquet_reader), intent(inout), optional :: rdr
        end subroutine table_materialize
        !> Reads every supported, file-backed column that is not resident yet, releasing each
        !! column's Arrow buffers as it goes so peak memory stays one column above the Fortran
        !! store. Columns already resident are left untouched.
        module subroutine table_materialize_all(cache, sc)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            type(table_scope), intent(in) :: sc               !! rows this table covers.
        end subroutine table_materialize_all
        !> Frees the reader-side Arrow buffers behind column path `name`, which for a dotted
        !! struct leaf means the whole struct's array. Releasing a name twice, or one that was
        !! never read, is a quiet no-op.
        module subroutine table_release_one(cache, name, rdr)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            character(len=*), intent(in) :: name              !! column path to release.
            type(parquet_reader), intent(inout), optional :: rdr !! reader to release from; see table_materialize.
        end subroutine table_release_one
        !> Whether a lazy first touch on this store would be unsafe right now.
        !!
        !! It is unsafe in exactly one situation: the caller is inside an OpenMP parallel region
        !! AND the table was opened outside it, so other threads may be reading the same store
        !! while this one allocates into it. A table a thread opened INSIDE the region is
        !! thread-private by construction -- that is how a parallel per-slice program is written
        !! -- so its first touch is left alone.
        !!
        !! Always .false. when the library itself is built without `-fopenmp`: under fpm the
        !! whole tree is built with one flag set, so a program using OpenMP gets the real answer,
        !! but a library compiled without it cannot see its caller's regions at all.
        module function unsafe_first_touch(cache) result(unsafe)
            type(parquet_table_cache), intent(in) :: cache !! the column store.
            logical :: unsafe                              !! .true. if a first touch must be refused.
        end function unsafe_first_touch
        !> Records, at open time, whether this store was created inside a parallel region and by
        !! which thread -- the two facts `unsafe_first_touch` needs later.
        module subroutine record_open_thread(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine record_open_thread
        !> Whether a STRUCTURAL change to this store would be unsafe right now.
        !!
        !! Same ownership test `unsafe_first_touch` applies to a first touch, and deliberately the
        !! same one rather than a second concept: a table this very thread opened inside the region
        !! is thread-private, so mutating it is the caller's own business, while any other table
        !! visible inside a parallel region may be shared. Every column- and row-structural entry
        !! point routes through this; %append is the one exception, because the table's own lock
        !! makes it safe (see table_lock).
        module function unsafe_shared_mutation(cache) result(unsafe)
            type(parquet_table_cache), intent(in) :: cache !! the column store.
            logical :: unsafe                              !! .true. if a structural change must be refused.
        end function unsafe_shared_mutation
        !> Initialises the store's lock. Called once, immediately after the cache is allocated.
        module subroutine table_init_lock(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine table_init_lock
        !> Destroys the store's lock, if it has one. Safe to call twice and safe to call from a
        !! finalizer: it validates nothing and can never abort.
        module subroutine table_destroy_lock(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine table_destroy_lock
        !> Takes the store's lock, blocking until it is free. ONLY `table_append_table` may call
        !! this -- an OpenMP simple lock is not recursive, so a second acquisition on one thread
        !! deadlocks rather than failing to build.
        module subroutine table_lock(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine table_lock
        !> Releases the store's lock.
        module subroutine table_unlock(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine table_unlock
        !> TEST-ONLY -- forces this table's "an append is in flight"/"a read is in flight" counters,
        !! so the two concurrency aborts can be provoked from ONE thread, deterministically.
        !!
        !! **This is a debug hook, not API.** It exists because the guards it drives
        !! (`table_check_no_append`, and `%append`'s own reader check) can otherwise only be
        !! triggered by two threads overlapping on demand, and a timing-dependent test is worse than
        !! no test -- it fails on a busy machine and gets disabled. Unlike the C++ `parquet_debug_*`
        !! hooks, which a test reaches through its own local `bind(C)` interface, a Fortran-side hook
        !! has no such escape hatch: these counters live on `parquet_table_cache`, whose components
        !! are private to this module, so forcing them requires a public procedure here. That cost
        !! was accepted deliberately (feature_risks.md Risk-6); it is excluded from README.md's API
        !! overview and no library code calls it.
        !!
        !! Both arguments are optional and independent: `appending=.true.` makes every READ on this
        !! table abort, `reading=.true.` makes every `%append` abort. Pass `.false.` to clear.
        module subroutine parquet_debug_table_set_inflight(table, appending, reading)
            type(parquet_table), intent(in) :: table   !! the table whose counters to force.
            logical, intent(in), optional :: appending !! .true.: pretend an append is in flight.
            logical, intent(in), optional :: reading   !! .true.: pretend a read is in flight.
        end subroutine parquet_debug_table_set_inflight
        !> Exposes the parallel single-column read's validity-block alignment arithmetic for testing.
        !!
        !! **This is a debug hook, not API**, public for the same reason
        !! `parquet_debug_table_set_inflight` is: the procedure it forwards to lives in a submodule
        !! and the property it computes cannot be observed from outside.
        !!
        !! What it computes is which rows of `lo..hi` occupy WHOLE validity-bitmap blocks, and it is
        !! the one thing standing between the parallel column read and a silent wrong answer -- two
        !! threads pasting adjacent row groups share a bitmap block unless their ranges are trimmed
        !! to this. That race is a few instructions wide, so an end-to-end test cannot be relied on
        !! to catch a mistake in it; this makes the rule itself assertable, exactly as
        !! `parquet_debug_string_row_ranges` does for `parquet_string_column`'s byte-aligned split.
        !! `mid_lo > mid_hi` reports that no whole block exists in the range.
        module subroutine parquet_debug_colread_block_rows(lo, hi, width, mid_lo, mid_hi)
            integer(int64), intent(in) :: lo      !! first row of the range.
            integer(int64), intent(in) :: hi      !! last row of the range.
            integer(int64), intent(in) :: width   !! elements per row.
            integer(int64), intent(out) :: mid_lo !! first row occupying a whole block.
            integer(int64), intent(out) :: mid_hi !! last such row; < mid_lo when there is none.
        end subroutine parquet_debug_colread_block_rows
        !> Aborts if another thread is inside %append on this store.
        !!
        !! The cheap half of the append/read contract, and the one every read entry point takes:
        !! a single atomic read of a counter, against a call that was going to copy a column
        !! anyway. Detection is best-effort by construction -- a read starting fractionally before
        !! the appender publishes its flag is not caught -- so this is a safety net over the
        !! documented append-only contract, not a replacement for it.
        module subroutine table_check_no_append(cache, proc)
            type(parquet_table_cache), intent(in) :: cache !! the column store.
            character(len=*), intent(in) :: proc           !! calling procedure, for the message.
        end subroutine table_check_no_append
        !> `table_check_no_append`, plus registering this read as in flight so a concurrent %append
        !! can refuse. Paired with `table_read_exit` around the two LONG windows only -- a lazy
        !! first touch and the bulk materialize -- never around a read of a column that is already
        !! resident, whichever accessor asked for it; see parquet_table_cache's `readers_active`
        !! comment for why that asymmetry is deliberate.
        module subroutine table_read_enter(cache, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            character(len=*), intent(in) :: proc              !! calling procedure, for the message.
        end subroutine table_read_enter
        !> Ends a read registered by `table_read_enter`. Must run on every exit path from it.
        module subroutine table_read_exit(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine table_read_exit
        !> Aborts if a caller is about to WRITE into a column another thread may be using, in one
        !! of the two ways that is unsafe even though writing values generally is not.
        !!
        !! Writing values into a resident column is normally free of any concurrency concern --
        !! different columns are different allocations, and disjoint row ranges of one fixed-width
        !! column are ordinary Fortran element writes. Two cases break that, and both are silent:
        !!
        !!   1. **A string column's rows are not independent.** `parquet_string_column` is a packed
        !!      variable-length store, so writing any element can move the whole payload -- "disjoint
        !!      row ranges" is not a meaningful division of it. Any table-level write to a string
        !!      column on a possibly-shared table is refused.
        !!   2. **The first null allocates.** Validity storage is lazy (that is the sparse-validity
        !!      property a null-free column depends on), so two threads nulling elements of the same
        !!      previously null-free column race on the allocation. Refused, naming
        !!      `%ensure_validity`, which is the way to make the allocation happen up front and let
        !!      the concurrent nulling proceed.
        !!
        !! A temporal column is exempt from (2) by construction: its null state lives in the element,
        !! so nulling allocates nothing and never could.
        module subroutine table_check_shared_write(self, idx, proc, nulling)
            class(parquet_table), intent(in) :: self !! the table being written to.
            integer, intent(in) :: idx               !! 1-based slot index of the target column.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
            logical, intent(in) :: nulling           !! .true. if the write can create a null.
        end subroutine table_check_shared_write
        !> Materializes a column's validity storage up front, so concurrent nulling allocates
        !! nothing. See `table_check_shared_write`.
        module subroutine table_ensure_validity(self, name, found)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in), optional :: name !! column to prepare; absent = every resident one.
            logical, intent(out), optional :: found  !! .false. if `name` is not a column here.
        end subroutine table_ensure_validity
        !> Aborts if a caller is about to mutate a store another thread may be using.
        !!
        !! The shared refusal behind every structural entry point: `unsafe_shared_mutation` plus a
        !! message naming `proc` and the table.
        module subroutine table_check_not_shared(self, proc)
            class(parquet_table), intent(in) :: self !! the table being changed.
            character(len=*), intent(in) :: proc     !! calling procedure, for the message.
        end subroutine table_check_not_shared
        !> Collects the slots a row-structural mutation will actually rewrite, in slot order.
        !!
        !! Exists so the serial loop, the parallel loop and the caller that hoists the first column
        !! out of it cannot disagree about which columns are touched -- the same reason
        !! `materialize_wanted` is factored out of the prefetch paths. The predicate is
        !! `table_mutable_column` and nothing else.
        module subroutine table_mutable_slots(self, slots)
            class(parquet_table), intent(in) :: self          !! the table.
            integer, allocatable, intent(out) :: slots(:)     !! the rewritable slot indices, in order.
        end subroutine table_mutable_slots
        !> Runs one row-structural operation over `slots`, on several threads when that is worth
        !! doing and serially otherwise. The single entry point for the whole parallel-mutation
        !! path: the caller never sees the gate and there is no second copy of the serial loop.
        !!
        !! `op` selects the operation (`PCW_*`), and the two optional arrays carry its argument:
        !! `rows` for `PCW_REINDEX_TRUSTED` (a permutation) and `PCW_GATHER` (a selection), `keep`
        !! for `PCW_DELETE_MASK`. Exactly one is expected per op.
        !!
        !! **Every column in `slots` is rewritten, and nothing else is touched** -- no counter, no
        !! flag, no cache-level field. The `generation` bump and the detach stay with the caller,
        !! after this returns, exactly as they were around the serial loop.
        module subroutine table_colwork(cache, op, slots, rows, keep)
            type(parquet_table_cache), intent(inout) :: cache      !! the column store.
            integer, intent(in) :: op                              !! which operation; a PCW_* constant.
            integer, intent(in) :: slots(:)                        !! slots to rewrite, from table_mutable_slots.
            integer(int64), intent(in), optional :: rows(:)        !! permutation or selection, per `op`.
            logical, intent(in), optional :: keep(:)               !! per-row keep mask, per `op`.
        end subroutine table_colwork
        !> Deep-copies `slots` from one column store into another, on several threads when that is
        !! worth doing and serially otherwise. `%clone`'s counterpart to `table_colwork`.
        !!
        !! **A separate entry point rather than another `PCW_*` op, because a clone has TWO stores
        !! and every other operation has one.** Adding an optional destination to `table_colwork`
        !! would put an argument on an op-code dispatcher that exactly one op uses, and a future op
        !! that ignored it would be a silent wrong answer rather than a compile error.
        !!
        !! The caller has already copied every descriptor and is responsible for deciding which
        !! slots are resident; this copies values and nothing else.
        module subroutine table_colwork_clone(src, dst, slots)
            type(parquet_table_cache), intent(in) :: src           !! the source column store.
            type(parquet_table_cache), intent(inout) :: dst        !! the destination column store.
            integer, intent(in) :: slots(:)                        !! resident slots to copy, in order.
        end subroutine table_colwork_clone
        !> Resolves a `width_pending` column's kind and width, then clears the flag. A no-op for
        !! every other column, so callers can invoke it unconditionally.
        !!
        !! `proven` is the whole design in one argument. `.true.` (the `%kind`/`%width` path) walks
        !! the covered row groups to PROVE the width, since answering a metadata query with a
        !! guess would silently mis-type the column. `.false.` (the read path) takes the footer
        !! screen's unproven candidate and lets the read itself settle it -- the C++ reader checks
        !! every row's length against the width it was given and aborts on a mismatch, so a wrong
        !! candidate fails loudly instead of quietly, and the read that would have happened anyway
        !! doubles as the proof. That is what keeps `%prefetch` on such a column to ONE pass over
        !! the data.
        !!
        !! Measuring is scoped to the row groups `sc` actually covers, so a slice pays for its own
        !! rows only -- and a file that is ragged overall may therefore present a uniform width
        !! within one slice.
        module subroutine table_resolve_width(cache, sc, idx, proven, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            type(table_scope), intent(in) :: sc               !! rows this table covers.
            integer, intent(in) :: idx                        !! slot to resolve.
            logical, intent(in) :: proven                     !! .true.: prove it; .false.: candidate only.
            character(len=*), intent(in) :: proc              !! calling procedure, for messages.
        end subroutine table_resolve_width
        !> Makes slot `idx` resident if it is not already: the lazy first touch every value
        !! accessor goes through. Returns immediately for a column that is already RES_FULL --
        !! that path takes no lock and is what a parallel loop over resident data runs on.
        !!
        !! Takes the cache and the scope rather than the table, so that a `parquet_table_row`
        !! handle (which holds exactly those two things) triggers an identical first touch.
        module subroutine table_touch(cache, sc, idx, proc)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            type(table_scope), intent(in) :: sc               !! rows this table covers.
            integer, intent(in) :: idx                        !! slot to make resident.
            character(len=*), intent(in) :: proc              !! calling procedure, for messages.
        end subroutine table_touch
        !> Reads the named columns now rather than on first touch. A column already resident is
        !! left alone; an unsupported one is an error, since asking to read something unreadable
        !! is a mistake worth hearing about.
        !!
        !! `names` may list SEVERAL columns separated by commas and/or semicolons
        !! ("ra;dec, mag"), which is the same spelling parquet_prefetch_columns has taken since
        !! 1.0.0 and goes through the same tokenizer. Blanks around a name are trimmed and an
        !! empty token is ignored, so a trailing or repeated separator is harmless. Prefer this
        !! over a fixed-length array constructor: a too-short declared length there silently
        !! TRUNCATES a name rather than failing.
        !!
        !! Each token may also be a STRUCT's own name, with no dot: every leaf under `name.` is
        !! then read in ONE pass, which is what makes it worth having -- the reader decodes a
        !! struct as one array shared by all its leaves, so reading them one at a time decodes it
        !! once per leaf. A real column of that exact name always wins over the prefix reading,
        !! and a name matching neither is a missing column reported the usual way.
        !!
        !! A column whose own name contains a comma or a semicolon is reachable through the array
        !! form only.
        module subroutine prefetch_string(self, names, found)
            class(parquet_table), intent(in) :: self !! the table (fills through %cache).
            character(len=*), intent(in) :: names    !! column(s) to read, comma/semicolon separated.
            logical, intent(out), optional :: found  !! present: .false. if ANY name was missing.
        end subroutine prefetch_string
        !> Reads several named columns now, in one pass, so that a struct whose leaves are all
        !! named is decoded once rather than once per leaf.
        module subroutine prefetch_array(self, names, found)
            class(parquet_table), intent(in) :: self !! the table (fills through %cache).
            character(len=*), intent(in) :: names(:) !! columns to read.
            logical, intent(out), optional :: found  !! present: .false. if ANY name was missing.
        end subroutine prefetch_array
        !> Reads every supported, file-backed column that is not resident yet -- the one-call way
        !! to make a whole table safe to use from a parallel region.
        module subroutine table_materialize_every(self)
            class(parquet_table), intent(in) :: self !! the table (fills through %cache).
        end subroutine table_materialize_every
        !> Prints what this table holds, to standard output: one header line and one line per
        !! column, with each column's kind, width, row count, null count and min/max.
        !!
        !! **Materialized columns only, by default** -- the header says how many of the table's
        !! columns those are, so a lazy table reports what it is actually holding rather than what
        !! its file contains. `all=.true.` lists every column, with `-` where a column that has
        !! not been read has nothing to report.
        !!
        !! **Printing never reads anything.** A deferred plain-`LIST` column, whose width is only
        !! knowable from its data, prints as `pending` rather than being resolved -- a diagnostic
        !! that changes what it is diagnosing is worse than one that admits it does not know.
        !!
        !! These are statistics of the values IN MEMORY, computed here by a plain Fortran scan.
        !! They are not the file's own footer statistics, and they are the only ones available for
        !! a column built with %add_column, which has no footer at all. The scan is O(rows) per
        !! column, so this is not a call to put in a loop over a large table.
        module subroutine table_print_stat(self, all)
            class(parquet_table), intent(in) :: self !! the table.
            logical, intent(in), optional :: all     !! .true.: list every column, not just the resident ones.
        end subroutine table_print_stat
        !> .true. when this table's read-time transform REMOVES rows, so the reader's own row
        !! numbering is the surviving rows rather than the file's.
        !!
        !! A sort does not count: it reorders rows without removing any. Declared here rather than
        !! kept private to one submodule because more than one needs it, and two copies of "does
        !! this transform narrow?" would be exactly the kind of predicate that drifts apart.
        module function table_transform_narrows(cache) result(narrows)
            type(parquet_table_cache), intent(in) :: cache !! the table's store, with its transform.
            logical :: narrows                             !! .true. if rows are removed.
        end function table_transform_narrows
        !> Gives the automatic `parquet_row_index` column a real slot and fills it.
        !!
        !! Where the values come from depends only on what the table is: `i` for a whole file with
        !! no transform, `row_lo + i - 1` for an unfiltered slice, and -- for a filtered, sampled
        !! or sorted table -- the reader's own account of which file rows survived and in what
        !! order, which nothing else can reconstruct.
        !!
        !! Private: reached through the ordinary column API, which resolves the reserved name to
        !! this on first use.
        !!
        !! `proc` is the name the CALLER was invoked under -- `get`, `col`, `prefetch` -- because
        !! every abort here happens while the user is asking for a column, never while adding one.
        !! Without it the shared-table refusal came out of `table_new_slot` naming `add_column`,
        !! which is a procedure the caller did not invoke and cannot find in their own code.
        module subroutine table_make_row_index(self, proc)
            class(parquet_table), intent(in) :: self !! the table (fills through %cache).
            character(len=*), intent(in) :: proc     !! calling procedure name (for the message).
        end subroutine table_make_row_index
        !> Checks this table's read-time qc against the file, WITHOUT leaving the columns resident.
        !!
        !! qc is enforced when a column is read, so on a lazy table a declared bound is only
        !! checked once something asks for that column -- which means a program that reads two of
        !! forty columns never finds out whether the other thirty-eight satisfy their bounds. This
        !! reads exactly the columns that declare a bound, letting the reader check them, and then
        !! releases the ones it had to read.
        !!
        !! **What it leaves behind is the point.** Residency is recorded BEFORE anything is read,
        !! and only the columns this call made resident are released afterwards -- a column that
        !! was already in memory stays there, values and all. So it can be called at any time
        !! without disturbing what the program is working on.
        !!
        !! A violation is reported the way it would be on an ordinary read: an abort, or a warning
        !! under `qc_soft=`. A table with no qc declared at all, and one built in memory, are
        !! no-ops -- there is nothing to check and no file to check it against.
        module subroutine table_validate_qc(self)
            class(parquet_table), intent(inout) :: self !! the table.
        end subroutine table_validate_qc
        !> Releases one column's VALUES while keeping its slot -- the honest counterpart of
        !! %prefetch, and the way to give a column's memory back without losing the column.
        !!
        !! The difference from %drop_column is what survives: an evicted column still appears in
        !! %column_names, still answers %kind/%width/%unit, and is READ AGAIN on the next touch.
        !! A dropped one is gone. So %drop_column is for a column you are finished with, and
        !! %evict_column for one you are finished with FOR NOW.
        !!
        !! Only a file-backed column of an attached table can be evicted, and that restriction is
        !! the whole safety story: everywhere else the values are the only copy that exists, so
        !! evicting them would be silent data loss rather than a memory saving. A column built
        !! with %add_column, and any column of a detached table, is therefore an error naming what
        !! is wrong. Evicting a column that is not resident is a no-op.
        !!
        !! Eviction is user-driven only. Nothing in this library evicts on its own -- no LRU, no
        !! memory budget -- so what a table holds stays predictable from the calls you wrote.
        !!
        !! A column holding values the CALLER wrote (%user_populated) is refused as well, because
        !! the file's own values would come back on the next read and the edits would be gone with
        !! nothing to notice -- pass force=.true. to discard them on purpose. Note the protection
        !! covers what the value-setting API wrote, NOT a write made through a %col/%ref pointer:
        !! the library cannot tell such a write from a read, so a caller who edits that way marks
        !! the column with %set_user_populated themselves.
        module subroutine table_evict_column(self, name, force, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column to release.
            logical, intent(in), optional :: force      !! .true. to evict a column holding local edits.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine table_evict_column
        !> Re-reads one column from the file, discarding whatever is in the store -- the escape
        !! hatch back to the file's own values after %set has changed them locally. Only valid
        !! for a file-backed column of a table that has not been detached.
        !!
        !! Discarding local edits is what this is FOR, so it says so rather than assuming: a
        !! column holding values the caller wrote (%user_populated) is refused unless force=.true.
        !! is passed, which puts %reload and %evict_column under one rule instead of two. A
        !! forced reload clears the mark, since the slot then holds the file's values again.
        module subroutine table_reload(self, name, force, found)
            class(parquet_table), intent(in) :: self !! the table (refills through %cache).
            character(len=*), intent(in) :: name     !! column to re-read.
            logical, intent(in), optional :: force   !! .true. to discard local edits and re-read.
            logical, intent(out), optional :: found  !! present: report a miss instead of aborting.
        end subroutine table_reload
        !> Marks a column as holding values the CALLER wrote, or clears that mark.
        !!
        !! The library sets this itself for every value-setting call (%set, %set_element,
        !! %set_slice, a row or column handle's %set, %set_null, %add_column, ...), and
        !! %evict_column and %reload then refuse that column unless force=.true. is passed. It
        !! cannot set it for a write made through the pointer %col/%ref hands out, because it
        !! cannot tell such a write from a read -- so a caller who edits a column that way marks
        !! it here, and gets the same protection. Clearing it says the opposite: the slot's values
        !! are the file's again, and may be discarded without force=.
        !!
        !! Marking a column that holds no values is refused: there is nothing to protect, and the
        !! mark would outlive the read that filled the slot. Clearing is always allowed.
        module subroutine table_set_user_populated(self, name, flag, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column to mark.
            logical, intent(in) :: flag                 !! .true. = the caller's own values; .false. = the file's.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine table_set_user_populated
        !> Whether a column is marked as holding values the caller wrote rather than the file's.
        !!
        !! Reads no values and never triggers a read. A column that has not been read is never
        !! marked, so this answers .false. for one -- see %set_user_populated for what sets it.
        module function table_is_user_populated(self, name, found) result(ok)
            class(parquet_table), intent(in) :: self  !! the table.
            character(len=*), intent(in) :: name      !! column to ask about.
            logical, intent(out), optional :: found   !! present: report a miss instead of aborting.
            logical :: ok                             !! .true. when the slot holds the caller's own writes.
        end function table_is_user_populated
    end interface
    !
    ! ---- Write-out (parquet_tables_write) ----
    interface
        !> Writes `table` to `filename` using `schema` to choose and name the output columns:
        !! every enabled schema field is looked up in the table BY ITS INTERNAL NAME, and written
        !! under its own output name (a col_map: rename is honoured automatically). A schema field
        !! with no matching table column is an error; a table column the schema does not name is
        !! simply not written. `row_mask` writes a row subset without changing the table.
        !!
        !! **`schema` is optional.** Without one the write is *schema-less*: every column that is
        !! currently RESIDENT is written, in slot order, under its own internal name -- a quick
        !! path for a small or temporary table that reads nothing and needs no schema built for
        !! it. A table with nothing resident writes a valid empty file. The automatic
        !! `parquet_row_index` column is never written by a schema-less write, even when it is
        !! resident; name it in a schema to write it. Without a schema there is no `col_map:` and
        !! no `qc:`, so output names are the internal names and writer-side qc is off.
        !!
        !! A schema built with `%init`/`%add_field` and never parsed is parsed here, so calling
        !! `parquet_parse_maml` first is optional. That is why `schema` is `intent(inout)`: the
        !! caller's schema is parsed on return.
        !!
        !! `copy_metadata=.true.` carries every key/value metadata entry of the table's SOURCE FILE
        !! into the output; `metadata_keys=` carries only the listed keys (and error stops on one
        !! the source does not have). The two are mutually exclusive. A key the schema itself
        !! declares wins and is not overwritten -- the schema is the explicit statement. Both work
        !! after the table has detached, since the metadata was snapshotted at open, and neither
        !! adds anything to the caller's own schema.
        !!
        !! `write_maml`, `qc`, `compression`, `compression_level`, `chunk_size`, `use_threads` and
        !! `overwrite` are pass-throughs to `parquet_open_writer` with its own defaults and no
        !! reinterpretation, so a table write and the equivalent hand-written open stay the same
        !! calls. `chunk_size` is default-kind `integer` on purpose: a row group cannot hold more
        !! than int32 rows, so there is no `int64` form to add.
        !!
        !! `release` (default `.true.`) leaves the table in the residency state it started in: a
        !! column this write had to materialize is evicted again once it has been written, while a
        !! column the caller had already read is left alone. Releasing frees storage a `%col`
        !! pointer could alias, so the generation counter advances when at least one column was
        !! actually released -- possibly a false alarm (nothing you can hold a pointer to is ever
        !! released), never a missed one.
        module subroutine parquet_write_table(table, filename, schema, row_mask, copy_metadata,   &
                metadata_keys, write_maml, qc, compression, compression_level, chunk_size,        &
                use_threads, overwrite, release)
            class(parquet_table), intent(in) :: table  !! the table to write (any extending type too).
            character(len=*), intent(in) :: filename   !! output parquet file.
            type(parquet_schema), intent(inout), optional :: schema !! output schema; absent = schema-less write.
            logical, intent(in), optional :: row_mask(:)  !! per-row write mask.
            logical, intent(in), optional :: copy_metadata !! .true.: carry every source-file metadata entry.
            character(len=*), intent(in), optional :: metadata_keys(:) !! carry only these source-file keys.
            logical, intent(in), optional :: write_maml !! also save a sidecar .maml next to filename.
            logical, intent(in), optional :: qc !! run the schema's qc: checks on write; defaults to on.
            character(len=*), intent(in), optional :: compression !! Arrow compression codec name (e.g. "snappy").
            integer, intent(in), optional :: compression_level !! codec-specific compression level.
            integer, intent(in), optional :: chunk_size !! Parquet row-group size, in rows.
            logical, intent(in), optional :: use_threads !! use Arrow's multi-threaded writer.
            logical, intent(in), optional :: overwrite !! allow truncating an existing file; default .true.
            logical, intent(in), optional :: release !! evict columns this write materialized; default .true.
        end subroutine parquet_write_table
    end interface
    !
    ! ---- Zero-copy pointer access (parquet_tables_access) ----
    interface
        !> Points `p` at a PK_INT32 column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_i32(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            integer(int32), pointer, intent(out) :: p(:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_i32
        !> Points `p` at a PK_INT64 column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_i64(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            integer(int64), pointer, intent(out) :: p(:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_i64
        !> Points `p` at a PK_FLOAT32 column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_f32(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            real(real32), pointer, intent(out) :: p(:)      !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_f32
        !> Points `p` at a PK_FLOAT64 column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_f64(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            real(real64), pointer, intent(out) :: p(:)      !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_f64
        !> Points `p` at a PK_LOGICAL column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_bool(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            logical, pointer, intent(out) :: p(:)           !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_bool
        !> Points `p` at a PK_DATE column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_date(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_date), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_date
        !> Points `p` at a PK_TIME column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_time(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_time), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_time
        !> Points `p` at a PK_TIMESTAMP column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_ts(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_timestamp), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_ts
        !> Points `p` at a PK_INT32_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_i32v(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            integer(int32), pointer, intent(out) :: p(:,:)  !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_i32v
        !> Points `p` at a PK_INT64_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_i64v(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            integer(int64), pointer, intent(out) :: p(:,:)  !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_i64v
        !> Points `p` at a PK_FLOAT32_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_f32v(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            real(real32), pointer, intent(out) :: p(:,:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_f32v
        !> Points `p` at a PK_FLOAT64_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_f64v(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            real(real64), pointer, intent(out) :: p(:,:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_f64v
        !> Points `p` at a PK_LOGICAL_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_boolv(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            logical, pointer, intent(out) :: p(:,:)         !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_boolv
        !> Points `p` at a PK_DATE_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_datev(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_date), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_datev
        !> Points `p` at a PK_TIME_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_timev(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_time), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_timev
        !> Points `p` at a PK_TIMESTAMP_VEC column's storage. The stored kind must match EXACTLY.
        module subroutine col_ptr_tsv(self, name, p, is_valid, found)
            class(parquet_table), intent(in), target :: self !! the table.
            character(len=*), intent(in) :: name             !! column name.
            type(parquet_timestamp), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found          !! present: report a miss instead of aborting.
        end subroutine col_ptr_tsv
        !> Points `p` at a PK_STRING column's packed store (offsets + data + validity).
        !!
        !! The compact counterpart of the typed `%col` pointers, and the one pointer this layer
        !! hands out that a caller could use to change the column's SHAPE. Reading and in-place
        !! value edits are supported; appending to it, or otherwise changing how many elements it
        !! holds, is not -- the column's own row count is kept separately and would stop matching.
        module subroutine col_ptr_strcol(self, name, p, found)
            class(parquet_table), intent(in), target :: self          !! the table.
            character(len=*), intent(in) :: name                      !! column name.
            type(parquet_string_column), pointer, intent(out) :: p    !! alias to the packed store.
            logical, intent(out), optional :: found                   !! present: report a miss instead of aborting.
        end subroutine col_ptr_strcol
        !> Points `p` at a PK_INT32 column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_i32(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            integer(int32), pointer, intent(out) :: p(:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_i32
        !> Points `p` at a PK_INT64 column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_i64(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            integer(int64), pointer, intent(out) :: p(:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_i64
        !> Points `p` at a PK_FLOAT32 column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_f32(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            real(real32), pointer, intent(out) :: p(:)      !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_f32
        !> Points `p` at a PK_FLOAT64 column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_f64(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            real(real64), pointer, intent(out) :: p(:)      !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_f64
        !> Points `p` at a PK_LOGICAL column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_bool(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            logical, pointer, intent(out) :: p(:)           !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_bool
        !> Points `p` at a PK_DATE column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_date(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_date), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_date
        !> Points `p` at a PK_TIME column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_time(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_time), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_time
        !> Points `p` at a PK_TIMESTAMP column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_ts(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_timestamp), pointer, intent(out) :: p(:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
        end subroutine col_ref_ts
        !> Points `p` at a PK_INT32_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_i32v(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            integer(int32), pointer, intent(out) :: p(:,:)  !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_i32v
        !> Points `p` at a PK_INT64_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_i64v(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            integer(int64), pointer, intent(out) :: p(:,:)  !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_i64v
        !> Points `p` at a PK_FLOAT32_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_f32v(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            real(real32), pointer, intent(out) :: p(:,:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_f32v
        !> Points `p` at a PK_FLOAT64_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_f64v(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            real(real64), pointer, intent(out) :: p(:,:)    !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_f64v
        !> Points `p` at a PK_LOGICAL_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_boolv(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            logical, pointer, intent(out) :: p(:,:)         !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_boolv
        !> Points `p` at a PK_DATE_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_datev(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_date), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_datev
        !> Points `p` at a PK_TIME_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_timev(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_time), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_timev
        !> Points `p` at a PK_TIMESTAMP_VEC column's storage, from an already-resolved handle.
        !!
        !! The handle twin of `%col`, with the same warning: `p` aliases the LIVE column, so a
        !! write through it changes the table and nothing revalidates the result. Reordering one
        !! column through its pointer breaks the table's row alignment silently. The stored kind
        !! must match EXACTLY -- the pointer path never widens.
        module subroutine col_ref_tsv(self, p, is_valid)
            class(parquet_table_col), intent(in) :: self     !! the handle.
            type(parquet_timestamp), pointer, intent(out) :: p(:,:) !! alias to the live storage.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
        end subroutine col_ref_tsv
        !> Points `p` at a PK_STRING column's packed store, from an already-resolved handle.
        !!
        !! The handle twin of `%col`'s string form, carrying its warning verbatim: this is the one
        !! pointer this layer hands out that a caller could use to change the column's SHAPE.
        !! Reading and in-place value edits are supported; appending to it, or otherwise changing
        !! how many elements it holds, is not -- the column's own row count is kept separately and
        !! would stop matching.
        module subroutine col_ref_strcol(self, p)
            class(parquet_table_col), intent(in) :: self              !! the handle.
            type(parquet_string_column), pointer, intent(out) :: p    !! alias to the packed store.
        end subroutine col_ref_strcol
    end interface
    !
    ! ---- Copy out (parquet_tables_access) ----
    interface
        !> Copies a PK_INT32 column out into a freshly allocated array.
        module subroutine get_arr_i32(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            integer(int32), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_i32
        !> Copies a PK_INT64 column out into a freshly allocated array.
        !! Also accepts a PK_INT32 column, widening on the way.
        module subroutine get_arr_i64(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            integer(int64), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_i64
        !> Copies a PK_FLOAT32 column out into a freshly allocated array.
        module subroutine get_arr_f32(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            real(real32), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_f32
        !> Copies a PK_FLOAT64 column out into a freshly allocated array.
        !! Also accepts a PK_FLOAT32 column, widening on the way.
        module subroutine get_arr_f64(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            real(real64), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_f64
        !> Copies a PK_LOGICAL column out into a freshly allocated array.
        module subroutine get_arr_bool(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            logical, allocatable, intent(out) :: arr(:)     !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_bool
        !> Copies a PK_DATE column out into a freshly allocated array.
        module subroutine get_arr_date(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_date), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_date
        !> Copies a PK_TIME column out into a freshly allocated array.
        module subroutine get_arr_time(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_time), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_time
        !> Copies a PK_TIMESTAMP column out into a freshly allocated array.
        module subroutine get_arr_ts(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_timestamp), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_ts
        !> Copies a PK_INT32_VEC column out into a freshly allocated array.
        module subroutine get_arr_i32v(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            integer(int32), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_i32v
        !> Copies a PK_INT64_VEC column out into a freshly allocated array.
        !! Also accepts a PK_INT32_VEC column, widening on the way.
        module subroutine get_arr_i64v(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            integer(int64), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_i64v
        !> Copies a PK_FLOAT32_VEC column out into a freshly allocated array.
        module subroutine get_arr_f32v(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            real(real32), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_f32v
        !> Copies a PK_FLOAT64_VEC column out into a freshly allocated array.
        !! Also accepts a PK_FLOAT32_VEC column, widening on the way.
        module subroutine get_arr_f64v(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            real(real64), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_f64v
        !> Copies a PK_LOGICAL_VEC column out into a freshly allocated array.
        module subroutine get_arr_boolv(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            logical, allocatable, intent(out) :: arr(:,:)   !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_boolv
        !> Copies a PK_DATE_VEC column out into a freshly allocated array.
        module subroutine get_arr_datev(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_date), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_datev
        !> Copies a PK_TIME_VEC column out into a freshly allocated array.
        module subroutine get_arr_timev(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_time), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_timev
        !> Copies a PK_TIMESTAMP_VEC column out into a freshly allocated array.
        module subroutine get_arr_tsv(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_timestamp), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_arr_tsv
        !> Copies a PK_STRING column out as a parquet_string_column (offsets+data+validity).
        module subroutine get_arr_str(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self               !! the table.
            character(len=*), intent(in) :: name                   !! column name.
            type(parquet_string_column), intent(inout) :: arr      !! cleared, then filled.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found                !! present: report a miss instead of aborting.
        end subroutine get_arr_str
        !> Copies a PK_STRING column out as a fixed-width character array, sized to the longest
        !! element present. A null element comes back blank -- gate on %is_null to tell a null
        !! from a genuinely empty string.
        module subroutine get_arr_chr(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self               !! the table.
            character(len=*), intent(in) :: name                   !! column name.
            character(len=:), allocatable, intent(out) :: arr(:)   !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: per-row validity, .true. = value.
            logical, intent(out), optional :: found                !! present: report a miss instead of aborting.
        end subroutine get_arr_chr
        !> Copies a PK_STRING_VEC column out as a fixed-width character (element, row) array.
        module subroutine get_arr_chrv(self, name, arr, is_valid, found)
            class(parquet_table), intent(in) :: self               !! the table.
            character(len=*), intent(in) :: name                   !! column name.
            character(len=:), allocatable, intent(out) :: arr(:,:) !! (element, row) values.
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity, (width, nrows).
            logical, intent(out), optional :: found                !! present: report a miss instead of aborting.
        end subroutine get_arr_chrv
    end interface
    !
    ! ---- Copy back (parquet_tables_access) ----
    interface
        !> Replaces every value of a PK_INT32 column. The array must have the column's own shape.
        module subroutine set_arr_i32(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), intent(in) :: arr(:)            !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_i32
        !> Replaces every value of a PK_INT64 column. The array must have the column's own shape.
        module subroutine set_arr_i64(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(in) :: arr(:)            !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_i64
        !> Replaces every value of a PK_FLOAT32 column. The array must have the column's own shape.
        module subroutine set_arr_f32(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), intent(in) :: arr(:)              !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_f32
        !> Replaces every value of a PK_FLOAT64 column. The array must have the column's own shape.
        module subroutine set_arr_f64(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), intent(in) :: arr(:)              !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_f64
        !> Replaces every value of a PK_LOGICAL column. The array must have the column's own shape.
        module subroutine set_arr_bool(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            logical, intent(in) :: arr(:)                   !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_bool
        !> Replaces every value of a PK_DATE column. The array must have the column's own shape.
        module subroutine set_arr_date(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), intent(in) :: arr(:)        !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_date
        !> Replaces every value of a PK_TIME column. The array must have the column's own shape.
        module subroutine set_arr_time(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), intent(in) :: arr(:)        !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_time
        !> Replaces every value of a PK_TIMESTAMP column. The array must have the column's own shape.
        module subroutine set_arr_ts(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), intent(in) :: arr(:)   !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_ts
        !> Replaces every value of a PK_INT32_VEC column. The array must have the column's own shape.
        module subroutine set_arr_i32v(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), intent(in) :: arr(:,:)          !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_i32v
        !> Replaces every value of a PK_INT64_VEC column. The array must have the column's own shape.
        module subroutine set_arr_i64v(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(in) :: arr(:,:)          !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_i64v
        !> Replaces every value of a PK_FLOAT32_VEC column. The array must have the column's own shape.
        module subroutine set_arr_f32v(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), intent(in) :: arr(:,:)            !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_f32v
        !> Replaces every value of a PK_FLOAT64_VEC column. The array must have the column's own shape.
        module subroutine set_arr_f64v(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), intent(in) :: arr(:,:)            !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_f64v
        !> Replaces every value of a PK_LOGICAL_VEC column. The array must have the column's own shape.
        module subroutine set_arr_boolv(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            logical, intent(in) :: arr(:,:)                 !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_boolv
        !> Replaces every value of a PK_DATE_VEC column. The array must have the column's own shape.
        module subroutine set_arr_datev(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), intent(in) :: arr(:,:)      !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_datev
        !> Replaces every value of a PK_TIME_VEC column. The array must have the column's own shape.
        module subroutine set_arr_timev(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), intent(in) :: arr(:,:)      !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_timev
        !> Replaces every value of a PK_TIMESTAMP_VEC column. The array must have the column's own shape.
        module subroutine set_arr_tsv(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), intent(in) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null entries untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_tsv
        !> Replaces every value of a PK_STRING column from a character array. **Trailing blanks
        !! are trimmed** -- every element of a `character(len=*)` array shares one declared length,
        !! so a shorter value is blank-padded by Fortran and those blanks carry nothing the caller
        !! could have meant. `%set_element`, which takes a scalar, stores its value verbatim.
        module subroutine set_arr_chr(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            character(len=*), intent(in) :: arr(:)       !! one value per row.
            logical, intent(in), optional :: is_valid(:) !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null rows untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_chr
        !> Replaces every value of a PK_STRING_VEC column from a character (element, row) array.
        !! Trailing blanks are trimmed, as in the rank-1 form above.
        module subroutine set_arr_chrv(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            character(len=*), intent(in) :: arr(:,:)     !! (element, row) values.
            logical, intent(in), optional :: is_valid(:,:) !! present: elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves null rows untouched.
            logical, intent(out), optional :: found       !! present: report a miss instead of aborting.
        end subroutine set_arr_chrv
        !> Replaces every value of a PK_STRING column from a compact parquet_string_column.
        !!
        !! The counterpart of `%get(name, packed)`: an independent copy is taken, so the caller's
        !! own column and the table's do not share storage afterwards. The row count must match,
        !! exactly as it must for the character-array form.
        module subroutine set_arr_strcol(self, name, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self         !! the table.
            character(len=*), intent(in) :: name                !! column name.
            type(parquet_string_column), intent(in) :: arr      !! one value per row.
            logical, intent(in), optional :: is_valid(:)        !! present: rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls       !! .false. keeps a row null if it was.
            logical, intent(out), optional :: found             !! present: report a miss instead of aborting.
        end subroutine set_arr_strcol
    end interface
    !
    ! ---- From-scratch construction (parquet_tables_addcol) ----
    interface
        !> Appends a new PK_INT32 column holding `values`.
        module subroutine add_column_i32(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            integer(int32), intent(in) :: values(:)         !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_i32
        !> Appends a new PK_INT64 column holding `values`.
        module subroutine add_column_i64(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            integer(int64), intent(in) :: values(:)         !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_i64
        !> Appends a new PK_FLOAT32 column holding `values`.
        module subroutine add_column_f32(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            real(real32), intent(in) :: values(:)           !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_f32
        !> Appends a new PK_FLOAT64 column holding `values`.
        module subroutine add_column_f64(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            real(real64), intent(in) :: values(:)           !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_f64
        !> Appends a new PK_LOGICAL column holding `values`.
        module subroutine add_column_bool(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            logical, intent(in) :: values(:)                !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_bool
        !> Appends a new PK_DATE column holding `values`.
        module subroutine add_column_date(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_date), intent(in) :: values(:)     !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_date
        !> Appends a new PK_TIME column holding `values`.
        module subroutine add_column_time(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_time), intent(in) :: values(:)     !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_time
        !> Appends a new PK_TIMESTAMP column holding `values`.
        module subroutine add_column_ts(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_timestamp), intent(in) :: values(:) !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_ts
        !> Appends a new PK_INT32_VEC column holding `values`.
        module subroutine add_column_i32v(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            integer(int32), intent(in) :: values(:,:)       !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_i32v
        !> Appends a new PK_INT64_VEC column holding `values`.
        module subroutine add_column_i64v(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            integer(int64), intent(in) :: values(:,:)       !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_i64v
        !> Appends a new PK_FLOAT32_VEC column holding `values`.
        module subroutine add_column_f32v(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            real(real32), intent(in) :: values(:,:)         !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_f32v
        !> Appends a new PK_FLOAT64_VEC column holding `values`.
        module subroutine add_column_f64v(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            real(real64), intent(in) :: values(:,:)         !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_f64v
        !> Appends a new PK_LOGICAL_VEC column holding `values`.
        module subroutine add_column_boolv(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            logical, intent(in) :: values(:,:)              !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_boolv
        !> Appends a new PK_DATE_VEC column holding `values`.
        module subroutine add_column_datev(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_date), intent(in) :: values(:,:)   !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_datev
        !> Appends a new PK_TIME_VEC column holding `values`.
        module subroutine add_column_timev(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_time), intent(in) :: values(:,:)   !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_timev
        !> Appends a new PK_TIMESTAMP_VEC column holding `values`.
        module subroutine add_column_tsv(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            type(parquet_timestamp), intent(in) :: values(:,:) !! (element, row), shaped (width, nrows).
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_tsv
        !> Appends a new PK_STRING column holding `values` (trailing blanks are trimmed).
        module subroutine add_column_chr(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            character(len=*), intent(in) :: values(:)    !! one value per row.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_chr
        !> Appends a new PK_STRING_VEC column holding `values` (trailing blanks are trimmed).
        module subroutine add_column_chrv(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! the new column's name.
            character(len=*), intent(in) :: values(:,:)  !! (element, row) values.
            character(len=*), intent(in), optional :: unit !! unit string to store.
            logical, intent(in), optional :: force        !! .true. replaces an existing same-named column.
        end subroutine add_column_chrv
        !> Appends a new PK_STRING column holding a compact parquet_string_column's values.
        !!
        !! Unlike the character-array form nothing is trimmed: a parquet_string_column already
        !! stores each value at its own length, which is the reason to build one.
        module subroutine add_column_strcol(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self       !! the table.
            character(len=*), intent(in) :: name              !! the new column's name.
            type(parquet_string_column), intent(in) :: values !! one value per row.
            character(len=*), intent(in), optional :: unit    !! unit string to store.
            logical, intent(in), optional :: force            !! .true. replaces an existing same-named column.
        end subroutine add_column_strcol
        !> Appends a new column holding a copy of an already-built `parquet_column`, taking its
        !! kind, width and row count from the column itself.
        !!
        !! This is the one `%add_column` form that covers EVERY kind and width through a single
        !! call, because it reads all three off the column rather than from the shape of a Fortran
        !! array. What it is for is a column that could not be handed over as a plain array: one
        !! grown a row at a time with `%append_values` when the final length was not known up
        !! front, one carrying per-ELEMENT nulls on a vector kind, or one derived from another with
        !! `%gather`/`%delete_by_mask`/`%reindex`.
        !!
        !! **The column is COPIED, and the caller keeps its own.** That matches every other
        !! `%add_column` form, none of which disturbs what it is given -- so a single built column
        !! can be added to several tables, and one that was built with `%adopt` to avoid a copy
        !! does pay for one here.
        !!
        !! `unit=` overrides whatever unit the column carries; omitted, the column's own is kept.
        !!
        !! A column that has never been given a kind (no `%init`, `%adopt` or `%append_values`) is
        !! refused rather than added as an unusable `PK_NONE` slot -- the one failure mode the
        !! array forms cannot have, since they take their kind from the type they are handed.
        module subroutine add_column_col(self, name, values, unit, force)
            class(parquet_table), intent(inout) :: self    !! the table.
            character(len=*), intent(in) :: name           !! the new column's name.
            type(parquet_column), intent(in) :: values     !! the column to copy in.
            character(len=*), intent(in), optional :: unit !! unit string to store, overriding the column's own.
            logical, intent(in), optional :: force         !! .true. replaces an existing same-named column.
        end subroutine add_column_col
    end interface
    !
    ! ---- Single-cell mutation (the per-kind writers in ..._access, the rest in ..._mutate) ----
    interface
        !> Reads one row of a PK_INT32 column (i32 row index).
        module subroutine get_element_i32_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(out) :: value            !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i32_i32
        !> Reads one row of a PK_INT32 column (i64 row index).
        module subroutine get_element_i32_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(out) :: value            !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i32_i64
        !> Reads one row of a PK_INT64 column (i32 row index).
        !! Also accepts a PK_INT32 column, widening on the way out.
        module subroutine get_element_i64_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(out) :: value            !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i64_i32
        !> Reads one row of a PK_INT64 column (i64 row index).
        !! Also accepts a PK_INT32 column, widening on the way out.
        module subroutine get_element_i64_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(out) :: value            !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i64_i64
        !> Reads one row of a PK_FLOAT32 column (i32 row index).
        module subroutine get_element_f32_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(out) :: value              !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f32_i32
        !> Reads one row of a PK_FLOAT32 column (i64 row index).
        module subroutine get_element_f32_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(out) :: value              !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f32_i64
        !> Reads one row of a PK_FLOAT64 column (i32 row index).
        !! Also accepts a PK_FLOAT32 column, widening on the way out.
        module subroutine get_element_f64_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(out) :: value              !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f64_i32
        !> Reads one row of a PK_FLOAT64 column (i64 row index).
        !! Also accepts a PK_FLOAT32 column, widening on the way out.
        module subroutine get_element_f64_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(out) :: value              !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f64_i64
        !> Reads one row of a PK_LOGICAL column (i32 row index).
        module subroutine get_element_bool_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            logical, intent(out) :: value                   !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_bool_i32
        !> Reads one row of a PK_LOGICAL column (i64 row index).
        module subroutine get_element_bool_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            logical, intent(out) :: value                   !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_bool_i64
        !> Reads one row of a PK_DATE column (i32 row index).
        module subroutine get_element_date_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(out) :: value        !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_date_i32
        !> Reads one row of a PK_DATE column (i64 row index).
        module subroutine get_element_date_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(out) :: value        !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_date_i64
        !> Reads one row of a PK_TIME column (i32 row index).
        module subroutine get_element_time_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(out) :: value        !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_time_i32
        !> Reads one row of a PK_TIME column (i64 row index).
        module subroutine get_element_time_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(out) :: value        !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_time_i64
        !> Reads one row of a PK_TIMESTAMP column (i32 row index).
        module subroutine get_element_ts_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(out) :: value   !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_ts_i32
        !> Reads one row of a PK_TIMESTAMP column (i64 row index).
        module subroutine get_element_ts_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(out) :: value   !! receives the value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_ts_i64
        !> Reads one row of a PK_INT32_VEC column (i32 row index).
        module subroutine get_element_i32v_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int32), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i32v_i32
        !> Reads one row of a PK_INT32_VEC column (i64 row index).
        module subroutine get_element_i32v_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int32), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i32v_i64
        !> Reads one row of a PK_INT64_VEC column (i32 row index).
        !! Also accepts a PK_INT32_VEC column, widening on the way out.
        module subroutine get_element_i64v_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int64), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i64v_i32
        !> Reads one row of a PK_INT64_VEC column (i64 row index).
        !! Also accepts a PK_INT32_VEC column, widening on the way out.
        module subroutine get_element_i64v_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int64), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_i64v_i64
        !> Reads one row of a PK_FLOAT32_VEC column (i32 row index).
        module subroutine get_element_f32v_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real32), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f32v_i32
        !> Reads one row of a PK_FLOAT32_VEC column (i64 row index).
        module subroutine get_element_f32v_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real32), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f32v_i64
        !> Reads one row of a PK_FLOAT64_VEC column (i32 row index).
        !! Also accepts a PK_FLOAT32_VEC column, widening on the way out.
        module subroutine get_element_f64v_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real64), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f64v_i32
        !> Reads one row of a PK_FLOAT64_VEC column (i64 row index).
        !! Also accepts a PK_FLOAT32_VEC column, widening on the way out.
        module subroutine get_element_f64v_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real64), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_f64v_i64
        !> Reads one row of a PK_LOGICAL_VEC column (i32 row index).
        module subroutine get_element_boolv_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            logical, allocatable, intent(out) :: value(:)   !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_boolv_i32
        !> Reads one row of a PK_LOGICAL_VEC column (i64 row index).
        module subroutine get_element_boolv_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            logical, allocatable, intent(out) :: value(:)   !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_boolv_i64
        !> Reads one row of a PK_DATE_VEC column (i32 row index).
        module subroutine get_element_datev_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_datev_i32
        !> Reads one row of a PK_DATE_VEC column (i64 row index).
        module subroutine get_element_datev_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_datev_i64
        !> Reads one row of a PK_TIME_VEC column (i32 row index).
        module subroutine get_element_timev_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_timev_i32
        !> Reads one row of a PK_TIME_VEC column (i64 row index).
        module subroutine get_element_timev_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_timev_i64
        !> Reads one row of a PK_TIMESTAMP_VEC column (i32 row index).
        module subroutine get_element_tsv_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_tsv_i32
        !> Reads one row of a PK_TIMESTAMP_VEC column (i64 row index).
        module subroutine get_element_tsv_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self    !! the table (fills through %cache).
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives that row's width values.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine get_element_tsv_i64
        !> Reads one row of a PK_STRING column into an allocatable character (i32 row index).
        module subroutine get_element_chr_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self             !! the table (fills through %cache).
            character(len=*), intent(in) :: name                 !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value  !! receives the value ("" when null).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_element_chr_i32
        !> Reads one row of a PK_STRING_VEC column, one array element per position (i32 row index).
        module subroutine get_element_chrv_i32(self, name, i, value, found)
            class(parquet_table), intent(in) :: self                !! the table (fills through %cache).
            character(len=*), intent(in) :: name                    !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value(:)  !! receives width values.
            logical, intent(out), optional :: found                 !! present: report a miss instead of aborting.
        end subroutine get_element_chrv_i32
        !> Reads one row of a PK_STRING column into an allocatable character (i64 row index).
        module subroutine get_element_chr_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self             !! the table (fills through %cache).
            character(len=*), intent(in) :: name                 !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value  !! receives the value ("" when null).
            logical, intent(out), optional :: found              !! present: report a miss instead of aborting.
        end subroutine get_element_chr_i64
        !> Reads one row of a PK_STRING_VEC column, one array element per position (i64 row index).
        module subroutine get_element_chrv_i64(self, name, i, value, found)
            class(parquet_table), intent(in) :: self                !! the table (fills through %cache).
            character(len=*), intent(in) :: name                    !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            character(len=:), allocatable, intent(out) :: value(:)  !! receives width values.
            logical, intent(out), optional :: found                 !! present: report a miss instead of aborting.
        end subroutine get_element_chrv_i64
        !> Writes one row of a PK_INT32 column (i32 row index).
        module subroutine set_element_i32_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(in) :: value             !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i32_i32
        !> Writes one row of a PK_INT32 column (i64 row index).
        module subroutine set_element_i32_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(in) :: value             !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i32_i64
        !> Writes one row of a PK_INT64 column (i32 row index).
        module subroutine set_element_i64_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(in) :: value             !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i64_i32
        !> Writes one row of a PK_INT64 column (i64 row index).
        module subroutine set_element_i64_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(in) :: value             !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i64_i64
        !> Writes one row of a PK_FLOAT32 column (i32 row index).
        module subroutine set_element_f32_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(in) :: value               !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f32_i32
        !> Writes one row of a PK_FLOAT32 column (i64 row index).
        module subroutine set_element_f32_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(in) :: value               !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f32_i64
        !> Writes one row of a PK_FLOAT64 column (i32 row index).
        module subroutine set_element_f64_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(in) :: value               !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f64_i32
        !> Writes one row of a PK_FLOAT64 column (i64 row index).
        module subroutine set_element_f64_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(in) :: value               !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f64_i64
        !> Writes one row of a PK_LOGICAL column (i32 row index).
        module subroutine set_element_bool_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            logical, intent(in) :: value                    !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_bool_i32
        !> Writes one row of a PK_LOGICAL column (i64 row index).
        module subroutine set_element_bool_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            logical, intent(in) :: value                    !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_bool_i64
        !> Writes one row of a PK_DATE column (i32 row index).
        module subroutine set_element_date_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(in) :: value         !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_date_i32
        !> Writes one row of a PK_DATE column (i64 row index).
        module subroutine set_element_date_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(in) :: value         !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_date_i64
        !> Writes one row of a PK_TIME column (i32 row index).
        module subroutine set_element_time_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(in) :: value         !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_time_i32
        !> Writes one row of a PK_TIME column (i64 row index).
        module subroutine set_element_time_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(in) :: value         !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_time_i64
        !> Writes one row of a PK_TIMESTAMP column (i32 row index).
        module subroutine set_element_ts_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value    !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_ts_i32
        !> Writes one row of a PK_TIMESTAMP column (i64 row index).
        module subroutine set_element_ts_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value    !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_ts_i64
        !> Writes one row of a PK_INT32_VEC column (i32 row index).
        module subroutine set_element_i32v_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(in) :: value(:)          !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i32v_i32
        !> Writes one row of a PK_INT32_VEC column (i64 row index).
        module subroutine set_element_i32v_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int32), intent(in) :: value(:)          !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i32v_i64
        !> Writes one row of a PK_INT64_VEC column (i32 row index).
        module subroutine set_element_i64v_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(in) :: value(:)          !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i64v_i32
        !> Writes one row of a PK_INT64_VEC column (i64 row index).
        module subroutine set_element_i64v_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            integer(int64), intent(in) :: value(:)          !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_i64v_i64
        !> Writes one row of a PK_FLOAT32_VEC column (i32 row index).
        module subroutine set_element_f32v_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(in) :: value(:)            !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f32v_i32
        !> Writes one row of a PK_FLOAT32_VEC column (i64 row index).
        module subroutine set_element_f32v_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real32), intent(in) :: value(:)            !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f32v_i64
        !> Writes one row of a PK_FLOAT64_VEC column (i32 row index).
        module subroutine set_element_f64v_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(in) :: value(:)            !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f64v_i32
        !> Writes one row of a PK_FLOAT64_VEC column (i64 row index).
        module subroutine set_element_f64v_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            real(real64), intent(in) :: value(:)            !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_f64v_i64
        !> Writes one row of a PK_LOGICAL_VEC column (i32 row index).
        module subroutine set_element_boolv_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            logical, intent(in) :: value(:)                 !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_boolv_i32
        !> Writes one row of a PK_LOGICAL_VEC column (i64 row index).
        module subroutine set_element_boolv_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            logical, intent(in) :: value(:)                 !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_boolv_i64
        !> Writes one row of a PK_DATE_VEC column (i32 row index).
        module subroutine set_element_datev_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(in) :: value(:)      !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_datev_i32
        !> Writes one row of a PK_DATE_VEC column (i64 row index).
        module subroutine set_element_datev_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_date), intent(in) :: value(:)      !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_datev_i64
        !> Writes one row of a PK_TIME_VEC column (i32 row index).
        module subroutine set_element_timev_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(in) :: value(:)      !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_timev_i32
        !> Writes one row of a PK_TIME_VEC column (i64 row index).
        module subroutine set_element_timev_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_time), intent(in) :: value(:)      !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_timev_i64
        !> Writes one row of a PK_TIMESTAMP_VEC column (i32 row index).
        module subroutine set_element_tsv_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value(:) !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_tsv_i32
        !> Writes one row of a PK_TIMESTAMP_VEC column (i64 row index).
        module subroutine set_element_tsv_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            type(parquet_timestamp), intent(in) :: value(:) !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_tsv_i64
        !> Writes one row of a PK_STRING column from a character value (i32 row index).
        module subroutine set_element_chr_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            character(len=*), intent(in) :: value           !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_chr_i32
        !> Writes one row of a PK_STRING column from a character value (i64 row index).
        module subroutine set_element_chr_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            character(len=*), intent(in) :: value           !! the new value.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_chr_i64
        !> Writes one row of a PK_STRING_VEC column from a character value (i32 row index).
        module subroutine set_element_chrv_i32(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i                 !! 1-based row index.
            character(len=*), intent(in) :: value(:)        !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_chrv_i32
        !> Writes one row of a PK_STRING_VEC column from a character value (i64 row index).
        module subroutine set_element_chrv_i64(self, name, i, value, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i                 !! 1-based row index.
            character(len=*), intent(in) :: value(:)        !! that row's whole vector.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_element_chrv_i64
        !> Marks row `i` of a column null (int32 row index).
        module subroutine set_null_i32(self, name, i, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i             !! 1-based row index.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_i32
        !> Marks row `i` of a column null (int64 row index).
        module subroutine set_null_i64(self, name, i, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i             !! 1-based row index.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_i64
        !> Marks element `e` of row `i` null, leaving the row's other elements alone (int32).
        module subroutine set_null_e32(self, name, i, e, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i             !! 1-based row index.
            integer(int32), intent(in) :: e             !! 1-based element index within the row.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_e32
        !> Marks element `e` of row `i` null, leaving the row's other elements alone (int64).
        module subroutine set_null_e64(self, name, i, e, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i             !! 1-based row index.
            integer(int64), intent(in) :: e             !! 1-based element index within the row.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_e64
        !> Marks null every ELEMENT whose `is_valid` entry is .false., in one call.
        !!
        !! The rank-2 counterpart of the row form below, taking a `(width, nrows)` mask. Only ever
        !! ADDS nulls, on exactly the same terms.
        module subroutine set_null_mask_elem(self, name, is_valid, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            logical, intent(in) :: is_valid(:,:)        !! (element, row); .false. marks it null.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_mask_elem
        !> Marks null every row whose `is_valid` entry is .false., in one call.
        !!
        !! The mask is the same shape `%get_valid_mask` hands back and `is_valid=` takes elsewhere:
        !! one entry per row, `.true.` meaning the row holds a value. Rows marked `.true.` are left
        !! exactly as they are -- this only ever ADDS nulls, so it composes with a mask that
        !! describes only part of what the caller knows. A wrong-length mask is an error.
        module subroutine set_null_mask(self, name, is_valid, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            logical, intent(in) :: is_valid(:)          !! one entry per row; .false. marks it null.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine set_null_mask
        !> Marks row `i` of a column valid, leaving its value unspecified (int32 row index).
        module subroutine clear_null_i32(self, name, i, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i             !! 1-based row index.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine clear_null_i32
        !> Marks row `i` of a column valid, leaving its value unspecified (int64 row index).
        module subroutine clear_null_i64(self, name, i, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i             !! 1-based row index.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine clear_null_i64
        !> Marks element `e` of row `i` valid, leaving its value unspecified (int32 indices).
        module subroutine clear_null_e32(self, name, i, e, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int32), intent(in) :: i             !! 1-based row index.
            integer(int32), intent(in) :: e             !! 1-based element index within the row.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine clear_null_e32
        !> Marks element `e` of row `i` valid, leaving its value unspecified (int64 indices).
        module subroutine clear_null_e64(self, name, i, e, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            integer(int64), intent(in) :: i             !! 1-based row index.
            integer(int64), intent(in) :: e             !! 1-based element index within the row.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine clear_null_e64
        !> Drops a column's null bitmap when it no longer holds any null, so a column that HAD
        !! nulls and no longer does stops paying for the bitmap. Scans the column, so it is not
        !! free -- a whole-column %set already compacts on its own and does not need this.
        !! Idempotent: calling it on an already-compact column is a cheap no-op.
        module subroutine table_compact_validity(self, name, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column name.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine table_compact_validity
    end interface
    !
    ! ---- Column-structural mutation (parquet_tables_mutate) ----
    interface
        !> Removes a column from the table. Cheap, and it does NOT detach: dropping a column
        !! leaves every remaining column the same length, so the table can still read the ones it
        !! has not read yet. Dropping a column that was never read is the memory-reclaiming case
        !! and reads nothing.
        module subroutine table_drop_column(self, name, force, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! column to remove.
            logical, intent(in), optional :: force      !! .true. to drop a PREDEFINED column.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine table_drop_column
        !> Changes a column's name. Only the name a caller looks it up by changes -- a
        !! file-backed column that has not been read yet still reads from the same physical
        !! column afterwards. A predefined column cannot be renamed at all (its accessor is bound
        !! to the name at compile time), and there is no `force=` for it.
        module subroutine table_rename_column(self, old_name, new_name, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: old_name    !! the column to rename.
            character(len=*), intent(in) :: new_name    !! its new name; must not already exist.
            logical, intent(out), optional :: found     !! present: report a missing SOURCE column instead of aborting.
        end subroutine table_rename_column
        !> Adds a NEW column holding a copy of `name`'s values, leaving the source column
        !! untouched. With `to_kind` absent it is a plain deep copy and works for EVERY kind the
        !! library can read -- string, temporal, logical and the vector kinds included. With
        !! `to_kind` given it copies and converts, and the conversion rules are `%cast`'s.
        !!
        !! `exact` defaults to `.true.` here, the opposite of `%cast`: a copy is usually taken to
        !! keep something, so a value that would not survive the round trip is an error naming the
        !! row and the value rather than a silent truncation. Every value is checked before
        !! anything is written, so a rejected copy leaves the table exactly as it was. The unit
        !! carries over unchanged -- a kind conversion is not a unit change.
        module subroutine table_copy_column(self, name, new_name, to_kind, exact, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! the source column.
            character(len=*), intent(in) :: new_name    !! the column to create.
            integer, intent(in), optional :: to_kind    !! target PK_* kind; absent keeps the source's.
            logical, intent(in), optional :: exact      !! .false. to allow lossy narrowing (default .true.).
            logical, intent(out), optional :: found     !! present: report a missing SOURCE column instead of aborting.
        end subroutine table_copy_column
        !> Converts a column to `to_kind` IN PLACE, so that `%col` can be called with a kind the
        !! calling code has decided on rather than the one the file happens to hold. A column
        !! already of `to_kind` is left alone.
        !!
        !! The conversions allowed are exactly those the reader and writer already perform
        !! between numeric kinds: int32 <-> int64, float32 <-> float64, and either integer kind
        !! to or from either real kind, scalar or vector, never changing a column's width. An
        !! integer overflow, and a real value with a fractional part converted to an integer
        !! kind, are errors naming the row and the value. Anything else -- logical, string,
        !! temporal, or a conversion that would change a column's rank -- is refused outright.
        !!
        !! `exact` defaults to `.false.`: precision loss (real64 to real32, or a large integer to
        !! a real kind) is silent, exactly as it is on the read path. Pass `.true.` to make any
        !! value that would not survive the round trip an error instead.
        !!
        !! **This invalidates any pointer previously taken from `%col`** for this column, which
        !! Fortran cannot detect -- take the pointer again afterwards.
        module subroutine table_cast(self, name, to_kind, exact, found)
            class(parquet_table), intent(inout) :: self !! the table.
            character(len=*), intent(in) :: name        !! the column to convert.
            integer, intent(in) :: to_kind              !! target PK_* kind.
            logical, intent(in), optional :: exact      !! .true. to refuse any precision loss.
            logical, intent(out), optional :: found     !! present: report a miss instead of aborting.
        end subroutine table_cast
    end interface
    !
    ! ---- Row-structural mutation -- detaches whenever it changes the row set (parquet_tables_rowmutate) ----
    interface
        !> Keeps only the rows whose `keep` entry is .true., dropping the rest from EVERY column.
        !!
        !! Row-structural, so it DETACHES the table from its file: after it, a column that was
        !! never read can never be read, because the file's rows no longer line up with the rows
        !! in memory. Materialize what you need first (`%prefetch`/`%materialize_all`). An
        !! all-`.true.` mask removes no row, so it changes nothing and does not detach.
        module subroutine table_filter_rows(self, keep)
            class(parquet_table), intent(inout) :: self !! the table.
            logical, intent(in) :: keep(:)              !! one entry per row; .true. to retain it.
        end subroutine table_filter_rows
        !> Reorders every column's rows by one or more key columns, in memory.
        !!
        !! Runs the library's own C++ sort engine -- the same one a read-time `sort_by=` uses, so
        !! the two cannot order the same keys differently. Keys apply in the order given, the
        !! first being the primary. `descending`/`nulls_first`, when given, carry one entry per
        !! key. Nulls and NaNs are placed absolutely and are never flipped by `descending`.
        !!
        !! A key column that is not resident yet is READ, by the same lazy first touch every value
        !! accessor uses -- so sorting a freshly opened table needs no `%prefetch` first. Only the
        !! key columns are read; the rest stay as they were.
        !!
        !! Row-structural, so it DETACHES -- unless the rows were already in that order, in which
        !! case nothing moves and nothing is detached.
        module subroutine table_sort_by(self, keys, descending, nulls_first)
            class(parquet_table), intent(inout) :: self       !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_sort_by
        !> Keeps only the `n` rows a `%sort_by` on the same keys would put first, in that order.
        !!
        !! Selection rather than a full sort, which is the point: asking a 100-million-row table for
        !! its best 100 costs O(nrows) instead of O(nrows log nrows), and each column is gathered
        !! straight to `n` rows rather than reindexed in full and then shrunk. `%sort_by` followed by
        !! `%truncate` gives the same answer and does neither of those things.
        !!
        !! Same keys, same engine and same refusals as `%sort_by`, including the lazy first touch of
        !! a key column that has not been read yet. "The last `n`" is `descending=`, not a separate
        !! binding.
        !!
        !! Row-structural, so it DETACHES -- except when `n` is at or above the row count, where this
        !! IS `%sort_by` and inherits its rule that a table already in that order is left alone.
        !!
        !! Deliberately the only way to reduce a table to a chosen set of rows in a chosen order:
        !! there is no `%take(indices)` taking a caller-supplied permutation, because applying one
        !! partially, or applying a stale one, breaks row correspondence with nothing to report it
        !! (`feature_risks.md` Risk-33). Here the indices are produced inside the call, from this
        !! table's own columns, and applied to every column together.
        module subroutine table_top_n(self, keys, n, descending, nulls_first)
            class(parquet_table), intent(inout) :: self       !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            !> how many rows to keep, CLAMPED to the row count rather than checked, so an `n`
            !! derived from a fraction or a config value needs no `min()` of its own; a negative `n`
            !! is an error. Deliberately a plain default-kind `integer` and not also an int64 form:
            !! an `n` that large is not a top-N but a whole sort, which is what this delegates to.
            integer, intent(in) :: n
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_top_n
        !> The 1-based row order `keys` implies, WITHOUT applying it, as int32 indices.
        !!
        !! Same keys, same engine and same refusals as `%sort_by`; the difference is that nothing
        !! moves. That matters because `%sort_by` DETACHES: this is the only way to read a table's
        !! rows in some order and still have the file behind it. `%get_slice(name,
        !! parquet_slice_list(perm), values)` is how the permutation is consumed.
        !!
        !! **The permutation describes the table AS IT WAS.** Nothing links the two afterwards, so
        !! any row-structural change (`%sort_by`, `%top_n`, `%filter_rows`, `%delete_rows`, `%truncate`,
        !! `%append`) silently invalidates it -- against a table that has since shrunk the indices
        !! stay in range and name the wrong rows. `%generation()` is bumped by every such change:
        !! record it beside a permutation you intend to keep, and compare before reusing.
        module subroutine table_argsort_by_i32(self, keys, perm, descending, nulls_first, &
                group_offsets, group_nkeys)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            integer(int32), allocatable, intent(out) :: perm(:) !! the 1-based row order.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            !> where each run of rows equal under the grouping keys begins, as offsets INTO `perm`:
            !! length `ngroups + 1`, last entry the sentinel `nrows + 1`, so group g is
            !! `perm(o(g) : o(g+1) - 1)` for every g. Always allocated when asked for -- an empty
            !! table gives `[1]`. All nulls form one group and all NaNs form one group.
            integer(int32), allocatable, intent(out), optional :: group_offsets(:)
            !> how many LEADING keys must be equal for two rows to share a group; absent means all
            !! of them. Counts key NAMES, so `group_nkeys=1` over `["field", "mag"]` gives one group
            !! per field with the rows inside each ordered by mag. Requires `group_offsets`.
            integer, intent(in), optional :: group_nkeys
        end subroutine table_argsort_by_i32
        !> The first `n` rows of the order `keys` implies, as int32 indices, length `n`.
        !!
        !! Selection rather than a full sort, which is the point: asking a 100M-row table for its
        !! best 100 costs O(nrows) instead of O(nrows log nrows), and reorders nothing. `n` is
        !! CLAMPED to the row count rather than checked, so a derived `n` needs no `min()` of its
        !! own. "The last n" is `descending=`, not a separate binding.
        !!
        !! Hands out row indices, so it carries `%argsort_by`'s staleness rule verbatim.
        module subroutine table_argsort_partial_i32(self, keys, perm, n, descending, nulls_first)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            integer(int32), allocatable, intent(out) :: perm(:) !! the first `n` 1-based row indices.
            integer, intent(in) :: n                          !! rows to order; clamped to %nrows().
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_argsort_partial_i32
        !> The 1-based row order `keys` implies, WITHOUT applying it, as int64 indices.
        !!
        !! Same keys, same engine and same refusals as `%sort_by`; the difference is that nothing
        !! moves. That matters because `%sort_by` DETACHES: this is the only way to read a table's
        !! rows in some order and still have the file behind it. `%get_slice(name,
        !! parquet_slice_list(perm), values)` is how the permutation is consumed.
        !!
        !! **The permutation describes the table AS IT WAS.** Nothing links the two afterwards, so
        !! any row-structural change (`%sort_by`, `%top_n`, `%filter_rows`, `%delete_rows`, `%truncate`,
        !! `%append`) silently invalidates it -- against a table that has since shrunk the indices
        !! stay in range and name the wrong rows. `%generation()` is bumped by every such change:
        !! record it beside a permutation you intend to keep, and compare before reusing.
        module subroutine table_argsort_by_i64(self, keys, perm, descending, nulls_first, &
                group_offsets, group_nkeys)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the 1-based row order.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            !> where each run of rows equal under the grouping keys begins, as offsets INTO `perm`:
            !! length `ngroups + 1`, last entry the sentinel `nrows + 1`, so group g is
            !! `perm(o(g) : o(g+1) - 1)` for every g. Always allocated when asked for -- an empty
            !! table gives `[1]`. All nulls form one group and all NaNs form one group.
            integer(int64), allocatable, intent(out), optional :: group_offsets(:)
            !> how many LEADING keys must be equal for two rows to share a group; absent means all
            !! of them. Counts key NAMES, so `group_nkeys=1` over `["field", "mag"]` gives one group
            !! per field with the rows inside each ordered by mag. Requires `group_offsets`.
            integer, intent(in), optional :: group_nkeys
        end subroutine table_argsort_by_i64
        !> The first `n` rows of the order `keys` implies, as int64 indices, length `n`.
        !!
        !! Selection rather than a full sort, which is the point: asking a 100M-row table for its
        !! best 100 costs O(nrows) instead of O(nrows log nrows), and reorders nothing. `n` is
        !! CLAMPED to the row count rather than checked, so a derived `n` needs no `min()` of its
        !! own. "The last n" is `descending=`, not a separate binding.
        !!
        !! Hands out row indices, so it carries `%argsort_by`'s staleness rule verbatim.
        module subroutine table_argsort_partial_i64(self, keys, perm, n, descending, nulls_first)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the first `n` 1-based row indices.
            integer, intent(in) :: n                          !! rows to order; clamped to %nrows().
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_argsort_partial_i64
        !> Whether the rows are ALREADY in the order `keys` describes.
        !!
        !! O(nrows) with an early exit and no permutation built, where asking `%sort_by` the same
        !! question costs a full sort. Same keys, same refusals, same null and NaN placement, so a
        !! `.true.` here means `%sort_by` with those arguments would move nothing.
        module function table_is_sorted_by(self, keys, descending, nulls_first) result(answer)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys(:)           !! key columns, primary first.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            logical :: answer                                 !! .true. when already in that order.
        end function table_is_sorted_by
        ! ---- Key lists written as ONE string ("ra,-dec") -------------------------------------
        !
        !> Every key-taking binding above also accepts its keys as a single string instead of an
        !! array, which is what removes the `[character(len=9) :: "object_id", "filter", "mjd"]`
        !! constructor from the commonest call in this whole layer. That constructor is not just
        !! verbose: guessing its declared length too short SILENTLY TRUNCATES a name.
        !!
        !! **Separators** are commas and/or semicolons, interchangeably, with blanks around a key
        !! trimmed and empty tokens ignored -- the same tokenizer, and so the same rules, as
        !! `%prefetch` and `parquet_prefetch_columns`.
        !!
        !! **A key may carry its own direction**, in exactly the grammar a read-time
        !! `parquet_sortkey%add` key uses (one parser backs both): `"<column> [asc|desc]"`,
        !! case-insensitive, with a leading `-` as shorthand for descending. So
        !! `t%sort_by("ra,-dec")` orders `ra` ascending and `dec` descending, and
        !! `"ra asc, dec desc"` says the same thing longhand.
        !!
        !! **A direction token and `descending=` together are an error**, for the whole call --
        !! including a redundant `asc`. The two are ways of saying one thing and can disagree; a
        !! per-key rule would leave `descending(1)` governing one key and a token governing
        !! another, which no reader can follow. Without any token, `descending=` works exactly as
        !! it does on the array form.
        !!
        !! `nulls_first=` is orthogonal and always accepted: the grammar has no null-placement
        !! token, matching `parquet_sortkey`.
        !!
        !! A column whose own name contains a comma, a semicolon, a leading `-` or a trailing
        !! " asc"/" desc" is reachable through the array form only.
        module subroutine table_sort_by_string(self, keys, descending, nulls_first)
            class(parquet_table), intent(inout) :: self       !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_sort_by_string
        !> %top_n over a string key list; see `table_sort_by_string` for the grammar.
        module subroutine table_top_n_string(self, keys, n, descending, nulls_first)
            class(parquet_table), intent(inout) :: self       !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            integer, intent(in) :: n                          !! rows to keep; clamped to %nrows().
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_top_n_string
        !> %argsort_by over a string key list, int32 permutation; see `table_sort_by_string`.
        module subroutine table_argsort_by_string_i32(self, keys, perm, descending, nulls_first, &
                                                      group_offsets, group_nkeys)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            integer(int32), allocatable, intent(out) :: perm(:) !! the 1-based row order.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            integer(int32), allocatable, intent(out), optional :: group_offsets(:)
                !! run boundaries: group g is perm(group_offsets(g) : group_offsets(g+1) - 1).
            integer, intent(in), optional :: group_nkeys      !! leading keys a group is defined by.
        end subroutine table_argsort_by_string_i32
        !> %argsort_by over a string key list, int64 permutation; see `table_sort_by_string`.
        module subroutine table_argsort_by_string_i64(self, keys, perm, descending, nulls_first, &
                                                      group_offsets, group_nkeys)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the 1-based row order.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            integer(int64), allocatable, intent(out), optional :: group_offsets(:)
                !! run boundaries: group g is perm(group_offsets(g) : group_offsets(g+1) - 1).
            integer, intent(in), optional :: group_nkeys      !! leading keys a group is defined by.
        end subroutine table_argsort_by_string_i64
        !> %argsort_partial over a string key list, int32 permutation; see `table_sort_by_string`.
        module subroutine table_argsort_partial_string_i32(self, keys, perm, n, descending, nulls_first)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            integer(int32), allocatable, intent(out) :: perm(:) !! the first `n` 1-based row indices.
            integer, intent(in) :: n                          !! rows to order; clamped to %nrows().
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_argsort_partial_string_i32
        !> %argsort_partial over a string key list, int64 permutation; see `table_sort_by_string`.
        module subroutine table_argsort_partial_string_i64(self, keys, perm, n, descending, nulls_first)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the first `n` 1-based row indices.
            integer, intent(in) :: n                          !! rows to order; clamped to %nrows().
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
        end subroutine table_argsort_partial_string_i64
        !> %is_sorted_by over a string key list; see `table_sort_by_string` for the grammar.
        module function table_is_sorted_by_string(self, keys, descending, nulls_first) result(answer)
            class(parquet_table), intent(in) :: self          !! the table.
            character(len=*), intent(in) :: keys              !! key columns, separated; primary first.
            logical, intent(in), optional :: descending(:)    !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:)   !! per key: .true. to put nulls first.
            logical :: answer                                 !! .true. when already in that order.
        end function table_is_sorted_by_string
        !> Splits a string key list into key NAMES plus the per-key direction its tokens asked
        !! for, refusing the token-plus-`descending=` conflict. Shared by all seven string
        !! specifics so that one grammar, one conflict rule and one set of messages back every
        !! spelling. `proc` names the caller in every message.
        !!
        !! `descending` comes back **unallocated when no token carried a direction**, which is how
        !! each specific decides what to forward: an unallocated allocatable passed on as an
        !! optional actual makes that dummy absent (F2018 15.5.2.12), so the caller's own
        !! `descending=` is forwarded untouched in that case and the parsed one otherwise. The two
        !! can never both apply -- that is the conflict this refuses.
        module subroutine table_split_key_list(self, keys, proc, have_descending, names, descending)
            class(parquet_table), intent(in) :: self             !! the table, for error context.
            character(len=*), intent(in) :: keys                 !! the raw key string.
            character(len=*), intent(in) :: proc                 !! calling binding, for messages.
            logical, intent(in) :: have_descending               !! caller's present(descending).
            character(len=:), allocatable, intent(out) :: names(:) !! one key name per token.
            logical, allocatable, intent(out) :: descending(:)
                !! parsed direction per name; unallocated when no token asked for one.
        end subroutine table_split_key_list
        !> Removes the listed rows (int32 indices). Repeats are harmless -- a row named twice is
        !! removed once. Row-structural, so it DETACHES; an empty index list removes nothing and
        !! does not.
        module subroutine table_delete_rows_i32(self, indices)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int32), intent(in) :: indices(:)    !! 1-based row indices to remove.
        end subroutine table_delete_rows_i32
        !> Removes the listed rows (int64 indices). Row-structural, so it DETACHES; an empty
        !! index list removes nothing and does not.
        module subroutine table_delete_rows_i64(self, indices)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int64), intent(in) :: indices(:)    !! 1-based row indices to remove.
        end subroutine table_delete_rows_i64
        !> Keeps only the first `n` rows (int32 count). `n` beyond the row count is a no-op and
        !! does not detach; 0 empties the table. Row-structural otherwise, so it DETACHES.
        module subroutine table_truncate_i32(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int32), intent(in) :: n             !! rows to keep.
        end subroutine table_truncate_i32
        !> Keeps only the first `n` rows (int64 count). Row-structural, so it DETACHES, unless
        !! `n` is at least the row count, which keeps every row and changes nothing.
        module subroutine table_truncate_i64(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int64), intent(in) :: n             !! rows to keep.
        end subroutine table_truncate_i64
        !> Releases the spare storage capacity that appending left behind, so every resident
        !! column occupies exactly the rows it holds.
        !!
        !! **A no-op on a table that has not been appended to.** Capacity only ever comes from an
        !! append: reading a column from a file allocates exact-fit, and so does every rebuild
        !! (`%filter_rows`, `%sort_by`, `%top_n`, `%delete_rows`, `%truncate`), which hand their
        !! memory back on their own. So this is safe to call unconditionally -- before
        !! `parquet_write_table`, say -- and costs nothing when there is nothing to release. It is
        !! not NEEDED before a write: the writer reads through a pointer that is already bounded by
        !! the row count, so slack costs a write nothing.
        !!
        !! **It INVALIDATES every pointer obtained from `%col` and every row handle**, because it
        !! reallocates storage. That makes it the one procedure in `parquet_tables_mutate` that
        !! does so -- everything else there leaves the row set and the storage alone. It does NOT
        !! detach: the row set is unchanged, so the table keeps its file and a column not yet read
        !! can still be read afterwards.
        !!
        !! **`%generation()` advances only if something was actually released**, which is what
        !! makes the no-op above observable: a caller following the documented
        !! take-generation/compare/re-fetch pattern re-fetches only when a pointer really did die.
        !!
        !! Refused on a shared table, like every other mutation except `%append`.
        module subroutine table_compact(self)
            class(parquet_table), intent(inout) :: self !! the table.
        end subroutine table_compact
        !> int32 form of `%reserve`; converts and delegates.
        module subroutine table_reserve_i32(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int32), intent(in) :: n             !! rows to make room for.
        end subroutine table_reserve_i32
        !> Makes room for `n` rows in every resident column without changing the row count, so the
        !! appends that follow perform no allocation at all.
        !!
        !! The intended shape for building a table incrementally is `%reserve(n)` -> append ->
        !! `%compact()`: growth is amortised O(1) without it, but reserving turns even that into a
        !! single up-front allocation. `n` is the TOTAL row count to make room for, not an
        !! increment, so reserving less than the table already holds does nothing.
        !!
        !! Shares `%compact`'s rules: it invalidates `%col` pointers and row handles if it
        !! reallocates, advances `%generation()` only then, does not detach, and is refused on a
        !! shared table.
        module subroutine table_reserve_i64(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int64), intent(in) :: n             !! total rows to make room for.
        end subroutine table_reserve_i64
        !> Makes room for `n` COLUMNS, and in doing so gives `%add_column` a guarantee it does not
        !! otherwise have:
        !!
        !! **While spare column capacity remains, adding a column under a NEW name relocates no
        !! existing column's storage, moves no existing column's slot position, and does not
        !! advance `%generation()`.** A pointer taken from `%col`, a `parquet_table_col` handle
        !! and a `parquet_table_row` handle all stay valid across such a call. A reservation
        !! survives `%clone` and `%clone_structure`, so a copy starts with the same spare
        !! capacity. Replacing an existing column (`force=.true.`) is **not** covered -- it frees
        !! that column's storage -- and neither is any row-structural mutation.
        !!
        !! That is what makes the commonest derived-column idiom safe rather than merely
        !! lucky. Without a reservation, an `%add_column` that happens to fill the slot array
        !! reallocates it, and Fortran leaves a pointer's association status UNDEFINED across the
        !! `MOVE_ALLOC` that does it -- code that usually works and is not permitted to:
        !!
        !!```fortran
        !! call t%reserve_columns(t%ncols() + 2)   ! two derived columns coming
        !! call t%col("mag_g", g)                  ! pointers taken up front...
        !! call t%col("mag_r", r)
        !! call t%add_column("g_minus_r", g - r)   ! ...and still valid here, by contract
        !!```
        !!
        !! **Reserve first, take pointers second** -- the same rule `%reserve` (rows) follows.
        !! Growing the capacity is itself a relocation, so this call invalidates every outstanding
        !! pointer and advances `%generation()` when it actually grows; below the current capacity
        !! it is a no-op, exactly as `%reserve` below the current row count is.
        !!
        !! `n` is the TOTAL capacity to make room for, not an increment. Deliberately a plain
        !! default `integer` and not also an int64 form: a column count cannot exceed int32 -- Arrow's
        !! own `Schema::num_fields()` is an `int32_t` and the writer already guards that ceiling --
        !! so the dual-kind rule in CLAUDE.md does not apply. Refused on a shared table, like every
        !! other structural change; does not detach.
        module subroutine table_reserve_columns(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer, intent(in) :: n                    !! total column slots to make room for.
        end subroutine table_reserve_columns
        !> How many column slots are allocated (`%ncols()` of them in use), or -- with
        !! `free=.true.` -- how many are spare.
        !!
        !! This is what `%reserve_columns` acts on, and the two are inverses: after
        !! `call t%reserve_columns(n)` with `n` above the current capacity, `%column_capacity()`
        !! is `n`. Capacity only ever grows: `%compact` releases row storage rather than slots,
        !! and `%drop_column` keeps the slot it vacated.
        !!
        !! A metadata query -- it reads no column data.
        module function table_column_capacity(self, free) result(n)
            class(parquet_table), intent(in) :: self !! the table.
            logical, intent(in), optional :: free    !! .true.: report the SPARE slots instead.
            integer :: n                             !! slots allocated, or spare.
        end function table_column_capacity
        !> Appends every row of another table. `other`'s columns must be a SUBSET of this
        !! table's, with matching kinds, widths and units; a column this table has and `other`
        !! does not is filled with nulls. A column `other` has and this table does not is an
        !! error rather than being silently dropped. Row-structural, so it DETACHES -- but a
        !! zero-row `other` adds no row, so it is still checked for compatibility and then does
        !! nothing at all.
        module subroutine table_append_table(self, other)
            class(parquet_table), intent(inout) :: self !! the table to grow.
            class(parquet_table), intent(in) :: other   !! the table whose rows are appended.
        end subroutine table_append_table
        !> Appends one row, taken from a row handle on another (or the same) table. Convenient,
        !! but slow in bulk -- build a batch with `%clone_structure` and append that instead.
        !! Row-structural, so it DETACHES.
        module subroutine table_append_row(self, r)
            class(parquet_table), intent(inout) :: self !! the table to grow.
            type(parquet_table_row), intent(in) :: r    !! the row to append.
        end subroutine table_append_row
        !> Appends `n` all-null rows (int32 count) to every column, so they can be filled in
        !! afterwards. Row-structural, so it DETACHES; `n = 0` appends nothing and does not.
        module subroutine table_append_null_rows_i32(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int32), intent(in) :: n             !! rows to append.
        end subroutine table_append_null_rows_i32
        !> Appends `n` all-null rows (int64 count). Row-structural, so it DETACHES; `n = 0`
        !! appends nothing and does not.
        module subroutine table_append_null_rows_i64(self, n)
            class(parquet_table), intent(inout) :: self !! the table.
            integer(int64), intent(in) :: n             !! rows to append.
        end subroutine table_append_null_rows_i64
        !> The shared back half of every row-structural mutation: applies `keep` to every
        !! resident column, updates the row count, and detaches. Returns without touching
        !! anything when `keep` retains every row. Private to the implementation.
        module subroutine table_apply_keep(self, keep, proc)
            class(parquet_table), intent(inout) :: self !! the table.
            logical, intent(in) :: keep(:)              !! one entry per row; .true. to retain it.
            character(len=*), intent(in) :: proc        !! calling procedure, for messages.
        end subroutine table_apply_keep
        !> Cuts the table loose from its file: sets `detached`, releases the reader (it can never
        !! be read from again) and rewrites the row scope, since the surviving rows are no longer
        !! a contiguous range of file rows. Keeps `source_file`, which `%filename` and `%clone`
        !! still need.
        module subroutine table_detach(self)
            class(parquet_table), intent(inout) :: self !! the table.
        end subroutine table_detach
        !> Whether slot `idx` takes part in a row-structural mutation: it must hold values, so a
        !! column that was never read, or whose type this library cannot read at all, does not.
        !!
        !! Such a column is skipped rather than making the mutation an error, which is what makes
        !! it possible to filter or sort a table without first reading every column it has (the
        !! whole point of a lazy table). The cost is that the skipped column can never be read
        !! afterwards -- detaching sees to that -- so it is left behind deliberately, and the
        !! detach guard is what reports it if anyone reaches for it later.
        module function table_mutable_column(self, idx) result(ok)
            class(parquet_table), intent(in) :: self !! the table.
            integer, intent(in) :: idx               !! slot index.
            logical :: ok                            !! .true. if the mutation applies to it.
        end function table_mutable_column
    end interface
    !
    ! ---- Copying a whole table (parquet_tables_clone) ----
    interface
        !> Makes `out` an independent deep copy of this table -- the way to keep a version to go
        !! back to, since mutation is in place and there is no undo.
        !!
        !! Columns already read are copied; columns not yet read stay unread, so a clone costs
        !! what the table actually holds rather than what its file contains. A live file-backed
        !! table's clone opens its OWN reader on the same file and stays lazy; a detached or
        !! in-memory table's clone has no reader, like its source.
        !!
        !! `out` must be declared as the same concrete type as `self`.
        module subroutine table_clone(self, out)
            class(parquet_table), intent(in) :: self  !! the table to copy.
            class(parquet_table), intent(out) :: out  !! receives the copy.
        end subroutine table_clone
        !> Makes `out` an EMPTY table with this table's columns: same names, kinds, widths and
        !! units, zero rows, no file behind it.
        !!
        !! This is the first half of the bulk-append idiom -- `%clone_structure` a batch, fill it
        !! with `%add_column`/`%set`, then `%append` it -- which is how a program adds many rows
        !! without appending one at a time. A batch made this way structurally cannot have the
        !! wrong column set, and abandoning a half-filled one is just a variable going out of
        !! scope. Columns whose type this library cannot read are left out.
        !!
        !! `resident_only=.true.` copies only the columns that have been read, which is also the
        !! way to clone the structure of a table whose other columns are still deferred.
        module subroutine table_clone_structure(self, out, resident_only)
            class(parquet_table), intent(in) :: self  !! the table to take the shape of.
            class(parquet_table), intent(out) :: out  !! receives the empty table.
            logical, intent(in), optional :: resident_only !! .true.: only columns already read.
        end subroutine table_clone_structure
        !> Copies the components an EXTENDING type added, which `%clone` cannot know about.
        !!
        !! `parquet_table` is designed to be extended -- a generated table type
        !! (`doc/pages/utilities/generated-tables.md`) does exactly that, and so may hand-written code. But
        !! `table_clone` only knows `parquet_table`'s own components, so anything the extension
        !! declared would arrive default-initialized and nothing would report it. Overriding this
        !! hook is how an extension copies its own state; `%clone` and `%clone_structure` each call
        !! it as their LAST action, dispatching on `self`, so the override runs for free wherever
        !! either is used.
        !!
        !! **A concrete-typed override of `%clone` itself is not possible** -- an overriding
        !! procedure must keep every dummy argument's characteristics, so `out` cannot be narrowed
        !! from `class(parquet_table)`. This hook is the supported substitute, and it keeps one name
        !! for one operation rather than adding a second spelling of "clone" that the first one
        !! silently gets wrong.
        !!
        !! The default implementation does nothing, which is correct for `parquet_table` itself.
        !! An override reaches `out`'s own components through `select type` -- use `class is`, not
        !! `type is`, so that a further extension still gets this level's copy. `%clone` has already
        !! checked that `self` and `out` have the same dynamic type, so the guarded branch always
        !! matches.
        module subroutine table_clone_extra(self, out, structure_only)
            class(parquet_table), intent(in) :: self    !! the table being copied.
            class(parquet_table), intent(inout) :: out  !! the copy, already holding the base state.
            logical, intent(in) :: structure_only       !! .true. when called from %clone_structure.
        end subroutine table_clone_extra
    end interface
    !
    ! ---- Predefined columns, for generated table types (parquet_tables_predefined) ----
    interface
        !> Binds the predefined columns a GENERATED table type declares: checks each one, converts
        !! it to the kind the schema declared, reads it, and marks the slot `predefined`.
        !!
        !! This is the one library call a generated type's `%init` makes
        !! (`doc/pages/utilities/generated-tables.md`); it is public only because a generated module is a
        !! DIFFERENT module and `parquet_table`'s components are private, so there is no other way
        !! in. Hand-written code rarely needs it -- a table opened with `parquet_open_table` already
        !! reaches every column by name.
        !!
        !! Per column, in `names` order:
        !!
        !! * a `from_file` column must exist (after any `remap:`), or this aborts naming it;
        !! * its width must equal the declared `widths` entry, or this aborts;
        !! * if its kind differs from the declared one, `%cast` converts it -- and a conversion that
        !!   can lose information (a narrowing, or an integer wider than the target real's mantissa)
        !!   emits a warning naming the table and column, since the declaration is a contract the
        !!   file does not have to honour exactly;
        !! * every `from_file` column is then read in ONE `%prefetch`, after the casts, so a
        !!   converted column decodes straight into its declared kind rather than being read twice;
        !! * a column with `from_file` `.false.` -- a `source: computed` field, or any column of a
        !!   from-scratch table -- is created with `%nrows()` all-null rows instead of being looked
        !!   for in the file.
        !!
        !! Passing `from_file` all `.false.` is exactly what an in-memory generated table does, so
        !! the same procedure serves a file-backed and a from-scratch construction.
        !!
        !! `units` FILLS IN a unit the column does not already have; it never overwrites one. A
        !! read-in MAML describes the physical file and is authoritative about what a file column
        !! holds, so a declaration must not override it -- but a computed column has no other
        !! source of a unit at all, and a file opened without a MAML has none either, which is
        !! where the declared one belongs.
        module subroutine table_bind_predefined(self, names, kinds, widths, from_file, context, exact, units)
            class(parquet_table), intent(inout) :: self   !! the table, already opened or created.
            character(len=*), intent(in) :: names(:)      !! internal column names, in declaration order.
            integer, intent(in) :: kinds(:)               !! declared PK_* kind per name.
            integer, intent(in) :: widths(:)              !! declared col_size per name (1 if scalar).
            logical, intent(in) :: from_file(:)           !! .false. for a computed/from-scratch column.
            character(len=*), intent(in), optional :: context !! schema name, for error messages.
            logical, intent(in), optional :: exact !! refuse a lossy kind conversion; forwarded
            !! verbatim to %cast, whose own default applies when this is absent.
            character(len=*), intent(in), optional :: units(:) !! declared unit per name ("" for none).
        end subroutine table_bind_predefined
    end interface
    !
    ! ---- Sort key extraction (parquet_tables_sort) ----
    interface
        !> Builds the 1-based row permutation `keys` implies, without applying it. Split out from
        !! `%sort_by` so the key extraction and the mutation can be reasoned about separately.
        module subroutine table_build_sort_permutation(self, keys, descending, nulls_first, perm)
            class(parquet_table), intent(in) :: self        !! the table.
            character(len=*), intent(in) :: keys(:)         !! key columns, primary first.
            logical, intent(in), optional :: descending(:)  !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:) !! per key: .true. to put nulls first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the 1-based permutation.
        end subroutine table_build_sort_permutation
        !> Builds the 1-based indices of the `n` rows `keys` would put first, without applying them.
        !!
        !! Split out from `%top_n` for the same reason `table_build_sort_permutation` is split out
        !! from `%sort_by`, and for one more: `sort_collect_keys` and `sort_partial_check_n` are
        !! contained procedures of the `parquet_tables_sort` submodule, which the sibling submodule
        !! holding the mutation cannot reach.
        module subroutine table_build_top_n_permutation(self, keys, n, descending, nulls_first, perm)
            class(parquet_table), intent(in) :: self        !! the table.
            character(len=*), intent(in) :: keys(:)         !! key columns, primary first.
            integer, intent(in) :: n                        !! rows to select; clamped to the row count.
            logical, intent(in), optional :: descending(:)  !! per key: .true. for descending.
            logical, intent(in), optional :: nulls_first(:) !! per key: .true. to put nulls first.
            integer(int64), allocatable, intent(out) :: perm(:) !! the selected rows, in key order.
        end subroutine table_build_top_n_permutation
    end interface
    !
    ! ---- Row selection (parquet_tables_slice, and the per-kind copies in ..._access) ----
    interface
        !> int32 form of parquet_slice_range -- see the generic interface above.
        module function slice_range_i32(start, stop, step) result(s)
            integer(int32), intent(in) :: start           !! first row (1-based).
            integer(int32), intent(in), optional :: stop  !! last row; default the table's last.
            integer(int32), intent(in), optional :: step  !! stride; default 1, never 0.
            type(parquet_slice) :: s                      !! the slice.
        end function slice_range_i32
        !> int64 form of parquet_slice_range -- see the generic interface above.
        module function slice_range_i64(start, stop, step) result(s)
            integer(int64), intent(in) :: start           !! first row (1-based).
            integer(int64), intent(in), optional :: stop  !! last row; default the table's last.
            integer(int64), intent(in), optional :: step  !! stride; default 1, never 0.
            type(parquet_slice) :: s                      !! the slice.
        end function slice_range_i64
        !> int32 form of parquet_slice_list -- see the generic interface above.
        module function slice_list_i32(indices) result(s)
            integer(int32), intent(in) :: indices(:)      !! 1-based row indices, in order.
            type(parquet_slice) :: s                      !! the slice.
        end function slice_list_i32
        !> int64 form of parquet_slice_list -- see the generic interface above.
        module function slice_list_i64(indices) result(s)
            integer(int64), intent(in) :: indices(:)      !! 1-based row indices, in order.
            type(parquet_slice) :: s                      !! the slice.
        end function slice_list_i64
        !> Turns a slice into the explicit list of rows it selects, validated against `nrows`.
        !! Every index must land inside 1..nrows and a zero step is rejected, so a caller of the
        !! per-kind copies can assume the list is safe to index with.
        module subroutine slice_resolve(s, nrows, rows, proc)
            type(parquet_slice), intent(in) :: s                  !! the slice.
            integer(int64), intent(in) :: nrows                   !! the table's row count.
            integer(int64), allocatable, intent(out) :: rows(:)   !! selected rows, in order.
            character(len=*), intent(in) :: proc                  !! caller, for the message.
        end subroutine slice_resolve
        !> Reports a sliced read whose column kind cannot be copied into the caller's array.
        module subroutine slice_kind_error(self, name, idx)
            class(parquet_table), intent(in) :: self !! the table.
            character(len=*), intent(in) :: name     !! column name.
            integer, intent(in) :: idx               !! slot index.
        end subroutine slice_kind_error
    end interface
    !
    ! ---- Row view (parquet_tables_row, and the per-kind getters in ..._access) ----
    interface
        !> Builds a handle on row `i` (int32 index) -- see the %row generic.
        module function row_at_i32(self, i) result(r)
            class(parquet_table), intent(in), target :: self !! the table.
            integer(int32), intent(in) :: i                  !! 1-based row index.
            type(parquet_table_row) :: r                     !! the handle.
        end function row_at_i32
        !> Builds a handle on row `i` (int64 index) -- see the %row generic.
        module function row_at_i64(self, i) result(r)
            class(parquet_table), intent(in), target :: self !! the table.
            integer(int64), intent(in) :: i                  !! 1-based row index.
            type(parquet_table_row) :: r                     !! the handle.
        end function row_at_i64
        !> Whether this row is null in the named column.
        module function row_is_null(self, name) result(isnull)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical :: isnull                            !! .true. if this row is null there.
        end function row_is_null
        !> Whether element `e` of this row is null in a column.
        module function row_is_null_elem(self, name, e) result(isnull)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(in) :: e              !! 1-based element index within the row.
            logical :: isnull                            !! .true. if that element is null.
        end function row_is_null_elem
        !> This row's 1-based index within its table.
        !!
        !! Deliberately `pure`, and so the one query that does NOT check the stamp: it answers
        !! about the HANDLE ("which row was I made for"), not about the table, and that answer is
        !! still true after a mutation even though reading through the handle is refused. Its
        !! column-handle counterpart `%index()` does check, because a slot number is about the
        !! table and slots renumber.
        pure module function row_index(self) result(i)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            integer(int64) :: i                          !! the row index.
        end function row_index
        !> Whether the handle is attached to a table AND still current -- one predicate, for the
        !! reason its `parquet_table_col` twin gives: a caller can do nothing useful with a handle
        !! that is one and not the other.
        module function row_is_valid(self) result(ok)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            logical :: ok                                !! .true. when it can still be used.
        end function row_is_valid
        !> Aborts unless the row handle is attached and current, naming the remedy.
        module subroutine row_check_current(self, proc)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
        end subroutine row_check_current
        !> Resolves `name` for a row-handle access, triggering the same lazy first touch the
        !! table's own accessors do. Aborts on a missing, unsupported or unreadable column.
        module subroutine row_resolve(self, name, proc, idx)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
            integer, intent(out) :: idx                  !! slot index.
        end subroutine row_resolve
        !> Rebuilds `cache%name_order` from scratch, for a change that can reorder or renumber
        !! slots -- a drop, a rename, a clone, a reset. An append uses `cache_name_index_insert`
        !! instead, because rebuilding per appended column would make opening an n-column file
        !! cost O(n^2 log n) name comparisons.
        module subroutine cache_name_index_rebuild(cache)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
        end subroutine cache_name_index_rebuild
        !> Inserts slot `slot` into `cache%name_order`, keeping it sorted by name. O(log n)
        !! comparisons plus an integer shift, so building the index one column at a time as a file
        !! is opened stays O(n log n) comparisons overall.
        module subroutine cache_name_index_insert(cache, slot)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            integer, intent(in) :: slot                       !! the newly added slot index.
        end subroutine cache_name_index_insert
        !> Grows `cache%name_order`/`name_key` to hold at least `cap` entries, keeping whatever is
        !! already indexed. Called by `%reserve_columns` so that the appends a reservation exists
        !! to make cheap do not each reallocate the index instead. Cost only: the index holds no
        !! column storage, so it has no bearing on a `%col` pointer's validity.
        module subroutine cache_name_index_reserve(cache, cap)
            type(parquet_table_cache), intent(inout) :: cache !! the column store.
            integer, intent(in) :: cap                        !! entries to make room for.
        end subroutine cache_name_index_reserve
        !> Resolves `name` to its 1-based slot index in `cache`, or 0 when absent. The one place
        !! a name becomes an index, shared by the table and by a row handle.
        module function cache_find(cache, name) result(idx)
            type(parquet_table_cache), intent(in) :: cache !! the column store.
            character(len=*), intent(in) :: name           !! column name.
            integer :: idx                                 !! slot index, or 0.
        end function cache_find
        !> TEST-ONLY -- deallocates this table's name index, so the next lookup has to take
        !! `cache_find`'s linear-scan fallback.
        !!
        !! **This is a debug hook, not API**, public for the same reason
        !! `parquet_debug_table_set_inflight` is: the index lives on `parquet_table_cache`, whose
        !! components are private to this module, so nothing outside can reach it. It is excluded
        !! from README.md's API overview and no library code calls it.
        !!
        !! It exists because that fallback is a SAFETY NET with no route to it through the public
        !! API: every mutation maintains the index eagerly, so a correct library never reaches the
        !! scan, and it was measured executing zero times across the whole suite and every error
        !! scenario. What it protects against is a future mutation that forgets to maintain the
        !! index -- which would otherwise turn into wrong-column answers rather than a slower
        !! lookup. Untested, the net could rot away and nothing would say so.
        !!
        !! `had_index` is not optional on purpose: dropping the index is invisible from outside, so
        !! a test that did not check it would pass just as happily against a hook that did nothing.
        !! It reports whether an index was there to drop, which makes the before/after states
        !! assertable -- `.true.` on the first call, `.false.` on a second one.
        module subroutine parquet_debug_table_drop_name_index(table, had_index)
            type(parquet_table), intent(in) :: table !! the table whose index to drop.
            logical, intent(out) :: had_index        !! .true. when an index was present.
        end subroutine parquet_debug_table_drop_name_index
        !> error stops unless slot `idx` holds exactly `kind` -- the row handle's counterpart of
        !! `table_require_kind`, for the string kinds, which have no widening to fall back on.
        module subroutine row_require_kind(self, name, idx, kind)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer, intent(in) :: idx                   !! slot index.
            integer, intent(in) :: kind                  !! required PK_* discriminator.
        end subroutine row_require_kind
        !> Reports a row read whose column kind cannot be copied into the caller's variable.
        module subroutine row_kind_error(self, name, idx)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer, intent(in) :: idx                   !! slot index.
        end subroutine row_kind_error
        !> Aborts unless column handle `c` can be used to reach a cell of THIS row.
        !!
        !! Three checks, and the third is the one a caller cannot make for itself: the row handle
        !! must be attached, `c` must be attached and current (`col_resolve`), and the two must
        !! name the SAME table. A handle from another table would otherwise read that table's
        !! column at this row's index -- a wrong answer rather than an error, since both tables
        !! are perfectly valid objects.
        !!
        !! It then checks the row index against `c`'s scope. Both handles are current by that point,
        !! so the two scopes agree; taking `c`'s keeps the bounds check reading from the handle
        !! validated most recently.
        module subroutine row_require_col(self, c, proc)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! the column handle to validate.
            character(len=*), intent(in) :: proc         !! calling procedure, for the message.
        end subroutine row_require_col
        !> This row's value from a PK_INT32 column.
        module subroutine row_get_i32(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), intent(out) :: value          !! receives the value.
        end subroutine row_get_i32
        !> This row's value from a PK_INT64 column.
        !! Also accepts a PK_INT32 column, widening on the way out.
        module subroutine row_get_i64(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(out) :: value          !! receives the value.
        end subroutine row_get_i64
        !> This row's value from a PK_FLOAT32 column.
        module subroutine row_get_f32(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), intent(out) :: value            !! receives the value.
        end subroutine row_get_f32
        !> This row's value from a PK_FLOAT64 column.
        !! Also accepts a PK_FLOAT32 column, widening on the way out.
        module subroutine row_get_f64(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), intent(out) :: value            !! receives the value.
        end subroutine row_get_f64
        !> This row's value from a PK_LOGICAL column.
        module subroutine row_get_bool(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, intent(out) :: value                 !! receives the value.
        end subroutine row_get_bool
        !> This row's string value from a PK_STRING column.
        module subroutine row_get_str(self, name, value)
            class(parquet_table_row), intent(in) :: self          !! the row handle.
            character(len=*), intent(in) :: name                  !! column name.
            character(len=:), allocatable, intent(out) :: value   !! receives the value.
        end subroutine row_get_str
        !> This row's value from a PK_DATE column.
        module subroutine row_get_date(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), intent(out) :: value      !! receives the value.
        end subroutine row_get_date
        !> This row's value from a PK_TIME column.
        module subroutine row_get_time(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), intent(out) :: value      !! receives the value.
        end subroutine row_get_time
        !> This row's value from a PK_TIMESTAMP column.
        module subroutine row_get_ts(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), intent(out) :: value !! receives the value.
        end subroutine row_get_ts
        !> This row's vector from a PK_INT32_VEC column, one array element per position.
        module subroutine row_get_i32v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_i32v
        !> This row's vector from a PK_INT64_VEC column, one array element per position.
        !! Also accepts a PK_INT32_VEC column, widening on the way out.
        module subroutine row_get_i64v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_i64v
        !> This row's vector from a PK_FLOAT32_VEC column, one array element per position.
        module subroutine row_get_f32v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_f32v
        !> This row's vector from a PK_FLOAT64_VEC column, one array element per position.
        !! Also accepts a PK_FLOAT32_VEC column, widening on the way out.
        module subroutine row_get_f64v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_f64v
        !> This row's vector from a PK_LOGICAL_VEC column, one array element per position.
        module subroutine row_get_boolv(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_boolv
        !> This row's string vector from a PK_STRING_VEC column, one array element per position.
        module subroutine row_get_strv(self, name, value)
            class(parquet_table_row), intent(in) :: self             !! the row handle.
            character(len=*), intent(in) :: name                     !! column name.
            character(len=:), allocatable, intent(out) :: value(:)   !! receives width values.
        end subroutine row_get_strv
        !> This row's vector from a PK_DATE_VEC column, one array element per position.
        module subroutine row_get_datev(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_datev
        !> This row's vector from a PK_TIME_VEC column, one array element per position.
        module subroutine row_get_timev(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_timev
        !> This row's vector from a PK_TIMESTAMP_VEC column, one array element per position.
        module subroutine row_get_tsv(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives width values.
        end subroutine row_get_tsv
        !> Writes this row's value into a PK_INT32 column.
        module subroutine row_set_i32(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), intent(in) :: value             !! the new value.
        end subroutine row_set_i32
        !> Writes this row's value into a PK_INT64 column.
        module subroutine row_set_i64(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(in) :: value             !! the new value.
        end subroutine row_set_i64
        !> Writes this row's value into a PK_FLOAT32 column.
        module subroutine row_set_f32(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), intent(in) :: value               !! the new value.
        end subroutine row_set_f32
        !> Writes this row's value into a PK_FLOAT64 column.
        module subroutine row_set_f64(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), intent(in) :: value               !! the new value.
        end subroutine row_set_f64
        !> Writes this row's value into a PK_LOGICAL column.
        module subroutine row_set_bool(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, intent(in) :: value                    !! the new value.
        end subroutine row_set_bool
        !> Writes this row's string value into a PK_STRING column.
        module subroutine row_set_str(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            character(len=*), intent(in) :: value        !! the new value.
        end subroutine row_set_str
        !> Writes this row's value into a PK_DATE column.
        module subroutine row_set_date(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), intent(in) :: value         !! the new value.
        end subroutine row_set_date
        !> Writes this row's value into a PK_TIME column.
        module subroutine row_set_time(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), intent(in) :: value         !! the new value.
        end subroutine row_set_time
        !> Writes this row's value into a PK_TIMESTAMP column.
        module subroutine row_set_ts(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), intent(in) :: value    !! the new value.
        end subroutine row_set_ts
        !> Writes this row's value into a PK_INT32_VEC column.
        module subroutine row_set_i32v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), intent(in) :: value(:)          !! that row's whole vector.
        end subroutine row_set_i32v
        !> Writes this row's value into a PK_INT64_VEC column.
        module subroutine row_set_i64v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), intent(in) :: value(:)          !! that row's whole vector.
        end subroutine row_set_i64v
        !> Writes this row's value into a PK_FLOAT32_VEC column.
        module subroutine row_set_f32v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), intent(in) :: value(:)            !! that row's whole vector.
        end subroutine row_set_f32v
        !> Writes this row's value into a PK_FLOAT64_VEC column.
        module subroutine row_set_f64v(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), intent(in) :: value(:)            !! that row's whole vector.
        end subroutine row_set_f64v
        !> Writes this row's value into a PK_LOGICAL_VEC column.
        module subroutine row_set_boolv(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, intent(in) :: value(:)                 !! that row's whole vector.
        end subroutine row_set_boolv
        !> Writes this row's whole string vector into a PK_STRING_VEC column.
        module subroutine row_set_strv(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            character(len=*), intent(in) :: value(:)     !! width values.
        end subroutine row_set_strv
        !> Writes this row's value into a PK_DATE_VEC column.
        module subroutine row_set_datev(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), intent(in) :: value(:)      !! that row's whole vector.
        end subroutine row_set_datev
        !> Writes this row's value into a PK_TIME_VEC column.
        module subroutine row_set_timev(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), intent(in) :: value(:)      !! that row's whole vector.
        end subroutine row_set_timev
        !> Writes this row's value into a PK_TIMESTAMP_VEC column.
        module subroutine row_set_tsv(self, name, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), intent(in) :: value(:) !! that row's whole vector.
        end subroutine row_set_tsv
        !> This row's value from a PK_INT32 column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_i32(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            integer(int32), intent(out) :: value !! receives the value.
        end subroutine row_get_col_i32
        !> Writes this row's value in a PK_INT32 column named by an already-resolved handle.
        module subroutine row_set_col_i32(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            integer(int32), intent(in) :: value !! the value to write.
        end subroutine row_set_col_i32
        !> This row's value from a PK_INT64 column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_i64(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            integer(int64), intent(out) :: value !! receives the value.
        end subroutine row_get_col_i64
        !> Writes this row's value in a PK_INT64 column named by an already-resolved handle.
        module subroutine row_set_col_i64(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            integer(int64), intent(in) :: value !! the value to write.
        end subroutine row_set_col_i64
        !> This row's value from a PK_FLOAT32 column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_f32(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            real(real32), intent(out) :: value !! receives the value.
        end subroutine row_get_col_f32
        !> Writes this row's value in a PK_FLOAT32 column named by an already-resolved handle.
        module subroutine row_set_col_f32(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            real(real32), intent(in) :: value !! the value to write.
        end subroutine row_set_col_f32
        !> This row's value from a PK_FLOAT64 column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_f64(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            real(real64), intent(out) :: value !! receives the value.
        end subroutine row_get_col_f64
        !> Writes this row's value in a PK_FLOAT64 column named by an already-resolved handle.
        module subroutine row_set_col_f64(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            real(real64), intent(in) :: value !! the value to write.
        end subroutine row_set_col_f64
        !> This row's value from a PK_LOGICAL column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_bool(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            logical, intent(out) :: value !! receives the value.
        end subroutine row_get_col_bool
        !> Writes this row's value in a PK_LOGICAL column named by an already-resolved handle.
        module subroutine row_set_col_bool(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            logical, intent(in) :: value !! the value to write.
        end subroutine row_set_col_bool
        !> This row's value from a PK_STRING column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_str(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            character(len=:), allocatable, intent(out) :: value !! receives the value.
        end subroutine row_get_col_str
        !> Writes this row's value in a PK_STRING column named by an already-resolved handle.
        module subroutine row_set_col_str(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            character(len=*), intent(in) :: value !! the value to write.
        end subroutine row_set_col_str
        !> This row's value from a PK_DATE column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_date(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_date), intent(out) :: value !! receives the value.
        end subroutine row_get_col_date
        !> Writes this row's value in a PK_DATE column named by an already-resolved handle.
        module subroutine row_set_col_date(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_date), intent(in) :: value !! the value to write.
        end subroutine row_set_col_date
        !> This row's value from a PK_TIME column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_time(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_time), intent(out) :: value !! receives the value.
        end subroutine row_get_col_time
        !> Writes this row's value in a PK_TIME column named by an already-resolved handle.
        module subroutine row_set_col_time(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_time), intent(in) :: value !! the value to write.
        end subroutine row_set_col_time
        !> This row's value from a PK_TIMESTAMP column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_ts(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_timestamp), intent(out) :: value !! receives the value.
        end subroutine row_get_col_ts
        !> Writes this row's value in a PK_TIMESTAMP column named by an already-resolved handle.
        module subroutine row_set_col_ts(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_timestamp), intent(in) :: value !! the value to write.
        end subroutine row_set_col_ts
        !> This row's value from a PK_INT32_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_i32v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            integer(int32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_i32v
        !> Writes this row's value in a PK_INT32_VEC column named by an already-resolved handle.
        module subroutine row_set_col_i32v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            integer(int32), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_i32v
        !> This row's value from a PK_INT64_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_i64v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            integer(int64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_i64v
        !> Writes this row's value in a PK_INT64_VEC column named by an already-resolved handle.
        module subroutine row_set_col_i64v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            integer(int64), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_i64v
        !> This row's value from a PK_FLOAT32_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_f32v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            real(real32), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_f32v
        !> Writes this row's value in a PK_FLOAT32_VEC column named by an already-resolved handle.
        module subroutine row_set_col_f32v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            real(real32), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_f32v
        !> This row's value from a PK_FLOAT64_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_f64v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            real(real64), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_f64v
        !> Writes this row's value in a PK_FLOAT64_VEC column named by an already-resolved handle.
        module subroutine row_set_col_f64v(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            real(real64), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_f64v
        !> This row's value from a PK_LOGICAL_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_boolv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            logical, allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_boolv
        !> Writes this row's value in a PK_LOGICAL_VEC column named by an already-resolved handle.
        module subroutine row_set_col_boolv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            logical, intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_boolv
        !> This row's value from a PK_STRING_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_strv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            character(len=:), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_strv
        !> Writes this row's value in a PK_STRING_VEC column named by an already-resolved handle.
        module subroutine row_set_col_strv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            character(len=*), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_strv
        !> This row's value from a PK_DATE_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_datev(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_date), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_datev
        !> Writes this row's value in a PK_DATE_VEC column named by an already-resolved handle.
        module subroutine row_set_col_datev(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_date), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_datev
        !> This row's value from a PK_TIME_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_timev(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_time), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_timev
        !> Writes this row's value in a PK_TIME_VEC column named by an already-resolved handle.
        module subroutine row_set_col_timev(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_time), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_timev
        !> This row's value from a PK_TIMESTAMP_VEC column named by an already-resolved handle.
        !!
        !! The same operation as `%get(name, value)` over the same body -- what it saves is the
        !! name lookup, which a loop over rows would otherwise pay once per access. The handle
        !! must belong to THIS row's table and must still be current.
        module subroutine row_get_col_tsv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to read.
            type(parquet_timestamp), allocatable, intent(out) :: value(:) !! receives the value.
        end subroutine row_get_col_tsv
        !> Writes this row's value in a PK_TIMESTAMP_VEC column named by an already-resolved handle.
        module subroutine row_set_col_tsv(self, c, value)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            type(parquet_table_col), intent(in) :: c     !! a handle on the column to write.
            type(parquet_timestamp), intent(in) :: value(:) !! the value to write.
        end subroutine row_set_col_tsv
        !> Points `p` at this row's storage in a PK_INT32 column. The kind must match EXACTLY.
        module subroutine row_ref_i32(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), pointer, intent(out) :: p       !! alias to this row's value.
        end subroutine row_ref_i32
        !> Points `p` at this row's storage in a PK_INT64 column. The kind must match EXACTLY.
        module subroutine row_ref_i64(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), pointer, intent(out) :: p       !! alias to this row's value.
        end subroutine row_ref_i64
        !> Points `p` at this row's storage in a PK_FLOAT32 column. The kind must match EXACTLY.
        module subroutine row_ref_f32(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), pointer, intent(out) :: p         !! alias to this row's value.
        end subroutine row_ref_f32
        !> Points `p` at this row's storage in a PK_FLOAT64 column. The kind must match EXACTLY.
        module subroutine row_ref_f64(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), pointer, intent(out) :: p         !! alias to this row's value.
        end subroutine row_ref_f64
        !> Points `p` at this row's storage in a PK_LOGICAL column. The kind must match EXACTLY.
        module subroutine row_ref_bool(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, pointer, intent(out) :: p              !! alias to this row's value.
        end subroutine row_ref_bool
        !> Points `p` at this row's storage in a PK_DATE column. The kind must match EXACTLY.
        module subroutine row_ref_date(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), pointer, intent(out) :: p   !! alias to this row's value.
        end subroutine row_ref_date
        !> Points `p` at this row's storage in a PK_TIME column. The kind must match EXACTLY.
        module subroutine row_ref_time(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), pointer, intent(out) :: p   !! alias to this row's value.
        end subroutine row_ref_time
        !> Points `p` at this row's storage in a PK_TIMESTAMP column. The kind must match EXACTLY.
        module subroutine row_ref_ts(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), pointer, intent(out) :: p !! alias to this row's value.
        end subroutine row_ref_ts
        !> Points `p` at this row's storage in a PK_INT32_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_i32v(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int32), pointer, intent(out) :: p(:)    !! alias to this row's vector.
        end subroutine row_ref_i32v
        !> Points `p` at this row's storage in a PK_INT64_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_i64v(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            integer(int64), pointer, intent(out) :: p(:)    !! alias to this row's vector.
        end subroutine row_ref_i64v
        !> Points `p` at this row's storage in a PK_FLOAT32_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_f32v(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real32), pointer, intent(out) :: p(:)      !! alias to this row's vector.
        end subroutine row_ref_f32v
        !> Points `p` at this row's storage in a PK_FLOAT64_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_f64v(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            real(real64), pointer, intent(out) :: p(:)      !! alias to this row's vector.
        end subroutine row_ref_f64v
        !> Points `p` at this row's storage in a PK_LOGICAL_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_boolv(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            logical, pointer, intent(out) :: p(:)           !! alias to this row's vector.
        end subroutine row_ref_boolv
        !> Points `p` at this row's storage in a PK_DATE_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_datev(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_date), pointer, intent(out) :: p(:) !! alias to this row's vector.
        end subroutine row_ref_datev
        !> Points `p` at this row's storage in a PK_TIME_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_timev(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_time), pointer, intent(out) :: p(:) !! alias to this row's vector.
        end subroutine row_ref_timev
        !> Points `p` at this row's storage in a PK_TIMESTAMP_VEC column. The kind must match EXACTLY.
        module subroutine row_ref_tsv(self, name, p)
            class(parquet_table_row), intent(in) :: self !! the row handle.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_timestamp), pointer, intent(out) :: p(:) !! alias to this row's vector.
        end subroutine row_ref_tsv
        !> Copies the rows `s` selects from a PK_INT32 column into `arr`.
        module subroutine get_slice_i32(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            integer(int32), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_i32
        !> Copies the rows `s` selects from a PK_INT64 column into `arr`.
        !! Also accepts a PK_INT32 column, widening on the way out.
        module subroutine get_slice_i64(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            integer(int64), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_i64
        !> Copies the rows `s` selects from a PK_FLOAT32 column into `arr`.
        module subroutine get_slice_f32(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            real(real32), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_f32
        !> Copies the rows `s` selects from a PK_FLOAT64 column into `arr`.
        !! Also accepts a PK_FLOAT32 column, widening on the way out.
        module subroutine get_slice_f64(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            real(real64), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_f64
        !> Copies the rows `s` selects from a PK_LOGICAL column into `arr`.
        module subroutine get_slice_bool(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            logical, allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_bool
        !> Copies the rows `s` selects from a PK_DATE column into `arr`.
        module subroutine get_slice_date(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_date), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_date
        !> Copies the rows `s` selects from a PK_TIME column into `arr`.
        module subroutine get_slice_time(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_time), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_time
        !> Copies the rows `s` selects from a PK_TIMESTAMP column into `arr`.
        module subroutine get_slice_ts(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_timestamp), allocatable, intent(out) :: arr(:) !! one value per row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_ts
        !> Copies the rows `s` selects from a PK_INT32_VEC column into `arr`.
        module subroutine get_slice_i32v(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            integer(int32), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_i32v
        !> Copies the rows `s` selects from a PK_INT64_VEC column into `arr`.
        !! Also accepts a PK_INT32_VEC column, widening on the way out.
        module subroutine get_slice_i64v(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            integer(int64), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_i64v
        !> Copies the rows `s` selects from a PK_FLOAT32_VEC column into `arr`.
        module subroutine get_slice_f32v(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            real(real32), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_f32v
        !> Copies the rows `s` selects from a PK_FLOAT64_VEC column into `arr`.
        !! Also accepts a PK_FLOAT32_VEC column, widening on the way out.
        module subroutine get_slice_f64v(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            real(real64), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_f64v
        !> Copies the rows `s` selects from a PK_LOGICAL_VEC column into `arr`.
        module subroutine get_slice_boolv(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            logical, allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_boolv
        !> Copies the rows `s` selects from a PK_DATE_VEC column into `arr`.
        module subroutine get_slice_datev(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_date), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_datev
        !> Copies the rows `s` selects from a PK_TIME_VEC column into `arr`.
        module subroutine get_slice_timev(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_time), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_timev
        !> Copies the rows `s` selects from a PK_TIMESTAMP_VEC column into `arr`.
        module subroutine get_slice_tsv(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self     !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to pick.
            type(parquet_timestamp), allocatable, intent(out) :: arr(:,:) !! (element, row), shaped (width, nrows).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_tsv
        !> Copies the rows `s` selects from a PK_STRING column into a compact string column.
        module subroutine get_slice_str(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self             !! the table.
            character(len=*), intent(in) :: name                 !! column name.
            type(parquet_slice), intent(in) :: s                 !! rows to pick.
            type(parquet_string_column), intent(out) :: arr      !! the selected elements.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_str
        !> Copies the rows `s` selects from a PK_STRING column into a character array, sized to
        !! the longest element selected.
        module subroutine get_slice_chr(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self                 !! the table.
            character(len=*), intent(in) :: name                     !! column name.
            type(parquet_slice), intent(in) :: s                     !! rows to pick.
            character(len=:), allocatable, intent(out) :: arr(:)     !! one value per selected row.
            logical, allocatable, intent(out), optional :: is_valid(:) !! present: validity of the selected rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_chr
        !> Copies the rows `s` selects from a PK_STRING_VEC column, shaped (width, selected).
        module subroutine get_slice_chrv(self, name, s, arr, is_valid, found)
            class(parquet_table), intent(in) :: self                 !! the table.
            character(len=*), intent(in) :: name                     !! column name.
            type(parquet_slice), intent(in) :: s                     !! rows to pick.
            character(len=:), allocatable, intent(out) :: arr(:,:)   !! (element, selected row).
            logical, allocatable, intent(out), optional :: is_valid(:,:) !! present: per-element validity of the picked rows.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine get_slice_chrv
        !> Writes `arr` into the rows `s` selects of a PK_INT32 column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_i32(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            integer(int32), intent(in) :: arr(:)       !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_i32
        !> Writes `arr` into the rows `s` selects of a PK_INT64 column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_i64(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            integer(int64), intent(in) :: arr(:)       !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_i64
        !> Writes `arr` into the rows `s` selects of a PK_FLOAT32 column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_f32(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            real(real32), intent(in) :: arr(:)         !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_f32
        !> Writes `arr` into the rows `s` selects of a PK_FLOAT64 column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_f64(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            real(real64), intent(in) :: arr(:)         !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_f64
        !> Writes `arr` into the rows `s` selects of a PK_LOGICAL column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_bool(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            logical, intent(in) :: arr(:)              !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_bool
        !> Writes `arr` into the rows `s` selects of a PK_DATE column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_date(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_date), intent(in) :: arr(:)   !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_date
        !> Writes `arr` into the rows `s` selects of a PK_TIME column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_time(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_time), intent(in) :: arr(:)   !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_time
        !> Writes `arr` into the rows `s` selects of a PK_TIMESTAMP column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_ts(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_timestamp), intent(in) :: arr(:) !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_ts
        !> Writes `arr` into the rows `s` selects of a PK_INT32_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_i32v(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            integer(int32), intent(in) :: arr(:,:)       !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_i32v
        !> Writes `arr` into the rows `s` selects of a PK_INT64_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_i64v(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            integer(int64), intent(in) :: arr(:,:)       !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_i64v
        !> Writes `arr` into the rows `s` selects of a PK_FLOAT32_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_f32v(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            real(real32), intent(in) :: arr(:,:)         !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_f32v
        !> Writes `arr` into the rows `s` selects of a PK_FLOAT64_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_f64v(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            real(real64), intent(in) :: arr(:,:)         !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_f64v
        !> Writes `arr` into the rows `s` selects of a PK_LOGICAL_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_boolv(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            logical, intent(in) :: arr(:,:)              !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_boolv
        !> Writes `arr` into the rows `s` selects of a PK_DATE_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_datev(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_date), intent(in) :: arr(:,:)   !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_datev
        !> Writes `arr` into the rows `s` selects of a PK_TIME_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_timev(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_time), intent(in) :: arr(:,:)   !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_timev
        !> Writes `arr` into the rows `s` selects of a PK_TIMESTAMP_VEC column.
        !!
        !! The mirror of %get_slice: same selection object, same order, and the array must have
        !! exactly one value (or one vector) per selected row. The kind must match EXACTLY, as it
        !! does for %set -- a copy INTO the table never widens.
        module subroutine set_slice_tsv(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            type(parquet_timestamp), intent(in) :: arr(:,:) !! one value per selected row.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null entry null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_tsv
        !> Writes a character array into the rows `s` selects of a PK_STRING column. Trailing
        !! blanks are trimmed, as in `%set`.
        module subroutine set_slice_chr(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            character(len=*), intent(in) :: arr(:)       !! one value per selected row.
            logical, intent(in), optional :: is_valid(:) !! present: selected rows marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null row null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_chr
        !> Writes a character (element, row) array into the rows `s` selects of a PK_STRING_VEC column.
        module subroutine set_slice_chrv(self, name, s, arr, is_valid, modify_nulls, found)
            class(parquet_table), intent(inout) :: self  !! the table.
            character(len=*), intent(in) :: name         !! column name.
            type(parquet_slice), intent(in) :: s         !! rows to write.
            character(len=*), intent(in) :: arr(:,:)     !! (element, selected row) values.
            logical, intent(in), optional :: is_valid(:,:) !! present: selected elements marked .false. become null.
            logical, intent(in), optional :: modify_nulls !! .false. leaves a selected null row null.
            logical, intent(out), optional :: found      !! present: report a miss instead of aborting.
        end subroutine set_slice_chrv
    end interface
    !
    ! ---- Per-kind materialization (parquet_tables_materialize) ----
    interface
        !> Reads a PK_INT32 file column into `col`, carrying its nulls across.
        module subroutine mat_i32(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_i32
        !> Reads a PK_INT64 file column into `col`, carrying its nulls across.
        module subroutine mat_i64(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_i64
        !> Reads a PK_FLOAT32 file column into `col`, carrying its nulls across.
        module subroutine mat_f32(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_f32
        !> Reads a PK_FLOAT64 file column into `col`, carrying its nulls across.
        module subroutine mat_f64(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_f64
        !> Reads a PK_LOGICAL file column into `col`, carrying its nulls across.
        module subroutine mat_bool(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_bool
        !> Reads a PK_STRING file column into `col`, carrying its nulls across.
        module subroutine mat_str(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_str
        !> Reads a PK_DATE file column into `col`, carrying its nulls across.
        module subroutine mat_date(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_date
        !> Reads a PK_TIME file column into `col`, carrying its nulls across.
        module subroutine mat_time(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_time
        !> Reads a PK_TIMESTAMP file column into `col`, carrying its nulls across.
        module subroutine mat_ts(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_ts
        !> Reads a PK_INT32_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_i32v(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_i32v
        !> Reads a PK_INT64_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_i64v(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_i64v
        !> Reads a PK_FLOAT32_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_f32v(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_f32v
        !> Reads a PK_FLOAT64_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_f64v(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_f64v
        !> Reads a PK_LOGICAL_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_boolv(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_boolv
        !> Reads a PK_STRING_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_strv(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_strv
        !> Reads a PK_DATE_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_datev(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_datev
        !> Reads a PK_TIME_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_timev(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_timev
        !> Reads a PK_TIMESTAMP_VEC file column into `col`, carrying its nulls across.
        module subroutine mat_tsv(reader, name, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine mat_tsv
        !> Reads ONE ROW GROUP of a PK_INT32 file column into `col`, carrying its nulls across.
        module subroutine matchunk_i32(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_i32
        !> Reads ONE ROW GROUP of a PK_INT64 file column into `col`, carrying its nulls across.
        module subroutine matchunk_i64(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_i64
        !> Reads ONE ROW GROUP of a PK_FLOAT32 file column into `col`, carrying its nulls across.
        module subroutine matchunk_f32(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_f32
        !> Reads ONE ROW GROUP of a PK_FLOAT64 file column into `col`, carrying its nulls across.
        module subroutine matchunk_f64(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_f64
        !> Reads ONE ROW GROUP of a PK_LOGICAL file column into `col`, carrying its nulls across.
        module subroutine matchunk_bool(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_bool
        !> Reads ONE ROW GROUP of a PK_STRING file column into `col`, carrying its nulls across.
        module subroutine matchunk_str(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_str
        !> Reads ONE ROW GROUP of a PK_DATE file column into `col`, carrying its nulls across.
        module subroutine matchunk_date(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_date
        !> Reads ONE ROW GROUP of a PK_TIME file column into `col`, carrying its nulls across.
        module subroutine matchunk_time(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_time
        !> Reads ONE ROW GROUP of a PK_TIMESTAMP file column into `col`, carrying its nulls across.
        module subroutine matchunk_ts(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_ts
        !> Reads ONE ROW GROUP of a PK_INT32_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_i32v(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_i32v
        !> Reads ONE ROW GROUP of a PK_INT64_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_i64v(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_i64v
        !> Reads ONE ROW GROUP of a PK_FLOAT32_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_f32v(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_f32v
        !> Reads ONE ROW GROUP of a PK_FLOAT64_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_f64v(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_f64v
        !> Reads ONE ROW GROUP of a PK_LOGICAL_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_boolv(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_boolv
        !> Reads ONE ROW GROUP of a PK_STRING_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_strv(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_strv
        !> Reads ONE ROW GROUP of a PK_DATE_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_datev(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_datev
        !> Reads ONE ROW GROUP of a PK_TIME_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_timev(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_timev
        !> Reads ONE ROW GROUP of a PK_TIMESTAMP_VEC file column into `col`, carrying its nulls across.
        module subroutine matchunk_tsv(reader, name, rg, col, nrows, wdt, unit)
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine matchunk_tsv
        !> Dispatches one file column's read to the specific matching `kind`.
        module subroutine table_materialize_kind(kind, reader, name, col, nrows, wdt, unit)
            integer, intent(in) :: kind                  !! PK_* discriminator to read as.
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! rows to read.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine table_materialize_kind
        !> Dispatches ONE ROW GROUP of a column to the specific matching `kind`. Same contract
        !! as `table_materialize_kind`, scoped to a single row group -- the primitive the slice
        !! regime assembles a column from.
        module subroutine table_materialize_chunk_kind(kind, reader, name, rg, col, nrows, wdt, unit)
            integer, intent(in) :: kind                  !! PK_* discriminator to read as.
            type(parquet_reader), intent(in) :: reader   !! open reader.
            character(len=*), intent(in) :: name         !! file column name.
            integer(int64), intent(in) :: rg             !! 1-based row group.
            type(parquet_column), intent(inout) :: col   !! value store to fill.
            integer(int64), intent(in) :: nrows          !! that row group's own row count.
            integer(int32), intent(in) :: wdt            !! values per row.
            character(len=*), intent(in) :: unit         !! unit string to store ("" for none).
        end subroutine table_materialize_chunk_kind
    end interface
    !
contains
    !
    !> FINAL procedure: frees the column store and abandons the reader. Runs at scope exit
    !! and on an intent(out) reopen, so it must always succeed silently -- it validates
    !! nothing. Module-contained on purpose; see the note in the generator that emits it.
    subroutine table_finalize(self)
        type(parquet_table), intent(inout) :: self !! the table being destroyed.
        ! An implicit finalizer runs at unpredictable points -- scope exit, an intent(out)
        ! reopen, an early return -- with no caller able to see or handle a failure, so it
        ! must always succeed silently and validate nothing (CLAUDE.md). Deallocating the
        ! cache runs parquet_reader's own finalizer, which abandons rather than closes it.
        if (associated(self%cache)) then
            ! The lock is an OpenMP handle rather than a value, so it has to be destroyed
            ! explicitly -- deallocating the cache would otherwise leak whatever the
            ! runtime allocated for it. table_destroy_lock validates nothing and cannot
            ! abort, which is what makes it safe to call from here.
            call table_destroy_lock(self%cache)
            deallocate(self%cache)
            nullify(self%cache)
        end if
    end subroutine table_finalize
    !
end module parquet_tables ! GCOVR_EXCL_LINE -- gcov attribution artifact