diff --git a/docs/src/index.md b/docs/src/index.md index 1b75ab7..9722402 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -358,3 +358,11 @@ And that's it. Our `MatrixTable` type is now a fully fledged, valid Tables.jl so the ecosystem. Now, this is obviously not a lot of code; but then again, the actual Tables.jl interface implementations tend to be fairly simple, given the other behaviors that are already defined for table types (i.e. table types tend to already have a `getcolumn` like function defined). + +### `Tables.isrowtable` + +One option for certain table types is to define `Tables.isrowtable` to automatically satisfy the Tables.jl interface. +This can be convenient for "natural" table types that have row elements, along with other required properties. +```@docs +Tables.isrowtable +``` \ No newline at end of file diff --git a/src/Tables.jl b/src/Tables.jl index c18568d..fd408f2 100644 --- a/src/Tables.jl +++ b/src/Tables.jl @@ -162,7 +162,27 @@ columnaccess(::Type{<:AbstractColumns}) = true columns(x::AbstractColumns) = x schema(x::AbstractColumns) = nothing -# default definitions +""" + Tables.isrowtable(x) => Bool + +For convenience, some table objects that are naturally "row oriented" can +define `Tables.isrowtable(::Type{TableType}) = true` to simplify satisfying the +Tables.jl interface. Requirements for defining `isrowtable` include: + * `Tables.rows(x) === x`, i.e. the table object itself is a `Row` iterator + * If the table object is mutable, it should support: + * `push!(x, row)`: allow pushing a single row onto table + * `append!(x, rows)`: allow appending set of rows onto table + * If table object is mutable and indexable, it should support: + * `x[i] = row`: allow replacing of a row with another row by index + +A table object that defines `Tables.isrowtable` will have definitions for +`Tables.istable`, `Tables.rowaccess`, and `Tables.rows` automatically defined. +""" +function isrowtable end + +isrowtable(::T) where {T} = isrowtable(T) +isrowtable(::Type{T}) where {T} = false + """ Tables.istable(x) => Bool @@ -175,7 +195,7 @@ of knowing that the generator is a table. function istable end istable(x::T) where {T} = istable(T) || TableTraits.isiterabletable(x) === true -istable(::Type{T}) where {T} = false +istable(::Type{T}) where {T} = isrowtable(T) """ Tables.rowaccess(x) => Bool @@ -194,7 +214,7 @@ natural for them to *consume* instead of worrying about what and how the input p function rowaccess end rowaccess(x::T) where {T} = rowaccess(T) -rowaccess(::Type{T}) where {T} = false +rowaccess(::Type{T}) where {T} = isrowtable(T) """ Tables.columnaccess(x) => Bool diff --git a/src/fallbacks.jl b/src/fallbacks.jl index 946bfe5..45aad08 100644 --- a/src/fallbacks.jl +++ b/src/fallbacks.jl @@ -61,9 +61,7 @@ end Base.eltype(x::RowIterator{T}) where {T} = ColumnsRow{T} Base.length(x::RowIterator) = x.len -istable(::Type{<:RowIterator}) = true -rowaccess(::Type{<:RowIterator}) = true -rows(x::RowIterator) = x +isrowtable(::Type{<:RowIterator}) = true columnaccess(::Type{<:RowIterator}) = true columns(x::RowIterator) = x.columns @@ -77,6 +75,7 @@ end # this is our generic Tables.rows fallback definition function rows(x::T) where {T} + isrowtable(x) && return x # because this method is being called, we know `x` didn't define it's own Tables.rows # first check if it supports column access, and if so, wrap it in a RowIterator if columnaccess(T) diff --git a/src/matrix.jl b/src/matrix.jl index e23caa2..c24bea9 100644 --- a/src/matrix.jl +++ b/src/matrix.jl @@ -9,7 +9,7 @@ struct MatrixTable{T <: AbstractMatrix} <: AbstractColumns matrix::T end -istable(::Type{<:MatrixTable}) = true +isrowtable(::Type{<:MatrixTable}) = true names(m::MatrixTable) = getfield(m, :names) # row interface @@ -26,9 +26,7 @@ getcolumn(m::MatrixRow, nm::Symbol) = getfield(getfield(m, :source), :matrix)[getfield(m, :row), getfield(getfield(m, :source), :lookup)[nm]] columnnames(m::MatrixRow) = names(getfield(m, :source)) -rowaccess(::Type{<:MatrixTable}) = true schema(m::MatrixTable{T}) where {T} = Schema(Tuple(names(m)), NTuple{size(getfield(m, :matrix), 2), eltype(T)}) -rows(m::MatrixTable) = m Base.eltype(m::MatrixTable{T}) where {T} = MatrixRow{T} Base.length(m::MatrixTable) = size(getfield(m, :matrix), 1) diff --git a/src/namedtuples.jl b/src/namedtuples.jl index be7a2d2..3b51437 100644 --- a/src/namedtuples.jl +++ b/src/namedtuples.jl @@ -2,10 +2,7 @@ const RowTable{T} = AbstractVector{T} where {T <: NamedTuple} # interface implementation -istable(::Type{<:RowTable}) = true -rowaccess(::Type{<:RowTable}) = true -# an AbstractVector of NamedTuple iterates `Row`s itself -rows(x::RowTable) = x +isrowtable(::Type{<:RowTable}) = true schema(x::AbstractVector{NamedTuple{names, types}}) where {names, types} = Schema(names, types) materializer(x::RowTable) = rowtable diff --git a/test/runtests.jl b/test/runtests.jl index 2a49a7f..c28b42e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -741,3 +741,24 @@ Tables.columnnames(r::Columns) = fieldnames(Columns) @test Tables.schema(col) === nothing @test isequal(Tables.columntable(col), ct) end + +struct IsRowTable + rows::Vector{NamedTuple} +end + +Base.iterate(x::IsRowTable) = iterate(x.rows) +Base.iterate(x::IsRowTable, st) = iterate(x.rows, st) +Base.length(x::IsRowTable) = length(x.rows) + +Tables.isrowtable(::Type{IsRowTable}) = true + +@testset "Tables.isrowtable" begin + + nt = (a=1, b=3.14, c="hey") + rt = IsRowTable([nt, nt, nt]) + @test Tables.istable(rt) + @test Tables.rowaccess(rt) + @test Tables.rows(rt) === rt + @test Tables.columntable(rt) == Tables.columntable([nt, nt, nt]) + +end \ No newline at end of file