diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 13a4973f..b81a3f17 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: version: - - "1.6" # LTS + - "lts" # LTS - "1" # Latest Release os: - ubuntu-latest @@ -23,63 +23,32 @@ jobs: - windows-latest arch: - x64 - - x86 - exclude: - # Test 32-bit only on Linux - - os: macOS-latest - arch: x86 - - os: windows-latest - arch: x86 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v2 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}- - ${{ runner.os }}-${{ matrix.arch }}-test- - ${{ runner.os }}-${{ matrix.arch }}- - ${{ runner.os }}- - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 env: GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 + continue-on-error: true with: - file: lcov.info - - slack: - name: Notify Slack Failure - needs: test - runs-on: ubuntu-latest - if: always() && github.event_name == 'schedule' - steps: - - uses: technote-space/workflow-conclusion-action@v2 - - uses: voxmedia/github-action-slack-notify-build@v1 - if: env.WORKFLOW_CONCLUSION == 'failure' - with: - channel: nightly-dev - status: FAILED - color: danger - env: - SLACK_BOT_TOKEN: ${{ secrets.DEV_SLACK_BOT_TOKEN }} + files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} docs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 with: - version: '1.6' + version: "1.10" - run: | julia --project=docs -e ' using Pkg diff --git a/Project.toml b/Project.toml index 1b220628..57fc8970 100644 --- a/Project.toml +++ b/Project.toml @@ -2,9 +2,10 @@ name = "Intervals" uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5" license = "MIT" authors = ["Invenia Technical Computing"] -version = "1.10.0" +version = "1.11.0" [deps] +ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" @@ -12,6 +13,7 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" [compat] +ArrowTypes = "2.3" RecipesBase = "0.7, 0.8, 1" TimeZones = "1.7" -julia = "1.6" +julia = "1.10" diff --git a/src/Intervals.jl b/src/Intervals.jl index 7d3a939a..4f91f164 100644 --- a/src/Intervals.jl +++ b/src/Intervals.jl @@ -1,5 +1,6 @@ module Intervals +using ArrowTypes: ArrowTypes, JuliaType, arrowname using Dates using Printf using RecipesBase @@ -35,6 +36,7 @@ include("plotting.jl") include("docstrings.jl") include("deprecated.jl") include("compat.jl") +include("arrow.jl") export Bound, Closed, diff --git a/src/arrow.jl b/src/arrow.jl new file mode 100644 index 00000000..ad36681c --- /dev/null +++ b/src/arrow.jl @@ -0,0 +1,57 @@ +for T in (Closed, Open, Unbounded) + name = QuoteNode(Symbol("JuliaLang.Intervals.$(string(T))")) + + @eval begin + ArrowTypes.arrowname(::Type{$T}) = $name + ArrowTypes.JuliaType(::Val{$name}) = $T + end +end + +# Use a more efficient Arrow serialization when a vector uses a concrete element type +let name = Symbol("JuliaLang.Intervals.Interval{T,L,R}") + ArrowTypes.arrowname(::Type{Interval{T,L,R}}) where {T, L <: Bound, R <: Bound} = name + function ArrowTypes.ArrowType(::Type{Interval{T,L,R}}) where {T, L <: Bound, R <: Bound} + return Interval{T,L,R} + end + function ArrowTypes.arrowmetadata(::Type{Interval{T,L,R}}) where {T, L <: Bound, R <: Bound} + return join(arrowname.([L, R]), ",") + end + function ArrowTypes.JuliaType(::Val{name}, ::Type{NamedTuple{(:first, :last), Tuple{T, T}}}, meta) where T + L, R = ArrowTypes.JuliaType.(Val.(Symbol.(split(meta, ",")))) + return Interval{T,L,R} + end + function ArrowTypes.fromarrow(::Type{Interval{T,L,R}}, left, right) where {T, L <: Bound, R <: Bound} + return Interval{T,L,R}( + L === Unbounded ? nothing : left, + R === Unbounded ? nothing : right, + ) + end +end + +# A less efficient Arrow serialization format for when a vector contains non-concrete element types +let name = Symbol("JuliaLang.Intervals.Interval{T}") + ArrowTypes.arrowname(::Type{<:Interval{T}}) where T = name + function ArrowTypes.ArrowType(::Type{<:Interval{T}}) where T + return NamedTuple{(:left, :right), Tuple{Tuple{String, T}, Tuple{String, T}}} + end + function ArrowTypes.toarrow(x::Interval{T}) where T + L, R = bounds_types(x) + return (; left=(string(arrowname(L)), x.first), right=(string(arrowname(R)), x.last)) + end + function ArrowTypes.JuliaType(::Val{name}, ::Type{NamedTuple{names, types}}) where {names, types} + T = fieldtype(fieldtype(types, 1), 2) + return Interval{T} + end + function ArrowTypes.fromarrow(::Type{Interval{T}}, left, right) where T + L = ArrowTypes.JuliaType(Val(Symbol(left[1]))) + R = ArrowTypes.JuliaType(Val(Symbol(right[1]))) + return Interval{T,L,R}( + L === Unbounded ? nothing : left[2], + R === Unbounded ? nothing : right[2], + ) + end +end + +# AnchoredInterval support was initially implemented in https://github.com/invenia/Intervals.jl/pull/167 +# but was not roundtrippable since all Periods were coerced to Second. +# A new implementation that stores the period type will be needed. diff --git a/src/interval_sets.jl b/src/interval_sets.jl index f49fee8d..dba1c7f1 100644 --- a/src/interval_sets.jl +++ b/src/interval_sets.jl @@ -22,7 +22,7 @@ julia> intersect(IntervalSet(1..5), IntervalSet(3..8)) [3 .. 5] julia> symdiff(IntervalSet(1..5), IntervalSet(3..8)) -2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}: +2-interval IntervalSet{Interval{Int64}}: [1 .. 3) (5 .. 8] @@ -42,7 +42,7 @@ julia> intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) [8 .. 9] julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14])) -2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}: +2-interval IntervalSet{Interval{Int64}}: [1 .. 4) (9 .. 10] ``` diff --git a/test/Project.toml b/test/Project.toml index 2d7573b1..1c11d437 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" diff --git a/test/anchoredinterval.jl b/test/anchoredinterval.jl index 183d93a6..af0a76cb 100644 --- a/test/anchoredinterval.jl +++ b/test/anchoredinterval.jl @@ -237,33 +237,18 @@ using Intervals: Bounded, Ending, Beginning, canonicalize, isunbounded where_lr = "where {L<:$Bounded, R<:$Bounded}" where_tlr = "where {T, L<:$Bounded, R<:$Bounded}" - if VERSION >= v"1.7.0" - @test sprint(show, AnchoredInterval{Hour(-1)}) == - "HourEnding" - @test sprint(show, AnchoredInterval{Hour(1)}) == - "HourBeginning" - @test sprint(show, AnchoredInterval{Day(-1)}) == - "AnchoredInterval{Day(-1)}" - @test sprint(show, AnchoredInterval{Day(1)}) == - "AnchoredInterval{Day(1)}" - @test sprint(show, AnchoredInterval{Day(-1), DateTime}) == - "AnchoredInterval{Day(-1), DateTime}" - @test sprint(show, AnchoredInterval{Day(1), DateTime}) == - "AnchoredInterval{Day(1), DateTime}" - else - @test sprint(show, AnchoredInterval{Hour(-1)}) == - "HourEnding{T, L, R} $where_tlr" - @test sprint(show, AnchoredInterval{Hour(1)}) == - "HourBeginning{T, L, R} $where_tlr" - @test sprint(show, AnchoredInterval{Day(-1)}) == - "AnchoredInterval{Day(-1), T, L, R} $where_tlr" - @test sprint(show, AnchoredInterval{Day(1)}) == - "AnchoredInterval{Day(1), T, L, R} $where_tlr" - @test sprint(show, AnchoredInterval{Day(-1), DateTime}) == - "AnchoredInterval{Day(-1), DateTime, L, R} $where_lr" - @test sprint(show, AnchoredInterval{Day(1), DateTime}) == - "AnchoredInterval{Day(1), DateTime, L, R} $where_lr" - end + @test sprint(show, AnchoredInterval{Hour(-1)}) == + "HourEnding" + @test sprint(show, AnchoredInterval{Hour(1)}) == + "HourBeginning" + @test sprint(show, AnchoredInterval{Day(-1)}) == + "AnchoredInterval{Day(-1)}" + @test sprint(show, AnchoredInterval{Day(1)}) == + "AnchoredInterval{Day(1)}" + @test sprint(show, AnchoredInterval{Day(-1), DateTime}) == + "AnchoredInterval{Day(-1), DateTime}" + @test sprint(show, AnchoredInterval{Day(1), DateTime}) == + "AnchoredInterval{Day(1), DateTime}" # Tuples contain fields: interval, printed, shown tests = [ diff --git a/test/arrow.jl b/test/arrow.jl new file mode 100644 index 00000000..c44d2aab --- /dev/null +++ b/test/arrow.jl @@ -0,0 +1,25 @@ +@testset "Arrow support" begin + @testset "Interval (concrete)" begin + col = [Interval{Closed,Unbounded}(1, nothing)] + + table = (; col) + t = Arrow.Table(Arrow.tobuffer(table)) + + @test eltype(t.col) == Interval{Int, Closed, Unbounded} + @test t.col == col + end + + @testset "Interval (non-concrete)" begin + col = [ + Interval{Closed, Closed}(1, 2), + Interval{Closed, Open}(2, 3), + Interval{Unbounded, Open}(nothing, 4), + ] + + table = (; col) + t = Arrow.Table(Arrow.tobuffer(table)) + + @test eltype(t.col) == Interval{Int} + @test t.col == col + end +end diff --git a/test/runtests.jl b/test/runtests.jl index f622a574..436bd136 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,4 @@ +using Arrow using Base.Iterators: product using Dates using Documenter: doctest @@ -20,12 +21,11 @@ include("test_utils.jl") include("anchoredinterval.jl") include("comparisons.jl") include("sets.jl") + include("arrow.jl") include("plotting.jl") - # Note: The output of the doctests currently requires a newer version of Julia - # https://github.com/JuliaLang/julia/pull/34387 - # The doctests fail on x86, so only run them on 64-bit hardware - if v"1.6" <= VERSION < v"1.7" && Sys.WORD_SIZE == 64 + # only run doctests on one version (LTS) + if v"1.10" <= VERSION < v"1.11" doctest(Intervals) else @warn "Skipping doctests"