You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.2 KiB
Lua
217 lines
5.2 KiB
Lua
local mod_name = (...):match ( "^(.*)%..-$" )
|
|
|
|
local assert , error = assert , error
|
|
local pairs = pairs
|
|
local getmetatable = getmetatable
|
|
local type = type
|
|
local tonumber , tostring = tonumber , tostring
|
|
local t_insert = table.insert
|
|
local t_concat = table.concat
|
|
local strformat = string.format
|
|
local strmatch = string.match
|
|
local strbyte = string.byte
|
|
|
|
local ll = require ( mod_name .. ".ll" )
|
|
local le_uint_to_num = ll.le_uint_to_num
|
|
local le_int_to_num = ll.le_int_to_num
|
|
local num_to_le_uint = ll.num_to_le_uint
|
|
local num_to_le_int = ll.num_to_le_int
|
|
local from_double = ll.from_double
|
|
local to_double = ll.to_double
|
|
|
|
local getlib = require ( mod_name .. ".get" )
|
|
local read_terminated_string = getlib.read_terminated_string
|
|
|
|
local obid = require ( mod_name .. ".object_id" )
|
|
local new_object_id = obid.new
|
|
local object_id_mt = obid.metatable
|
|
local binary_mt = {}
|
|
local utc_date = {}
|
|
|
|
|
|
local function read_document ( get , numerical )
|
|
local bytes = le_uint_to_num ( get ( 4 ) )
|
|
|
|
local ho , hk , hv = false , false , false
|
|
local t = { }
|
|
while true do
|
|
local op = get ( 1 )
|
|
if op == "\0" then break end
|
|
|
|
local e_name = read_terminated_string ( get )
|
|
local v
|
|
if op == "\1" then -- Double
|
|
v = from_double ( get ( 8 ) )
|
|
elseif op == "\2" then -- String
|
|
local len = le_uint_to_num ( get ( 4 ) )
|
|
v = get ( len - 1 )
|
|
assert ( get ( 1 ) == "\0" )
|
|
elseif op == "\3" then -- Embedded document
|
|
v = read_document ( get , false )
|
|
elseif op == "\4" then -- Array
|
|
v = read_document ( get , true )
|
|
elseif op == "\5" then -- Binary
|
|
local len = le_uint_to_num ( get ( 4 ) )
|
|
local subtype = get ( 1 )
|
|
v = get ( len )
|
|
elseif op == "\7" then -- ObjectId
|
|
v = new_object_id ( get ( 12 ) )
|
|
elseif op == "\8" then -- false
|
|
local f = get ( 1 )
|
|
if f == "\0" then
|
|
v = false
|
|
elseif f == "\1" then
|
|
v = true
|
|
else
|
|
error ( f:byte ( ) )
|
|
end
|
|
elseif op == "\9" then -- UTC datetime milliseconds
|
|
v = le_uint_to_num ( get ( 8 ) , 1 , 8 )
|
|
elseif op == "\10" then -- Null
|
|
v = nil
|
|
elseif op == "\16" then --int32
|
|
v = le_int_to_num ( get ( 4 ) , 1 , 8 )
|
|
elseif op == "\17" then --int64
|
|
v = le_int_to_num(get(8), 1, 8)
|
|
elseif op == "\18" then --int64
|
|
v = le_int_to_num(get(8), 1, 8)
|
|
else
|
|
error ( "Unknown BSON type: " .. strbyte ( op ) )
|
|
end
|
|
|
|
if numerical then
|
|
t [ tonumber ( e_name ) ] = v
|
|
else
|
|
t [ e_name ] = v
|
|
end
|
|
|
|
-- Check for special universal map
|
|
if e_name == "_keys" then
|
|
hk = v
|
|
elseif e_name == "_vals" then
|
|
hv = v
|
|
else
|
|
ho = true
|
|
end
|
|
end
|
|
|
|
if not ho and hk and hv then
|
|
t = { }
|
|
for i=1,#hk do
|
|
t [ hk [ i ] ] = hv [ i ]
|
|
end
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
local function get_utc_date(v)
|
|
return setmetatable({v = v}, utc_date)
|
|
end
|
|
|
|
local function get_bin_data(v)
|
|
return setmetatable({v = v, st = "\0"}, binary_mt)
|
|
end
|
|
|
|
local function from_bson ( get )
|
|
local t = read_document ( get , false )
|
|
return t
|
|
end
|
|
|
|
local to_bson
|
|
local function pack ( k , v )
|
|
local ot = type ( v )
|
|
local mt = getmetatable ( v )
|
|
|
|
if ot == "number" then
|
|
return "\1" .. k .. "\0" .. to_double ( v )
|
|
elseif ot == "nil" then
|
|
return "\10" .. k .. "\0"
|
|
elseif ot == "string" then
|
|
return "\2" .. k .. "\0" .. num_to_le_uint ( #v + 1 ) .. v .. "\0"
|
|
elseif ot == "boolean" then
|
|
if v == false then
|
|
return "\8" .. k .. "\0\0"
|
|
else
|
|
return "\8" .. k .. "\0\1"
|
|
end
|
|
elseif mt == object_id_mt then
|
|
return "\7" .. k .. "\0" .. v.id
|
|
elseif mt == utc_date then
|
|
return "\9" .. k .. "\0" .. num_to_le_int(v.v, 8)
|
|
elseif mt == binary_mt then
|
|
return "\5" .. k .. "\0" .. num_to_le_uint(string.len(v.v)) ..
|
|
v.st .. v.v
|
|
elseif ot == "table" then
|
|
local doc , array = to_bson(v)
|
|
if array then
|
|
return "\4" .. k .. "\0" .. doc
|
|
else
|
|
return "\3" .. k .. "\0" .. doc
|
|
end
|
|
else
|
|
error ( "Failure converting " .. ot ..": " .. tostring ( v ) )
|
|
end
|
|
end
|
|
|
|
function to_bson(ob)
|
|
-- Find out if ob if an array; string->value map; or general table
|
|
local onlyarray = true
|
|
local seen_n , high_n = { } , 0
|
|
local onlystring = true
|
|
for k , v in pairs ( ob ) do
|
|
local t_k = type ( k )
|
|
onlystring = onlystring and ( t_k == "string" )
|
|
if onlyarray then
|
|
if t_k == "number" and k >= 0 then
|
|
if k >= high_n then
|
|
high_n = k
|
|
seen_n [ k ] = v
|
|
end
|
|
else
|
|
onlyarray = false
|
|
end
|
|
end
|
|
if not onlyarray and not onlystring then break end
|
|
end
|
|
|
|
local retarray , m = false
|
|
if onlystring then -- Do string first so the case of an empty table is done properly
|
|
local r = { }
|
|
for k , v in pairs ( ob ) do
|
|
--ngx.log(ngx.ERR,"="..k..i)
|
|
t_insert ( r , pack ( k , v ) )
|
|
end
|
|
m = t_concat ( r )
|
|
elseif onlyarray then
|
|
local r = { }
|
|
|
|
local low = 0
|
|
--if seen_n [ 0 ] then low = 0 end
|
|
for i=low , high_n do
|
|
r [ i ] = pack ( i , seen_n [ i ] )
|
|
end
|
|
|
|
m = t_concat ( r , "" , low , high_n )
|
|
retarray = true
|
|
else
|
|
local ni = 1
|
|
local keys , vals = { } , { }
|
|
for k , v in pairs ( ob ) do
|
|
keys [ ni ] = k
|
|
vals [ ni ] = v
|
|
ni = ni + 1
|
|
end
|
|
return to_bson ( { _keys = keys , _vals = vals } )
|
|
end
|
|
|
|
return num_to_le_uint ( #m + 4 + 1 ) .. m .. "\0" , retarray
|
|
end
|
|
|
|
return {
|
|
from_bson = from_bson ;
|
|
to_bson = to_bson ;
|
|
get_bin_data = get_bin_data;
|
|
get_utc_date = get_utc_date;
|
|
}
|