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

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;
}