Skip to content

Commit

Permalink
improve MDBValue type handling
Browse files Browse the repository at this point in the history
- prevent possible invalid ptr for `Number` types by holding a reference utill `ccall` returns (fixes wildart#12)
- allow using any immutable bits types to be used in keys and values (fixes wildart#13)
  • Loading branch information
tanmaykm committed Nov 14, 2017
1 parent 6d56c88 commit 9a3e394
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 70 deletions.
33 changes: 14 additions & 19 deletions src/common.jl
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
const Cmode_t = Cushort

"Generic structure used for passing keys and data in and out of the database."
type MDBValue
immutable MDBValue
size::Csize_t # size of the data item
data::Ptr{Void} # address of the data item
end
MDBValue() = MDBValue(zero(Csize_t), C_NULL)
function MDBValue(val)
val_size = sizeof(val)
val = isa(val, Number) ? typeof(val)[val] : val
return MDBValue(val_size, pointer(val))
end
MDBValue(_::Void) = MDBValue()
MDBValue{T<:Union{String,Array}}(val::T) = MDBValue(sizeof(val), pointer(val))
MDBValue{T<:Number}(val::T) = error("can not wrap a $T in MDBValue, use a $T array instead")
MDBValue(val) = MDBValue(sizeof(val), pointer_from_objref(val))

function convert{T}(::Type{T}, mdb_val_ref::Ref{MDBValue})
mdb_val = mdb_val_ref[]
return if T <: String
unsafe_string(convert(Ptr{UInt8}, mdb_val.data), mdb_val.size)
elseif T <: AbstractVector
E = eltype(T)
nvals = floor(Int, mdb_val.size/sizeof(E))
unsafe_wrap(T, convert(Ptr{E}, mdb_val.data), nvals)
else
unsafe_load(convert(Ptr{T}, mdb_val.data))
end
convert{T}(::Type{T}, mdb_val_ref::Ref{MDBValue}) = _convert(T, mdb_val_ref[])
_convert(::Type{String}, mdb_val::MDBValue) = unsafe_string(convert(Ptr{UInt8}, mdb_val.data), mdb_val.size)
function _convert{T}(::Type{Vector{T}}, mdb_val::MDBValue)
nvals = floor(Int, mdb_val.size/sizeof(T))
unsafe_wrap(Array, convert(Ptr{T}, mdb_val.data), nvals)
end
_convert{T<:Number}(::Type{T}, mdb_val::MDBValue) = unsafe_wrap(Array, convert(Ptr{T}, mdb_val.data), 1)[1]
_convert{T}(::Type{T}, mdb_val::MDBValue) = unsafe_load(convert(Ptr{T}, mdb_val.data), 1)

# Environment Flags
# -----------------
Expand Down Expand Up @@ -117,9 +112,9 @@ end
"""LMDB exception type"""
immutable LMDBError <: Exception
code::Cint
msg::AbstractString
LMDBError(code::Cint) = new(code, errormsg(code))
msg::String
end
LMDBError(code::Cint) = LMDBError(code, errormsg(code))
Base.show(io::IO, err::LMDBError) = print(io, "Code[$(err.code)]: $(err.msg)")

""" Check if binary flag is set in provided value"""
Expand Down
7 changes: 7 additions & 0 deletions src/cur.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ end
This function retrieves key/data pairs from the database.
"""
get{K<:Number,T}(cur::Cursor, key::K, ::Type{T}) = get(cur, [key], T)
function get{T}(cur::Cursor, key, ::Type{T}, op::CursorOps=FIRST)
# Setup parameters
mdb_key_ref = Ref(MDBValue(key))
Expand All @@ -85,6 +86,12 @@ end
This function stores key/data pairs into the database. The cursor is positioned at the new item, or on failure usually near it.
"""
function put!(cur::Cursor, key, val; flags::Cuint = zero(Cuint))
if isa(key, Number) || isa(val, Number)
key = isa(key, Number) ? [key] : key
val = isa(val, Number) ? [val] : val
return put!(cur, key, val; flags=flags)
end

mdb_key_ref = Ref(MDBValue(key))
mdb_val_ref = Ref(MDBValue(val))

Expand Down
15 changes: 14 additions & 1 deletion src/dbi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ end

"Store items into a database"
function put!(txn::Transaction, dbi::DBI, key, val; flags::Cuint = zero(Cuint))
if isa(key, Number) || isa(val, Number)
key = isa(key, Number) ? [key] : key
val = isa(val, Number) ? [val] : val
return put!(txn, dbi, key, val; flags=flags)
end

mdb_key_ref = Ref(MDBValue(key))
mdb_val_ref = Ref(MDBValue(val))

Expand All @@ -81,6 +87,12 @@ end

"Delete items from a database"
function delete!(txn::Transaction, dbi::DBI, key, val=C_NULL)
if isa(key, Number) || isa(val, Number)
key = isa(key, Number) ? [key] : key
val = isa(val, Number) ? [val] : val
return delete!(txn, dbi, key, val)
end

mdb_key_ref = Ref(MDBValue(key))
mdb_val_ref = (val === C_NULL) ? C_NULL : Ref(MDBValue(val))

Expand All @@ -92,7 +104,8 @@ function delete!(txn::Transaction, dbi::DBI, key, val=C_NULL)
return ret
end

"Get items from a database"
"Get items from a database. Returned values are safe to access as long as the transaction is not closed."
get{K<:Number,T}(txn::Transaction, dbi::DBI, key::K, ::Type{T}) = get(txn, dbi, [key], T)
function get{T}(txn::Transaction, dbi::DBI, key, ::Type{T})
# Setup parameters
mdb_key_ref = Ref(MDBValue(key))
Expand Down
2 changes: 1 addition & 1 deletion test/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module LMDB_Common
val = [1233]
T = eltype(val)
val_size = sizeof(val)
mdb_val = MDBValue(val[1])
mdb_val = MDBValue(val)
@test val_size == mdb_val.size
nvals = floor(Int, mdb_val.size/sizeof(T))
value = unsafe_wrap(Array, convert(Ptr{T}, mdb_val.data), nvals)
Expand Down
190 changes: 142 additions & 48 deletions test/dbi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,156 @@ module LMDB_DBI
using Base.Test

const dbname = "testdb"
key = 10
val = "key value is "

# Create dir
mkdir(dbname)
try
immutable MyType
intval::Int
boolval::Bool
end

function test_int_str()
println(" int keys, string values")
key = 10
val = "key value is "

# Procedural style
env = create()
# Create dir
mkdir(dbname)
try
open(env, dbname)
txn = start(env)
dbi = open(txn)
put!(txn, dbi, key+1, val*string(key+1))
put!(txn, dbi, key, val*string(key))
put!(txn, dbi, key+2, key+2)
put!(txn, dbi, key+3, [key, key+1, key+2])
@test isopen(txn)
commit(txn)
@test !isopen(txn)
close(env, dbi)
@test !isopen(dbi)
# Procedural style
env = create()
try
open(env, dbname)
txn = start(env)
dbi = open(txn)
put!(txn, dbi, key+1, val*string(key+1))
println(" put value for key $(key+1): $(val*string(key+1))")
put!(txn, dbi, key, val*string(key))
println(" put value for key $(key): $(val*string(key))")
@test isopen(txn)
commit(txn)
@test !isopen(txn)
close(env, dbi)
@test !isopen(dbi)
finally
close(env)
end
@test !isopen(env)

# Block style
create() do env
set!(env, LMDB.NOSYNC)
open(env, dbname)
start(env) do txn
open(txn, flags = Cuint(LMDB.REVERSEKEY)) do dbi
k = key
value = get(txn, dbi, k, String)
println(" got value for key $(k): $(value)")
@test value == val*string(k)
delete!(txn, dbi, k)
k += 1
value = get(txn, dbi, k, String)
println(" got value for key $(k): $(value)")
@test value == val*string(k)
delete!(txn, dbi, k, value)
@test_throws LMDBError get(txn, dbi, k, String)
end
end
end
finally
close(env)
rm(dbname, recursive=true)
end
@test !isopen(env)
end

function test_str_int()
println(" string keys, int values")
key = "key1"
val = 10

# Create dir
mkdir(dbname)
try
env = create()
try
open(env, dbname)
txn = start(env)
dbi = open(txn)
put!(txn, dbi, key, val)
println(" put value for key $(key): $(val)")
@test isopen(txn)
commit(txn)
@test !isopen(txn)
close(env, dbi)
@test !isopen(dbi)
finally
close(env)
end
@test !isopen(env)

# Block style
create() do env
set!(env, LMDB.NOSYNC)
open(env, dbname)
start(env) do txn
open(txn, flags = Cuint(LMDB.REVERSEKEY)) do dbi
k = key
value = get(txn, dbi, k, String)
println("Got value for key $(k): $(value)")
@test value == val*string(k)
delete!(txn, dbi, k)
k += 1
value = get(txn, dbi, k, String)
println("Got value for key $(k): $(value)")
@test value == val*string(k)
delete!(txn, dbi, k, value)
@test_throws LMDBError get(txn, dbi, k, String)
k += 1
value = get(txn, dbi, k, Int)
println("Got value for key $(k): $(value)")
@test value == k
k += 1
value = get(txn, dbi, k, Vector{Int})
println("Got value for key $(k): $(value)")
@test value == [key, key+1, key+2]
# Block style
create() do env
set!(env, LMDB.NOSYNC)
open(env, dbname)
start(env) do txn
open(txn, flags = Cuint(LMDB.REVERSEKEY)) do dbi
k = key
value = get(txn, dbi, k, Int)
println(" got value for key $(k): $(value)")
@test value == val
delete!(txn, dbi, k)
@test_throws LMDBError get(txn, dbi, k, Int)
end
end
end
finally
rm(dbname, recursive=true)
end
finally
rm(dbname, recursive=true)
end

function test_struct_struct()
println(" struct keys, struct values")
key = MyType(10, true)
val = MyType(11, false)

# Create dir
mkdir(dbname)
try
env = create()
try
open(env, dbname)
txn = start(env)
dbi = open(txn)
put!(txn, dbi, key, val)
println(" put value for key $(key): $(val)")
@test isopen(txn)
commit(txn)
@test !isopen(txn)
close(env, dbi)
@test !isopen(dbi)
finally
close(env)
end
@test !isopen(env)

# Block style
create() do env
set!(env, LMDB.NOSYNC)
open(env, dbname)
start(env) do txn
open(txn, flags = Cuint(LMDB.REVERSEKEY)) do dbi
k = key
value = get(txn, dbi, k, MyType)
println(" got value for key $(k): $(value)")
@test value == val
delete!(txn, dbi, k)
@test_throws LMDBError get(txn, dbi, k, MyType)
end
end
end
finally
rm(dbname, recursive=true)
end
end

test_int_str()
test_str_int()
test_struct_struct()
end
8 changes: 7 additions & 1 deletion test/env.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ module LMDB_Env
@test env[:Readers] == 126
@test env[:KeySize] == 511
@test env[:Flags] == 0
@test path(env) == ""

show(STDOUT, env)

# Manipulate flags
@test !isflagset(env[:Flags], Cuint(LMDB.NOSYNC))
Expand All @@ -30,9 +33,12 @@ module LMDB_Env
ret = open(env, dbname)
@test ret[1] == 0

@show env

# Close environment
close(env)
@test !isopen(env)
@test_throws LMDBError close(env)

# do block
create() do env
Expand All @@ -43,4 +49,4 @@ module LMDB_Env
finally
rm(dbname, recursive=true)
end
end
end

0 comments on commit 9a3e394

Please sign in to comment.