Repository navigation
Conversation
Include raw field metadata and dictionary IDs in the semantic equality check between the leading file schema and Footer.schema. Co-Authored-By: Codex <codex@openai.com>
Validate C format descriptors before recursive or geometry work. Export and require the canonical terminal offset for empty offset layouts. Co-Authored-By: Codex <codex@openai.com>
Keep only a slice across the GC reachability probe, then collect the mapping before deleting its path on platforms that lock active mappings. Co-Authored-By: Codex <codex@openai.com>
Track each result export key until its caller-owned C struct is published. Discard unpublished roots and keep failed batches available for retry. Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Use verified block extents before classifying the final marker-shaped bytes, so valid no-EOS files cannot lose part of their last data buffer. Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Verify each Footer Block against its on-wire Message envelope before classifying optional EOS bytes. Keep record decoding and resource limits lazy, and cover forged collision extents plus zero-body Blocks. Co-Authored-By: Codex <codex@openai.com>
Bind each Footer Block to its Message header, nested RecordBatch, and declared wire-buffer extents before optional EOS detection. This rejects a coordinated Footer and Message length forgery while keeping lazy decoding and configured resource limits unchanged. Co-Authored-By: Codex <codex@openai.com>
Record the optional-EOS Block-verification finding, its completed disposition, the checked-clean areas, and final validation. Align the README review index and file-verification claim. Co-Authored-By: Codex <codex@openai.com>
Pass readfile's existing whole-file slice into the Block preflight. This removes the stable 48-byte allocation from constructing an identical second slice after warm-up. Co-Authored-By: Codex <codex@openai.com>
Record the Block-preflight allocation finding, its narrow fix, the checked-clean binding and interoperability results, and final validation. Co-Authored-By: Codex <codex@openai.com>
Closing verification of the round-16 slice-reuse fix: zero findings, VERDICT: CLEAN. Rounds 14-17 converge the adapter build-out (20 -> 1 -> 1 LOW -> 0). Co-Authored-By: Codex <codex@openai.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iers Proposal extending report §9/§14: Stage-A/B Scan apply semantics (IO reduction with full residual now; exact facade consumption later), limit/offset as decode-free exact pushdowns, the RangedSource fetcher contract + coalescing range planner over the footer/Block/buffer-table metadata, footer-carried per-batch statistics for tail-fetch-only pruning, and the two-tier trimmable public API where Scan type overrides double as the known-schema pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
skipfield!/skipbuffer! advance the registry cursor with every buffer-table invariant intact but zero body access; Tables.apply on ArrowFile decodes only (selected ∪ filter) columns, consumes limit/offset exactly via metadata row counts (whole batches skipped), and returns source-named columns with a RESOLVED residual selection — Not/Regex re-binding against the reduced table is wrong, which the differential battery caught. Acceptance: 15 scans differentially equal to Tables.finish over the full table, corruption probes proving skipped columns and window-excluded batches are never decoded, residual consumption semantics, and the duplicate-name facade boundary. Requires Tables.jl#jq/scan dev'ed into the repo project (Manifest is untracked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RangedSource{F} is the design's fetcher contract (parametric functor,
fetchranges overridable for concurrent transports); RangedFile.apply runs
the fetch protocol: tail-first footer, one coalesced metadata pass,
dictionary bodies only for decode-set ids, per-buffer body ranges for
exactly the surviving batches' decode set, and sparse decode through a
SparseBody standing in for the contiguous message body (DecodeCursor{B}
parametrizes the seam; _bodyslice is the one indirection). Counting-source
acceptance proves the model: differential equality with whole-file reads,
14% of bytes for a narrow column over a 2.3MB file, corruption-backed
never-fetched proofs for skipped columns, window-excluded batches, and
unneeded dictionary bodies, request/byte coalescing trade-offs, undersized
tails, compressed files, and forged-footer refusals. Footer-only schema
authority (no leading-message cross-check, blocks bounded by footer start)
is documented loudly as the ranged reader's trust divergence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…runing withstatistics folds per-column (null count, min, max) plus row_count per batch into the statistics schema's official array layout — struct<column, map<dictionary<utf8,int32>, dense_union<i64,f64,utf8,bool>>> — serialized through this writer itself and carried base64-wrapped under JuliaArrow:batch_statistics.v1 in schema metadata (placement is our convention; the spec declares placement a non-goal). Both applies prune with one-sided may-contain interval logic (Cmp/In/IsNull/startswith with bytewise prefix successors, And/Or, Not-of-equality); the ranged path prunes before its block-metadata pass so pruned batches cost zero fetches; the filter always stays in the residual. Acceptance pins differential exactness under pruning on both paths, never-fetched and never-decoded proofs, malformed-blob degradation to no pruning, and the trust model in both directions: wide lies only cost pruning, narrow lies silently lose rows (trusted-for-completeness, as Parquet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve positional filter references against source names before the residual is rebound. Validate batch row metadata before it drives a window, and preserve row counts for zero-column results. Add differential and corruption regressions for both whole-file and ranged applies. Co-Authored-By: Codex <codex@openai.com>
Enforce Footer Block overlap, features, limits, and complete RecordBatch metadata before any body range is fetched. Make coalescing overflow-safe and validate overridden multi-range fetch results. Use one allocation budget and codec state across each whole-file Scan apply. Correct the design request model and add fail-closed, fetch, and aggregate-budget regressions. Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Reject legacy compression, unsupported codecs, and missing dictionary ids while only metadata is loaded. Apply per-record limits after statistics pruning to match ArrowFile lazy acceptance. Co-Authored-By: Codex <codex@openai.com>
Describe surviving-record validation and distinguish planned ranges from tail and coalescing over-read. Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
A NamedTuple column whose rows have different field types, such as rows
with some fields missing, has an abstract element type like
NamedTuple{(:a, :b)}. Arrow 2.x wrote it as one Struct with
Union{Missing, T} children; Arrow 3.0 refused it. Narrow such a
column to the field-wise Union of its observed row types before
construction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow 2.x accepted a vector of sources; on 3.0 `Arrow.Table([a, b])` or a byte view failed with a MethodError naming the internal _opensource. Throw an ArgumentError that lists the supported source kinds and says to read several sources separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow 2.x wrote every column with a DataAPI ref pool (PooledArray,
CategoricalArray) dictionary-encoded. Arrow 3.0 wrote PooledArrays as
plain values and failed on CategoricalArrays with "fromjulia: unsupported
element type Ptr{Nothing}", because the default struct reflection walked
into CategoricalPool internals; CategoricalArrays' Arrow extension cannot
load on 3.0. Write a fresh ref-pool column dictionary-encoded with its
pool order and unused entries, unwrapping values through DataAPI.unwrap
when their type has no ArrowTypes.jl mapping, so a mapping that a future
CategoricalArrays extension defines still applies. When struct reflection
does fail, name the column and element type and say how to convert it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow 3.0 defines DataAPI.metadatasupport and colmetadatasupport, which DataAPI added in 1.12. The compat bound allowed DataAPI 1.0-1.11, with which Arrow fails to precompile (UndefVarError: metadatasupport). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow.jl releases before 2.6 wrote the file footer with metadata version
V4 over V5 messages, so Arrow 3.0 refused every file-format output of
Arrow.jl 1.0 through 2.5 ("file schema and footer metadata versions
differ"); MixedModelsDatasets' cbpp dataset is one. pyarrow reads them.
Accept a V4 or V5 footer over V4 or V5 messages; the message version
still selects the layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover the list, map, and fixed-size-list row types and their conversions, DateTime conversion of timestamp columns, the rejection of bytes after a stream's end-of-stream marker (Arrow 2.x read only the first stream), and replacements for the Arrow.names and Arrow.metadata accessors that downstream packages call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every table after the first that Arrow.Writer writes must reuse the first
table's exact dictionary pool. With automatic ref-pool encoding, two
tables whose PooledArrays had different pools failed the second write
("dictionary id 0 changed pools"), where they previously wrote as plain
values. Arrow.Writer and later-table construction now write ref-pool
columns as their values, unwrapped when needed; explicit DictEncode
columns are still encoded. The eager writer keeps encoding them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every DataAPI column-metadata call rechecked that the retained schema aligned with the output columns by interning each field name with Symbol(name). DataFrame(table) lists all columns' metadata, so it was quadratic in the column count: 0.6-0.9 s for a 2000-column table (Arrow 2.x: under 1 ms), 1.9 s to list a 3000-column table's metadata. Check alignment once per listing, compare names without interning them, and use the name lookup to detect duplicate names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Julia 1.10 boxes in the per-element null punch (73 MB per 1M nullable Float64 values after the copy removal, 90 MB before), so the 14 MB bound holds only on newer Julia; the Int64 pin holds on 1.10 too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union array has no validity bitmap, so its physical null count is always 0 even when a selected child slot is null. _statfold recorded that 0, and _maypass then pruned every batch under isnull(col(:u)), silently dropping the matching rows from files written with withstatistics/statsfile. Derive the logical count the same way the dictionary and REE layouts already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scan that selects one column twice (`select=(:u, :u => :v)`) gives
both output fields the same retained child Field objects. Column
construction for ArrowTypes extension columns with children (UUID,
Vector{<:extension}, structs or dictionaries holding one) reused those
child objects, so Arrow.write of the scan result failed with "IPC writer
schema reuses one Field object in multiple positions". Copy each
retained field tree before constructing the column; the writer never
keys anything on the retained objects themselves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow keeps the vectors readrange returns without copying them: the tail window is cached on the handle and fetched spans are decoded after later requests. A source that reuses one buffer for every request silently corrupts results (or leaves Arrow holding a stale pointer after a resize!). Say so in the AbstractArrowSource and readrange contracts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full read retains each dictionary column's source category pool, but
every scan result (even an identity Scan()) dropped them. Arrow.write
then refused any scanned ordered dictionary ("its original category
pool is unavailable") and rebuilt unordered pools from the surviving
values, losing category order and unused categories.
File and ranged scans now remember the first decoded dictionary of each
selected dictionary column (file batches share each dictionary's final
pool), stream scans merge their batch snapshots as a full read does, and
the public fallback takes the full table's pools. A real type override
drops the pool exactly when it drops the retained field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The battery's remote-scan budget sat 536 bytes above what the opened read needed. Reading without the redundant public-column copy lowered both needs by the same amount (opened 739,272, remote 797,366 bytes on Julia 1.10 and 1.12), so pick a limit between them again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The eager writer copied each buffer into a fresh vector before appending it to the message body, grew the body, the batch message, and the output buffer by repeated appends, and so allocated about 6x the data it wrote. Copy uncompressed buffers straight from their slices, size the body and the message once, and size the eager output buffer from the record bodies. Writing 2x2M Int64/Float64 to an IOBuffer allocates 132 MiB instead of 188 MiB, and to a path 92 MiB instead of 147 MiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A forged Footer Block offset or body length close to typemax(Int64) made the block-extent check's checked addition throw a raw OverflowError on every read path (found by the structure-aware fuzzer: 398 of 1,500 footer mutants). Compare the nonnegative extents against the data section end without overflowing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A declared timezone becomes the Z parameter of ZonedTimestamp{P,Z}. A zone
string containing NUL escaped as Base's raw ArgumentError (found by the
fuzzer on the ranged path, which reads only the footer schema), and a zone
string of any length was interned as process-global Symbol state. Refuse
both as ValidationError, with the facade's 4096-byte per-name bound.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pin measured the first call, so it counted compilation: Julia 1.14.0-alpha1 (the CI "pre" channel) allocates 1.6 MB compiling it there and 42 MB cold, versus 14 KB warm. The pin is about per-row work, so measure a warmed call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Julia 1.14 hashes a Date like the midnight DateTime it isequal to, so Date(2024, 3, 1) in Set([DateTime(2024, 3, 1)]) is now true (false through 1.12). The battery hard-coded the old answer; Arrow's scan already agreed with Tables.scan on both. Take the expected row from Julia's in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation tests The "materialized once" pins measured `@allocated`, which counts the usable size of each big malloc block. macOS malloc can satisfy a 9 MB request with a larger cached block (12-17 MB observed), so the nullable-read pin failed intermittently on macOS CI with no extra allocation: same allocation counts, and the reported excess equals the block's `malloc_size` minus the request. Measure the bytes requested, via Profile.Allocs at sample rate 1, which is deterministic on every platform and still fails if a second copy of the column appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dictionary encoder scanned categories with `for v in vals, x in v`
over a Vector{AbstractVector} and converted indices through a run-time
index type, so each row paid four heap allocations (an iterate tuple, two
Int64 boxes, and a storage key). Writing a 1M-row PooledArray or
DictEncode string column took 300-450 ms; Arrow 2.x took about 85 ms.
Put both loops behind function barriers. The same column now writes in
about 40 ms with ~81k allocations (from 4.08M). Ref-pool columns, which
write dictionary-encoded as in Arrow 2.x, take this path, so a typical
DataFrame with a PooledArray column no longer writes slower than 2.x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Null counting (run by semantic validation on every nullable read and write) and the nullable bulk read's null punch called isvalid_at per element, which repeats the buffer-role lookup and a bounds-checked load for every bit. Check the bitmap bytes' bounds once and walk them directly; out-of-range bitmaps still throw BoundsError as before. A new core test compares both against per-element validity for every slice offset and length over 40 slots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under a typed claim, fixed-width values (integers, floats, temporal,
interval, bool, null) charge nothing beyond the output column, yet the
budgeted materialize walked every value to learn that. Skip the walk for
those layouts. Also drop the reservation for a Vector{E} scratch copy:
the nullable bulk path loads values straight into its result and has no
scratch, so default limits refused nullable columns smaller than they
can read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder made a Vector{Bool} of presence flags, scanned it twice
(any, count), then built the bitmap and the values in two more passes.
Fill values and bitmap in one loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing a Vector{Union{Missing,Int64}} (or any Union of concrete
non-container types) walked every value through an IdDict lookup to ask
whether ArrowTypes lowering was needed, although the type-level check
already answers for every value such a column can hold. Skip the walk
for those element types, as concrete element types already did.
2x10M nullable Int64/Float64 write (Julia 1.12, best of 7 after GC):
215 ms at the PR head -> 67 ms with this and the previous two commits
(Arrow 2.8.1: 40 ms). Nullable read of the same file: 253 -> 35 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CloudBase 2.0 moved its CloudTest emulators to the test-only CloudBaseTest package, so `using CloudBase.CloudTest` fails as soon as the test environment resolves CloudBase 2 (every CI job since its release). CloudBaseTest 1 supports CloudBase 1.6 through 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hildren The fixed-list and Struct allocation children compare one `@allocated` reading of a small and a large write. `@allocated` counts each big block's usable size, and macOS malloc can satisfy a request with a larger cached block, so a single reading of a 20-30 MB workload can come in several MB high: generalhidden(50_000) requests 28.0 MB and read 32.3 and 36.1 MB in two runs, against 3.5 MB of slack in its bound. The fixed-list child failed 2 of 19 checks on two Julia pre macOS jobs this way, with allocation numbers identical to the last passing commit. Measure each side as the least of three readings after a full collection. The bounds are unchanged; a real extra allocation still shows in every reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every read was charged against one flat 256 MiB budget, so default Arrow.Table refused ordinary files: past 16.7M Int64 rows (a 128 MiB file), 1M 64-byte strings, 14.9M nullable Float64. Arrow 2.x read a 610 MB file with no options. A read's budget is now `max_allocation_ratio` (new, default 64) bytes per input byte, at least 256 MiB and at most `max_total_allocated_bytes` (default now 16 GiB). Ordinary columns cost a small multiple of their stored bytes, so they read with the defaults. Inputs under 4 MiB keep exactly the old 256 MiB allowance, so lengths a small malformed file declares without bytes behind them (run-end encoded, view, null-child, decompressed) still fail before allocation: a 464-byte file whose REE column claims 1e9 rows fails in 0.1 s after allocating 10 MiB. Raising only the ceiling would have let a 464-byte file claiming 5e7 rows allocate 382 MiB. The bound applies where the input size is known: byte, IO, and unmapped path reads, mapped files, ranged object reads (the object length), and handle-level batch and scan reads. Two tests that assumed the budget equals `max_total_allocated_bytes` now read the actual allowance, the empty-container guard keeps its explicit 256 MiB, and the error names both knobs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Arrow.Table(handle)` and `Arrow.Stream(handle)` gave an `ArrowFile` handle a fresh budget at `max_total_allocated_bytes`, and handles never pass through the input-sized bound added in ed0ed67: the 464-byte run-end encoded file claiming 1e9 rows, opened with `Arrow.readfile`, allocated 7.6 GB over 69 s instead of failing. Size that budget from the handle's region. The regression test now reads the file through bytes, IO, and stream and file handles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f263a5a moved every ARROW:extension pair of a dictionary Field to the C Data value node, as the C++ bridge does for an extension type it knows. For a name PyArrow does not know, such as arrow-testing's "dict-extension", PyArrow then dropped the pairs: the conformance job's C Data oracle lost `dict_exts`' metadata in all four round trips (143 pass, 4 fail; 147 and 0 before f263a5a). Move the pairs to the value node only for canonical extensions, whose `arrow.` prefix the format reserves; other names stay with the field metadata on the wrapper, where a consumer that does not know them keeps them. PyArrow dictionary columns of arrow.uuid and arrow.json, the case f263a5a fixed, still export back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Arrow.appendfrom the final dictionary state even when an update follows the last record batch; fixesArrow.Tableuses last dictionary for all batches in streaming decode, resulting in incorrect reads #610Tables.Scanonce, keep storage and public-domain plans separate, preserve stable empty-result schemas, and support sparse byte-range reads with footer statisticsDurations.Timestamp{P}at the column unit (a writtenDateTimecolumn reads back as an instant-equalTimestamp{Millisecond}) and every timezone-declared timestamp asDurations.ZonedTimestamp{P,Z}(no TimeZones.jl requirement), both writing back zero-conversion with every comparison operator pushing down; preserve wire descriptors on rewriteCurrent validation
Latest head:
0ffdf1436664228629cbe619249011a415d216f7.Appending to a valid IPC stream with a trailing dictionary replacement or delta previously either wrote incorrect values or rejected a matching dictionary. The reader now retains the final dictionary map; append resumes that map while earlier record batches keep their original snapshots. Public file and IO regressions cover replacements, deltas, and uncompressed/LZ4/Zstd streams.
All 55 hosted checks pass at
0ffdf1436664228629cbe619249011a415d216f7: both CI runs and the conformance run completed successfully. All 34 native coverage uploads were accepted for apache/arrow-julia at that exact commit, and all 34 reports finished processing, with aggregate coverage at 91.93%. Prior green job logs show coverage uploads were rejected withToken required because branch is protected. Uploading jobs now use scoped OIDC for repository-owned runs and fail on upload errors; public forks retain tokenless upload support. Coverage percentage thresholds and processing paths are unchanged. The notification count now matches one 17-report CI run, so main commits and branches without a duplicate PR run can report coverage; all 34 uploads from this PR's push and PR runs were independently verified. The scheduled nightly upload path receives the same authentication repair; scheduled execution is not part of the local validation above.Dependency
JuliaData/Tables.jl#380 is merged and released as Tables.jl 1.14.0. Commit dd8c272 removes the temporary source and CI pins, raises the Tables.jl compatibility floor to 1.14, and resolves Tables from the General registry everywhere (package, CI workflows, conformance image, docs).
Breaking changes
This is the Arrow.jl 3.0 rewrite. Important changes include:
Arrow.TableandArrow.Streamreturn materialized Julia vectors instead of lazy Arrow vector wrappers.Arrow.writeis eager and whole-buffer.Arrow.Writerand IPC-stream append are reimplemented with fixed-schema semantics; the curried write andtobuffercompatibility forms remain. Obsolete writer tuning keywords are ignored with warnings.Arrow.write(io, table)writes file format by default; usefile=falsefor stream format.ArrowTypesstays exported only as an Arrow 2.x compatibility exception. Packages that define mappings should depend on andimport ArrowTypesdirectly.Arrow.ArrowTypesremains as a qualified compatibility binding.Arrow.Fresh declared heterogeneous Julia Union columns are written as canonical dense Arrow Unions. A retained Union rewrite fails closed when materialization has lost its original route. Nested retained dictionary pools follow the same fail-closed rule.
See
CHANGELOG.mdanddocs/src/migration.mdfor the complete list and migration guidance.Release preparation
Timestamp/ZonedTimestamp) resolve from General; the bundled ArrowStrings package and its release steps are removed3.0.0-DEVpending the rewrite merge and Apache release processCo-authored by Codex
AI disclosure: This work was prepared with assistance from OpenAI Codex.
🤖 Generated with Claude Code