From 7a021ca57e9283cfad0578e67fa35f51db23e70f Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Tue, 22 Sep 2026 06:53:02 -0600 Subject: [PATCH] GH-610: Preserve dictionary replacements when reading tables --- src/table.jl | 21 ++++++--------------- test/runtests.jl | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/table.jl b/src/table.jl index de8bfc37..606f2abe 100644 --- a/src/table.jl +++ b/src/table.jl @@ -502,6 +502,9 @@ function Table(blobs::Vector{ArrowBlob}; convert::Bool=true) ) end elseif header isa Meta.DictionaryBatch + # Earlier record batches must capture their dictionaries before an update. + _waitall(tasks) + empty!(tasks) id = header.id recordbatch = header.data @debug "parsing dictionary batch message: id = $id, compression = $(recordbatch.compression)" @@ -522,20 +525,7 @@ function Table(blobs::Vector{ArrowBlob}; convert::Bool=true) convert, ) dictencoding = dictencodings[id] - if typeof(dictencoding.data) <: ChainedVector - append!(dictencoding.data, values) - else - A = ChainedVector([dictencoding.data, values]) - S = - field.dictionary.indexType === nothing ? Int32 : - juliaeltype(field, field.dictionary.indexType, false) - dictencodings[id] = DictEncoding{eltype(A),S,typeof(A)}( - id, - A, - field.dictionary.isOrdered, - values.metadata, - ) - end + append!(dictencoding.data, values) continue end # new dictencoding or replace @@ -551,7 +541,8 @@ function Table(blobs::Vector{ArrowBlob}; convert::Bool=true) Int64(1), convert, ) - A = values + # Keep the dictionary storage type stable when later deltas arrive. + A = ChainedVector([values]) S = field.dictionary.indexType === nothing ? Int32 : juliaeltype(field, field.dictionary.indexType, false) diff --git a/test/runtests.jl b/test/runtests.jl index 315d1b60..4f7d28b2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -149,6 +149,30 @@ end @test isequal(collect(str)[2].col1, [missing, 11]) end + @testset "# 610: dictionary replacement between record batches" begin + for compress in (nothing, :lz4, :zstd) + first = (a=Arrow.DictEncode(["alpha", "beta"]),) + second = (a=Arrow.DictEncode(["gamma", "delta"]),) + third = (a=Arrow.DictEncode(["gamma", "epsilon"]),) + b1 = take!(Arrow.tobuffer(Tables.partitioner((first, first)); compress)) + b2 = take!(Arrow.tobuffer(Tables.partitioner((second, third)); compress)) + + # Replace the first stream's EOS with the second stream after its schema. + _, (pos, _) = iterate(Arrow.BatchIterator(Arrow.ArrowBlob(b2, 1, nothing))) + bytes = vcat(b1[1:(end - 8)], b2[pos:end]) + batches = Arrow.BatchIterator(Arrow.ArrowBlob(bytes, 1, nothing)) + @test count(batches) do batch + header = batch.msg.header + header isa Arrow.Meta.DictionaryBatch && header.isDelta + end == 1 + + expected = [first.a.data, first.a.data, second.a.data, third.a.data] + @test Arrow.Table(bytes).a == vcat(expected...) + @test Arrow.Table([b1, b2]).a == vcat(expected...) + @test [collect(batch.a) for batch in Arrow.Stream(bytes)] == expected + end + end + @testset "# dictionary batch isDelta" begin t = ( col1=Int64[1, 2, 3, 4],