!=========================================== ! Author: Elmo Tempel (elmo.tempel@ut.ee) !=========================================== !> Temporal (date/time/timestamp) write specifics, scalar and matrix, whole- !> column and row-group-chunked: bodies of the module procedures declared in !> parquet_core.f90's interface block, plus the temporal-only private helpers !> (preamble/unit-resolution/validity/flat-write workers) they depend on. submodule (parquet_core:parquet_write) parquet_write_temporal implicit none contains !> Shared bookkeeping for every temporal write specific: schema checks (defined, enabled, !> col_size, exact data_type match), mark-written, and output-name resolution. Returns !> do_write=.false. (caller returns without writing) for a defined-but-disabled column; !> `idx` is the schema column index (0 for a schema-less writer), for unit resolution. subroutine temporal_write_preamble(writer, name, expected_type, asize, do_write, outname, idx) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. character(len=*), intent(in) :: expected_type !! "date"/"time"/"timestamp". integer(int64), intent(in) :: asize !! per-row element count (1 for a scalar column). logical, intent(out) :: do_write !! .true. if the column should actually be written. character(len=:), allocatable, intent(out) :: outname !! resolved output (file) name. integer, intent(out) :: idx !! schema column index, or 0 for a schema-less writer. character(len=:), allocatable :: ctx !! writer_context_suffix scratch. do_write = .false. idx = 0 if (writer%is_schema_enforced) then idx = parquet_get_defined_column_index(writer, name) call writer_context_suffix(writer, ctx) if (idx == 0) error stop & "parquet_write_column: column not defined in parquet_open_writer: " // trim(name) // ctx if (.not. writer%all_columns(idx)%is_set) return call parquet_resolve_or_check_col_size(writer, name, idx, asize, "parquet_write_column", ctx) if (trim(writer%all_columns(idx)%data_type) /= expected_type) then error stop "parquet_write_column: type mismatch for column " // trim(name) // & " (expected " // expected_type // ", got " // trim(writer%all_columns(idx)%data_type) // ")" // ctx end if end if if (.not. parquet_is_column_enabled(writer, name)) return call parquet_mark_column_written(writer, name) call parquet_resolve_output_name(writer, name, outname) do_write = .true. end subroutine temporal_write_preamble !> Resolves the file unit (a parquet_unit_* selector) and UTC flag for a temporal write: !> the schema-declared unit/utc when the column comes from a MAML/schema (idx > 0 and its !> time_unit is set), else the fixed default (microseconds, timezone-naive) for a !> schema-less writer or a schema column with no explicit unit. subroutine resolve_temporal_write_unit(writer, idx, unit, is_utc) type(parquet_writer), intent(in) :: writer !! open writer. integer, intent(in) :: idx !! schema column index, or 0 for schema-less. integer, intent(out) :: unit !! resolved unit selector. integer(c_int32_t), intent(out) :: is_utc !! 1 if UTC-adjusted, else 0. unit = parquet_unit_micros is_utc = 0_c_int32_t if (idx > 0) then if (writer%all_columns(idx)%time_unit /= 0) unit = writer%all_columns(idx)%time_unit if (writer%all_columns(idx)%is_utc) is_utc = 1_c_int32_t end if end subroutine resolve_temporal_write_unit !> Builds the write-side validity pointer for a temporal column: a null-free column is !> written non-nullable (c_null_ptr); otherwise a 1=valid/0=null buffer is built from the !> per-element null mask, and (for a schema-enforced writer) parquet_check_protected enforces !> that no protected column receives a null. subroutine temporal_valid_ptr(writer, name, is_null_mask, valid_buf, valid_ptr) type(parquet_writer), intent(in) :: writer !! open writer. character(len=*), intent(in) :: name !! column name (for the protected-column check). logical, intent(in) :: is_null_mask(:) !! .true. where the element is null. integer(c_int8_t), allocatable, target, intent(out) :: valid_buf(:) !! backing buffer for valid_ptr. type(c_ptr), intent(out) :: valid_ptr !! c_loc(valid_buf), or c_null_ptr if no nulls. if (any(is_null_mask)) then if (writer%is_schema_enforced) then call parquet_check_protected(writer, name, .not. is_null_mask) call parquet_check_qc_miss(writer, name, .not. is_null_mask) end if call parquet_make_valid_buf_write(.not. is_null_mask, valid_buf, valid_ptr) else call parquet_make_valid_buf_write(valid_buf=valid_buf, valid_ptr=valid_ptr) end if end subroutine temporal_valid_ptr !> Flat worker for the scalar and matrix date writes: `flat` holds asize*nrows dates in !> (element, row)-major order (a scalar column is asize == 1). subroutine write_date_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_date), intent(in), target :: flat(:) !! flattened dates. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! row count. logical :: do_write integer :: idx character(len=:), allocatable :: outname integer(c_int32_t), allocatable :: days(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_date), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_date), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. integer(int64) :: nrows_c type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_write_preamble(writer, name, "date", asize, do_write, outname, idx) if (.not. do_write) return call parquet_writer_whole_column_mask(writer, name, nrows, row_mask, masked) if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c nrows_c = count(row_mask, kind=int64) else fv => flat nrows_c = nrows end if allocate(days(size(fv, kind=int64))) days = fv%raw() call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) call parquet_check_row_count(writer, name, nrows_c) call parquet_append_date_column(writer%handle, trim(outname)//char(0), days, nrows_c, asize, valid_ptr) end subroutine write_date_flat !> Flat worker for the scalar and matrix time writes; see write_date_flat. subroutine write_time_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_time), intent(in), target :: flat(:) !! flattened times. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! row count. logical :: do_write integer :: idx, unit integer(c_int32_t) :: is_utc character(len=:), allocatable :: outname integer(c_int64_t), allocatable :: ns(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_time), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_time), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. integer(int64) :: nrows_c type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_write_preamble(writer, name, "time", asize, do_write, outname, idx) if (.not. do_write) return call resolve_temporal_write_unit(writer, idx, unit, is_utc) call parquet_writer_whole_column_mask(writer, name, nrows, row_mask, masked) if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c nrows_c = count(row_mask, kind=int64) else fv => flat nrows_c = nrows end if allocate(ns(size(fv, kind=int64))) ns = fv%raw() call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) call parquet_check_row_count(writer, name, nrows_c) call parquet_append_time_column(writer%handle, trim(outname)//char(0), ns, nrows_c, asize, & int(unit, c_int32_t), valid_ptr) end subroutine write_time_flat !> Flat worker for the scalar and matrix timestamp writes; see write_date_flat. Each !> non-null instant is converted to the file unit exactly (to_unix aborts on finer precision). subroutine write_timestamp_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_timestamp), intent(in), target :: flat(:) !! flattened instants. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! row count. logical :: do_write integer :: idx, unit integer(c_int32_t) :: is_utc character(len=:), allocatable :: outname integer(c_int64_t), allocatable :: vals(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr integer(int64) :: i, n logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_timestamp), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_timestamp), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. integer(int64) :: nrows_c type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_write_preamble(writer, name, "timestamp", asize, do_write, outname, idx) if (.not. do_write) return call resolve_temporal_write_unit(writer, idx, unit, is_utc) call parquet_writer_whole_column_mask(writer, name, nrows, row_mask, masked) if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c nrows_c = count(row_mask, kind=int64) else fv => flat nrows_c = nrows end if n = size(fv, kind=int64) allocate(vals(n)) do i = 1_int64, n if (fv(i)%is_null()) then vals(i) = 0_c_int64_t else vals(i) = fv(i)%to_unix(unit) end if end do call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) call parquet_check_row_count(writer, name, nrows_c) call parquet_append_timestamp_column(writer%handle, trim(outname)//char(0), vals, nrows_c, asize, & int(unit, c_int32_t), is_utc, valid_ptr) end subroutine write_timestamp_flat module procedure parquet_write_date_column call write_date_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_date_column module procedure parquet_write_date_matrix_column call write_date_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_date_matrix_column module procedure parquet_write_time_column call write_time_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_time_column module procedure parquet_write_time_matrix_column call write_time_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_time_matrix_column module procedure parquet_write_timestamp_column call write_timestamp_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_timestamp_column module procedure parquet_write_timestamp_matrix_column call write_timestamp_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_timestamp_matrix_column ! ---- Temporal streaming (row-group-chunked) writes ------------------------------- !> Chunk counterpart of temporal_write_preamble: schema checks + exact type match, the !> streaming row-group row-count check, and first-chunk mark-written. Returns do_write. subroutine temporal_chunk_preamble(writer, name, expected_type, asize, nrows, do_write, outname, idx, & row_mask, masked) type(parquet_writer), intent(inout) :: writer !! open writer with a row group open. character(len=*), intent(in) :: name !! column name. character(len=*), intent(in) :: expected_type !! "date"/"time"/"timestamp". integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! this chunk's row count. logical, intent(out) :: do_write !! whether to write this chunk. character(len=:), allocatable, intent(out) :: outname !! resolved output name. integer, intent(out) :: idx !! schema column index, or 0 schema-less. logical, allocatable, intent(out) :: row_mask(:) !! this chunk's row-keep mask; unallocated if .not. masked. logical, intent(out) :: masked !! .true. if a row mask applies to this chunk. character(len=:), allocatable :: ctx !! writer_context_suffix scratch. do_write = .false. idx = 0 masked = .false. if (writer%is_schema_enforced) then idx = parquet_get_defined_column_index(writer, name) call writer_context_suffix(writer, ctx) if (idx == 0) error stop & "parquet_write_column_chunk: column not defined in parquet_open_writer: " // trim(name) // ctx if (.not. writer%all_columns(idx)%is_set) return call parquet_resolve_or_check_col_size(writer, name, idx, asize, "parquet_write_column_chunk", ctx) if (trim(writer%all_columns(idx)%data_type) /= expected_type) then error stop "parquet_write_column_chunk: type mismatch for column " // trim(name) // & " (expected " // expected_type // ", got " // trim(writer%all_columns(idx)%data_type) // ")" // ctx end if end if if (.not. parquet_is_column_enabled(writer, name)) return call parquet_check_row_group_row_count(writer, name, nrows, row_mask, masked) call parquet_chunk_mark_written_if_first(writer, name) call parquet_resolve_output_name(writer, name, outname) do_write = .true. end subroutine temporal_chunk_preamble !> Flat worker for the scalar and matrix date chunk writes. subroutine write_date_chunk_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_date), intent(in), target :: flat(:) !! this chunk's flattened dates. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! this chunk's row count. logical :: do_write integer :: idx character(len=:), allocatable :: outname integer(c_int32_t), allocatable :: days(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_date), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_date), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_chunk_preamble(writer, name, "date", asize, nrows, do_write, outname, idx, row_mask, masked) if (.not. do_write) return if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c else fv => flat end if allocate(days(size(fv, kind=int64))) days = fv%raw() call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) if (size(fv, kind=int64) > 0_int64) & call parquet_append_date_column_chunk(writer%handle, trim(outname)//char(0), days, asize, valid_ptr) end subroutine write_date_chunk_flat !> Flat worker for the scalar and matrix time chunk writes. subroutine write_time_chunk_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_time), intent(in), target :: flat(:) !! this chunk's flattened times. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! this chunk's row count. logical :: do_write integer :: idx, unit integer(c_int32_t) :: is_utc character(len=:), allocatable :: outname integer(c_int64_t), allocatable :: ns(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_time), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_time), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_chunk_preamble(writer, name, "time", asize, nrows, do_write, outname, idx, row_mask, masked) if (.not. do_write) return call resolve_temporal_write_unit(writer, idx, unit, is_utc) if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c else fv => flat end if allocate(ns(size(fv, kind=int64))) ns = fv%raw() call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) if (size(fv, kind=int64) > 0_int64) & call parquet_append_time_column_chunk(writer%handle, trim(outname)//char(0), ns, asize, & int(unit, c_int32_t), valid_ptr) end subroutine write_time_chunk_flat !> Flat worker for the scalar and matrix timestamp chunk writes. subroutine write_timestamp_chunk_flat(writer, name, flat, asize, nrows) type(parquet_writer), intent(inout) :: writer !! open writer. character(len=*), intent(in) :: name !! column name. type(parquet_timestamp), intent(in), target :: flat(:) !! this chunk's flattened instants. integer(int64), intent(in) :: asize !! per-row element count. integer(int64), intent(in) :: nrows !! this chunk's row count. logical :: do_write integer :: idx, unit integer(c_int32_t) :: is_utc character(len=:), allocatable :: outname integer(c_int64_t), allocatable :: vals(:) integer(c_int8_t), allocatable, target :: valid_buf(:) type(c_ptr) :: valid_ptr integer(int64) :: i, m logical, allocatable :: row_mask(:), elem_mask(:) type(parquet_timestamp), allocatable, target :: flat_c(:) !! masked copy; unused on the unmasked fast path. type(parquet_timestamp), pointer :: fv(:) !! the elements actually written: `flat` itself, or flat_c. logical :: masked !! whether a row mask applies to this write. type(writer_lock) :: lk !! Releases writer's concurrency guard on every exit path (FINAL). call check_writer_open(writer) call lk%claim(writer) call temporal_chunk_preamble(writer, name, "timestamp", asize, nrows, do_write, outname, idx, row_mask, masked) if (.not. do_write) return call resolve_temporal_write_unit(writer, idx, unit, is_utc) if (masked) then elem_mask = parquet_mask_expand_block(row_mask, asize) flat_c = pack(flat, elem_mask) fv => flat_c else fv => flat end if m = size(fv, kind=int64) allocate(vals(m)) do i = 1_int64, m if (fv(i)%is_null()) then vals(i) = 0_c_int64_t else vals(i) = fv(i)%to_unix(unit) end if end do call temporal_valid_ptr(writer, name, fv%is_null(), valid_buf, valid_ptr) if (m > 0_int64) call parquet_append_timestamp_column_chunk(writer%handle, trim(outname)//char(0), vals, asize, & int(unit, c_int32_t), is_utc, valid_ptr) end subroutine write_timestamp_chunk_flat module procedure parquet_write_date_column_chunk call write_date_chunk_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_date_column_chunk module procedure parquet_write_date_matrix_column_chunk call write_date_chunk_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_date_matrix_column_chunk module procedure parquet_write_time_column_chunk call write_time_chunk_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_time_column_chunk module procedure parquet_write_time_matrix_column_chunk call write_time_chunk_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_time_matrix_column_chunk module procedure parquet_write_timestamp_column_chunk call write_timestamp_chunk_flat(writer, name, values, 1_int64, size(values, kind=int64)) end procedure parquet_write_timestamp_column_chunk module procedure parquet_write_timestamp_matrix_column_chunk call write_timestamp_chunk_flat(writer, name, reshape(values, [size(values, kind=int64)]), & size(values, 1, kind=int64), size(values, 2, kind=int64)) end procedure parquet_write_timestamp_matrix_column_chunk end submodule parquet_write_temporal