diff --git a/test/dbi.jl b/test/dbi.jl index 0bdb87c..d7410f3 100644 --- a/test/dbi.jl +++ b/test/dbi.jl @@ -1,6 +1,28 @@ using LMDB using Test +# Fixtures for the typed-read extension-point tests below. Defined at +# module scope because `const` and `struct` aren't allowed inside a +# `@testset` block. +struct Point2D + x::Float32 + y::Float32 +end +Base.read(io::IO, ::Type{Point2D}) = read!(io, Ref{Point2D}())[] + +struct FramedU64 + value::UInt64 +end +const FRAME_MAGIC = htol(UInt32(0xCAFEF00D)) +function Base.read(io::IO, ::Type{FramedU64}) + magic = read(io, UInt32) + magic == FRAME_MAGIC || error("bad magic 0x$(string(magic, base=16))") + pos_before = position(io) + skip(io, 0) + @assert position(io) == pos_before + FramedU64(ltoh(read(io, UInt64))) +end + @testset "DBI" begin key = 10 @@ -159,4 +181,82 @@ mktempdir() do dir end end +# Round-trip a custom bitstype through put!/tryget/walk using only the +# IO-based extension point (`Base.read(io::IO, ::Type{Point2D})`, +# defined at module scope above). +mktempdir() do dir + environment(dir) do env + start(env) do txn + open(txn) do dbi + LMDB.put!(txn, dbi, "origin", Point2D(0f0, 0f0)) + LMDB.put!(txn, dbi, "p1", Point2D(1.5f0, 2.5f0)) + + @test LMDB.tryget(txn, dbi, "p1", Point2D) == Point2D(1.5f0, 2.5f0) + @test LMDB.tryget(txn, dbi, "origin", Point2D) == Point2D(0f0, 0f0) + @test get(txn, dbi, "p1", Point2D) == Point2D(1.5f0, 2.5f0) + + # The Vector{E} overload also works for any bitstype E, + # without the user defining anything extra. + @test LMDB.tryget(txn, dbi, "p1", Vector{Point2D}) == + [Point2D(1.5f0, 2.5f0)] + + # Typed walk decodes both K and V through Base.read. + seen = Pair{String,Point2D}[] + LMDB.open(txn, dbi) do cur + LMDB.walk(cur, String, Point2D) do k, v + push!(seen, k => v) + end + end + @test sort(seen; by = first) == + ["origin" => Point2D(0f0, 0f0), + "p1" => Point2D(1.5f0, 2.5f0)] + end + end + end +end + +# Framed values: a custom `Base.read` can use the IO interface +# (`skip`, `position`) to step past a header before decoding the +# payload. Exercises the IO contract from inside user code (see +# `FramedU64` / `FRAME_MAGIC` at module scope). +mktempdir() do dir + environment(dir) do env + start(env) do txn + open(txn) do dbi + LMDB.put_reserved!(txn, dbi, "framed", 12) do buf + unsafe_store!(Ptr{UInt32}(pointer(buf)), FRAME_MAGIC) + unsafe_store!(Ptr{UInt64}(pointer(buf) + 4), htol(UInt64(0x1234_5678))) + end + @test LMDB.tryget(txn, dbi, "framed", FramedU64) == + FramedU64(0x1234_5678) + end + end + end +end + +# Non-Array AbstractArray inputs (e.g. `ReinterpretArray`, contiguous +# `SubArray`) flow through `cconvert(Ptr{MDB_val}, ::AbstractArray)`. +mktempdir() do dir + environment(dir) do env + start(env) do txn + open(txn) do dbi + # ReinterpretArray view onto a backing UInt64 vector. + ra_key = reinterpret(UInt8, UInt64[0xdeadbeefcafef00d]) + @test !(ra_key isa Array) + LMDB.put!(txn, dbi, ra_key, "v-reinterpret") + @test LMDB.tryget(txn, dbi, ra_key, String) == "v-reinterpret" + @test LMDB.tryget(txn, dbi, collect(ra_key), String) == "v-reinterpret" + + # Contiguous SubArray. + backing = collect(0x01:0x10) + sv_key = view(backing, 4:8) + @test !(sv_key isa Array) + LMDB.put!(txn, dbi, sv_key, "v-subarray") + @test LMDB.tryget(txn, dbi, sv_key, String) == "v-subarray" + @test LMDB.tryget(txn, dbi, collect(sv_key), String) == "v-subarray" + end + end + end +end + end # @testset "DBI"