Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
name = "Mongoc"
uuid = "4fe8b98c-fc19-5c23-8ec2-168ff83495f2"
license = "MIT"
version = "0.10.1"
authors = ["Felipe Noronha <felipenoris@gmail.com>"]
repo = "https://github.com/JuliaDatabases/Mongoc.jl.git"
version = "0.10.1"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DecFP = "55939f99-70c6-5e9b-8bb0-5071ed7d61fd"
MongoC_jll = "90100e71-7732-535a-9be7-2e9affd1cfc1"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[compat]
DecFP = "1"
MongoC_jll = "1"
OrderedCollections = "1"
julia = "1.6"

[extras]
Expand Down
2 changes: 1 addition & 1 deletion src/Mongoc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Mongoc
using MongoC_jll

import Base.UUID
using Dates, DecFP, Serialization
using Dates, DecFP, Serialization, OrderedCollections

#
# utility functions for date conversion
Expand Down
133 changes: 121 additions & 12 deletions src/bson.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Base.convert(::Type{T}, t::BSONType) where {T<:Number} = T(reinterpret(Cint, t))
Base.convert(::Type{BSONType}, n::T) where {T<:Number} = reinterpret(BSONType, Cint(n))
BSONType(u::UInt8) = convert(BSONType, u)

const DEFAULT_DICT_TYPE = Ref{Type{<:AbstractDict}}(Dict)

#
# Constants for BSONType
#
Expand Down Expand Up @@ -383,7 +385,7 @@ function BSON(args::Pair...)
result = BSON()

for (k, v) in args
result[k] = v
result[String(k)] = v
end

return result
Expand Down Expand Up @@ -503,16 +505,16 @@ Base.keys(doc::BSON) = BSONIterator(doc, IterateKeys)
Base.values(doc::BSON) = BSONIterator(doc, IterateValues)

"""
as_dict(document::BSON) :: Dict{String}
as_dict(document::BSON; dicttype::Type{D} = DEFAULT_DICT_TYPE[]) :: Dict{String}

Converts a BSON document to a Julia `Dict`.
Converts a BSON document to a given dicttype, defaulting to DEFAULT_DICT_TYPE[], which defaults to Dict
"""
as_dict(document::BSON) = convert(Dict, document)
as_dict(document::BSON; @nospecialize dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict = convert(D, document)

function as_dict(iter_ref::Ref{BSONIter})
result = Dict{String, Any}()
function as_dict(iter_ref::Ref{BSONIter}; @nospecialize dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict
result = D{String, Any}()
while bson_iter_next(iter_ref)
result[unsafe_string(bson_iter_key(iter_ref))] = get_value(iter_ref)
result[unsafe_string(bson_iter_key(iter_ref))] = get_value(iter_ref; dicttype)
end
return result
end
Expand Down Expand Up @@ -552,7 +554,7 @@ function get_array(iter_ref::Ref{BSONIter}, ::Type{T}) where T
return result_array
end

function get_value(iter_ref::Ref{BSONIter})
function get_value(iter_ref::Ref{BSONIter}; @nospecialize dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict
bson_type = bson_iter_type(iter_ref)

if bson_type == BSON_TYPE_UTF8
Expand Down Expand Up @@ -584,7 +586,7 @@ function get_value(iter_ref::Ref{BSONIter})
if !ok
error("Couldn't iterate document inside BSON.")
end
return as_dict(child_iter_ref)
return as_dict(child_iter_ref; dicttype)

elseif bson_type == BSON_TYPE_BINARY

Expand Down Expand Up @@ -612,10 +614,10 @@ function get_value(iter_ref::Ref{BSONIter})
end
end

function Base.getindex(document::BSON, key::AbstractString)
function Base.getindex(document::BSON, key::AbstractString, @nospecialize dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict
iter_ref = Ref{BSONIter}()
bson_iter_init_find(iter_ref, document.handle, key) || throw(KeyError(key))
return get_value(iter_ref)
return get_value(iter_ref; dicttype = D)
end

function Base.get(document::BSON, key::AbstractString, default::Any)
Expand Down Expand Up @@ -719,7 +721,7 @@ function Base.setindex!(document::BSON, value::BSON, key::AbstractString)
nothing
end

Base.setindex!(document::BSON, value::Dict, key::AbstractString) = setindex!(document, BSON(value), key)
Base.setindex!(document::BSON, value::AbstractDict, key::AbstractString) = setindex!(document, BSON(value), key)

function Base.setindex!(document::BSON, value::Vector{T}, key::AbstractString) where T
sub_document = BSON(value)
Expand Down Expand Up @@ -1092,3 +1094,110 @@ function read_bson_from_json(filepath::AbstractString) :: Vector{BSON}
destroy!(reader)
end
end

function Base.getindex(document::BSON, index::AbstractVector, dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict
v_iter = values(document)
n = Int(length(v_iter))
iter_ref = v_iter.bson_iter_ref
i = 0
vv = Vector{Any}(undef, n)
while(bson_iter_next(iter_ref))
i += 1
if i ∈ index
vv[i] = get_value(iter_ref; dicttype = D)
end
end
vv[index]
end

function Base.merge!(document::BSON)
kk_all = collect(keys(document))
n = length(kk_all)

index_dict = OrderedDict{String, Int}()
for (i, k) in enumerate(kk_all)
index_dict[k] = i # always overwrite → keeps the last index
end

kk = collect(keys(index_dict))
index = collect(values(index_dict))
length(kk) == n && return document

iter_ref = keys(document).bson_iter_ref
vv = document[index]
bson_reinit(document)
for (k, v) in zip(kk, vv)
document[k] = v
end
document
end

function Base.getindex(document::BSON, key::Symbol, @nospecialize dicttype::Type{D} = DEFAULT_DICT_TYPE[]) where D <: AbstractDict
k = String(key)
iter_ref = keys(document).bson_iter_ref
i = 0

while bson_iter_find(iter_ref, k)
i += 1
end
i == 0 && throw(KeyError(k))
bson_iter_init(iter_ref, getfield(document, :handle))
for _ in 1:i
bson_iter_find(iter_ref, k)
end
return get_value(iter_ref; dicttype = D)
end

function Base.setindex!(document::BSON, value, key::Symbol)
k_str::String = String(key)
iter_ref = Ref{BSONIter}()
key_exists = bson_iter_init_find(iter_ref, document.handle, k_str)

if key_exists
# append something inexpensive to parse as dummy value
document[k_str] = 0
kk_all = collect(keys(document))
n = length(kk_all)

index_dict = OrderedDict{String, Int}()
for (i, k) in enumerate(kk_all)
index_dict[k] = i # always overwrite → keeps the last index
end

kk = collect(keys(index_dict))
index = collect(values(index_dict))
k_pos = findfirst(==(k_str), kk)

vv = document[index]
# replace the dummy value by the real value
vv[k_pos] = value
bson_reinit(document)
for (k, v) in zip(kk, vv)
document[k] = k == k_str ? value : v
end
value
else
document[k_str] = value
end
end

function Base.getproperty(document::BSON, key::Symbol)
key == :__handle__ && !haskey(document, :__handle__) && haskey(document, :handle) && return document[:handle]
key == :handle ? getfield(document, :handle) : document[key]
end

function Base.setproperty!(document::BSON, key::Symbol, value)
document[key] = value
end

function Base.setproperty!(document::BSON, key::Symbol, value::Ptr{Nothing})
if key == :handle
setfield!(document, :handle, value)
else
document[key] = value
end
end

function Base.propertynames(document::BSON)
Symbol.(keys(document))
end
4 changes: 4 additions & 0 deletions src/c_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ function bson_oid_is_valid(str::String)
ccall((:bson_oid_is_valid, libbson), Bool, (Cstring, Csize_t), str, str_length)
end

function bson_reinit(bson_document::BSON)
ccall((:bson_reinit, libbson), Cvoid, (Ptr{Cvoid},), bson_document.handle)
end

function bson_append_oid(bson_document::Ptr{Cvoid}, key::AbstractString, key_length::Int, value::BSONObjectId)
ccall((:bson_append_oid, libbson), Bool,
(Ptr{Cvoid}, Cstring, Cint, Ref{BSONObjectId}),
Expand Down
38 changes: 37 additions & 1 deletion test/bson_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using Test
using Dates
using DecFP
using Distributed
using OrderedCollections

@testset "BSON" begin

Expand Down Expand Up @@ -171,6 +172,7 @@ using Distributed
@test doc["_id"] == new_id
@test doc["array"] == [1, 2, false, "inner_string"]
@test doc["document"] == Dict("a"=>1, "b"=>"b_string")
@test doc["document", OrderedDict] == OrderedDict("a"=>1, "b"=>"b_string")
@test doc["null"] == nothing

@test_throws KeyError doc["invalid key"]
Expand All @@ -187,7 +189,7 @@ using Distributed
@test doc_dict["_id"] == new_id
@test doc_dict["array"] == [1, 2, false, "inner_string"]
@test doc_dict["document"] == Dict("a"=>1, "b"=>"b_string")
@test doc_dict["null"] == nothing
@test doc_dict["null"] === nothing

@testset "convert(Dict, BSON)" begin
doc_dict2 = @inferred(convert(Dict, doc))
Expand All @@ -202,6 +204,40 @@ using Distributed

@test_throws MethodError Mongoc.get_array(doc, "float_array", String)
@test_throws ErrorException Mongoc.get_array(doc, "document", Any)

@test doc.a == 1
@test doc.b == 2.2
@test doc.str == "my string"
@test doc.bool_t
@test !doc.bool_f
@test doc._id == new_id
@test doc.array == [1, 2, false, "inner_string"]
@test doc.document == Dict("a"=>1, "b"=>"b_string")
@test doc.null == nothing

@test doc[:document, OrderedDict] == OrderedDict("a"=>1, "b"=>"b_string")
Mongoc.DEFAULT_DICT_TYPE[] = OrderedDict
@test doc.document == OrderedDict("a"=>1, "b"=>"b_string")
Mongoc.DEFAULT_DICT_TYPE[] = Dict


# setindex!() with a key of type String adds another pair
doc["a"] = "new_a"
pairs = collect(doc)
filter!(p -> p[1] == "a", pairs)
@test length(pairs) == 2
@test pairs[1][2] == 1
@test pairs[2][2] == "new_a"
# getindex() retrieves the first pair
@test_broken doc["a"] != 1
@test_broken doc["a"] == "new_a"
# getindex with a key of type Symbol and getproperty() retrieve the last pair
@test doc[:a] == "new_a"
@test doc.a == "new_a"
merge!(doc)
@test doc["a"] == "new_a"
doc.a = "even_newer_a"
@test doc["a"] == "even_newer_a"
end

@testset "BSON write" begin
Expand Down