Skip to content

Commit 00e42b1

Browse files
authored
RFC: Add append!(::StructVector, iterator::Any) using Tables.isrowtable (#117)
* Implement append!(::StructVector, ::Any) using Tables.isrowtable * Directly append columns; add some tests
1 parent 86252c8 commit 00e42b1

File tree

3 files changed

+47
-0
lines changed

3 files changed

+47
-0
lines changed

src/tables.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,30 @@ Tables.columnaccess(::Type{<:StructVector}) = true
66
Tables.columns(s::StructVector) = fieldarrays(s)
77

88
Tables.schema(s::StructVector) = Tables.Schema(staticschema(eltype(s)))
9+
10+
function Base.append!(s::StructVector, rows)
11+
if Tables.isrowtable(rows) && Tables.columnaccess(rows)
12+
# Input `rows` is a container of rows _and_ satisfies column
13+
# table interface. Thus, we can add the input column-by-column.
14+
table = Tables.columns(rows)
15+
isempty(_setdiff(propertynames(s), Tables.columnnames(rows))) ||
16+
_invalid_columns_error(s, rows)
17+
_foreach(propertynames(s)) do name
18+
append!(getproperty(s, name), Tables.getcolumn(table, name))
19+
end
20+
return s
21+
else
22+
# Otherwise, fallback to a generic implementation expecting
23+
# that `rows` is an iterator:
24+
return foldl(push!, rows; init = s)
25+
end
26+
end
27+
28+
@noinline function _invalid_columns_error(s, rows)
29+
missingnames = setdiff!(collect(Tables.columnnames(rows)), propertynames(s))
30+
throw(ArgumentError(string(
31+
"Cannot append rows from `$(typeof(rows))` to `$(typeof(s))` due to ",
32+
"missing column(s):\n",
33+
join(missingnames, ", "),
34+
)))
35+
end

src/utils.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,16 @@ hasfields(::Type{<:NTuple{N, Any}}) where {N} = true
134134
hasfields(::Type{<:NamedTuple{names}}) where {names} = true
135135
hasfields(::Type{T}) where {T} = !isabstracttype(T)
136136
hasfields(::Union) = false
137+
138+
_setdiff(a, b) = setdiff(a, b)
139+
140+
@inline _setdiff(::Tuple{}, ::Tuple{}) = ()
141+
@inline _setdiff(::Tuple{}, ::Tuple) = ()
142+
@inline _setdiff(a::Tuple, ::Tuple{}) = a
143+
@inline _setdiff(a::Tuple, b::Tuple) = _setdiff(_exclude(a, b[1]), Base.tail(b))
144+
@inline _exclude(a, b) = foldl((ys, x) -> x == b ? ys : (ys..., x), a; init = ())
145+
146+
# _foreach(f, xs) = foreach(f, xs)
147+
_foreach(f, xs::Tuple) = foldl((_, x) -> (f(x); nothing), xs; init = nothing)
148+
# Note `foreach` is not optimized for tuples yet.
149+
# See: https://github.com/JuliaLang/julia/pull/31901

test/runtests.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,13 @@ end
339339
@test Tables.rowaccess(typeof(s))
340340
@test Tables.columnaccess(s)
341341
@test Tables.columnaccess(typeof(s))
342+
@test append!(StructArray([1im]), [(re = 111, im = 222)]) ==
343+
StructArray([1im, 111 + 222im])
344+
@test append!(StructArray([1im]), (x for x in [(re = 111, im = 222)])) ==
345+
StructArray([1im, 111 + 222im])
346+
# Testing integer column "names":
347+
@test invoke(append!, Tuple{StructVector,Any}, StructArray(([0],)), StructArray(([1],))) ==
348+
StructArray(([0, 1],))
342349
end
343350

344351
struct S

0 commit comments

Comments
 (0)