---**************************************************************************************************
--- Python API for EJDB database library http://ejdb.org
--- Copyright (C) 2012-2013 Softmotions Ltd <info@softmotions.com>
---
--- This file is part of EJDB.
--- EJDB is free software; you can redistribute it and/or modify it under the terms of
--- the GNU Lesser General Public License as published by the Free Software Foundation; either
--- version 2.1 of the License or any later version. EJDB is distributed in the hope
--- that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
--- License for more details.
--- You should have received a copy of the GNU Lesser General Public License along with EJDB;
--- if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
--- Boston, MA 02111-1307 USA.
--- *************************************************************************************************/
-
-
---[["BSON_Value",
- "BSON_Double",
- "BSON_String",
- "BSON_Document",
- "BSON_Array",
- "BSON_Binary",
- "BSON_Binary_Generic",
- "BSON_Binary_Function",
- "BSON_Binary_UUID",
- "BSON_Binary_MD5",
- "BSON_Binary_UserDefined",
- "BSON_ObjectId",
- "BSON_Boolean",
- "BSON_Datetime",
- "BSON_Null",
- "BSON_Regex",
- "BSON_JavaScript",
- "BSON_Symbol",
- "BSON_JavaScriptWithScope",
- "BSON_Int32",
- "BSON_Timestamp",
- "BSON_Int64",]]
-
---BSONValue = {}
---BSONString = {}
-
-local modname = "bson"
+-- Taken from https://github.com/daurnimator/mongol under MIT license
+
+local modname = (...)
local M = {}
+
+local ll = require("ll")
+
_G[modname] = M
package.loaded[modname] = M
+setmetatable(M, { __index = _G })
+if type(_ENV) == "table" then
+ _ENV = M;
+else
+ setfenv(1, M)
+end
-require "middleclass"
+local binary_mt = {}
+local utc_date = {}
+local object_id_mt = {
+ __tostring = function(ob)
+ local t = {}
+ for i = 1, 12 do
+ table.insert(t, string.format("%02x", string.byte(ob.id, i, i)))
+ end
+ return table.concat(t)
+ end;
+ __eq = function(a, b) return a.id == b.id end;
+}
-setmetatable(M, { __index = _G })
-_ENV = M;
+function string_source(s, i)
+ i = i or 1
+ return function(n)
+ if not n then -- Rest of string
+ n = #s - i + 1
+ end
+ i = i + n
+ assert(i - 1 <= #s, "Unable to read enough characters")
+ return string.sub(s, i - n, i - 1)
+ end, function(new_i)
+ if new_i then i = new_i end
+ return i
+ end
+end
+
+
+function object_id_from_string(str)
+ assert(type(str) == "string" and #str == 24, string.format("Invalid object id string %s", str));
+ local t = {}
+ for i = 1, 24, 2 do
+ local n = tonumber(string.sub(str, i, i + 1), 16)
+ assert(n ~= nil, string.format("Invalid object id string %s", str));
+ table.insert(t, string.char(n))
+ end
+ return table.concat(t)
+end
+
+function object_id(str)
+ assert(#str == 12)
+ return setmetatable({ id = str }, object_id_mt)
+end
+
+local function string_to_array_of_chars(s)
+ local t = {}
+ for i = 1, #s do
+ t[i] = string.sub(s, i, i)
+ end
+ return t
+end
+
+local function read_terminated_string(get, terminators)
+ local terminators = string_to_array_of_chars(terminators or "\0")
+ local str = {}
+ local found = 0
+ while found < #terminators do
+ local c = get(1)
+ if c == terminators[found + 1] then
+ found = found + 1
+ else
+ found = 0
+ end
+ table.insert(str, c)
+ end
+ return table.concat(str, "", 1, #str - #terminators)
+end
+
+
+local function read_document(get, numerical)
+ local bytes = ll.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 = ll.from_double(get(8))
+ elseif op == "\2" then -- String
+ local len = ll.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 = ll.le_uint_to_num(get(4))
+ local subtype = get(1)
+ v = get(len)
+ elseif op == "\7" then -- ObjectId
+ v = 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 = ll.le_uint_to_num(get(8), 1, 8)
+ elseif op == "\10" then -- Null
+ v = nil
+ elseif op == "\16" then --int32
+ v = ll.le_int_to_num(get(4), 1, 8)
+ elseif op == "\17" then --int64
+ v = ll.le_int_to_num(get(8), 1, 8)
+ elseif op == "\18" then --int64
+ v = ll.le_int_to_num(get(8), 1, 8)
+ else
+ error("Unknown BSON type: " .. string.byte(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
-local typeHandlers = {}
+ return t
+end
+function get_utc_date(v)
+ return setmetatable({ v = v }, utc_date)
+end
+function get_bin_data(v)
+ return setmetatable({ v = v, st = "\0" }, binary_mt)
+end
+function from_bson(get)
+ local t = read_document(get, false)
+ return t
+end
+local function pack(k, v)
+ local ot = type(v)
+ local mt = getmetatable(v)
+ if ot == "number" then
+ return "\1" .. k .. "\0" .. ll.to_double(v)
+ elseif ot == "nil" then
+ return "\10" .. k .. "\0"
+ elseif ot == "string" then
+ return "\2" .. k .. "\0" .. ll.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" .. ll.num_to_le_int(v.v, 8)
+ elseif mt == binary_mt then
+ return "\5" .. k .. "\0" .. ll.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
-print("TYPE=" .. type(1223))
+ 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
+ table.insert(r, pack(k, v))
+ end
+ m = table.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 = table.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 ll.num_to_le_uint(#m + 4 + 1) .. m .. "\0", retarray
+end
-
-
-
-require "bson";
-
-
--- print(package.path)
---for p in pairs(package.loaded) do
--- print(p)
---end
+require("bson");
+
+local o = {
+ a = "lol";
+ b = "foo";
+ c = 42;
+ d = { 5, 4, 3, 2, 1 };
+ e = { { { {} } } };
+ f = { [true] = { baz = "mars" } };
+ g = bson.object_id("abcdefghijkl")
+ --z = { [{}] = {} } ; -- Can't test as tables are unique
+}
+
+for k, v in pairs(o) do
+ print("k=", k);
+end
+
+print("\n\n")
+
+assert(tostring(bson.object_id(bson.object_id_from_string(tostring(o.g)))) == tostring(o.g))
+
+local b = bson.to_bson(o)
+local t = bson.from_bson(bson.string_source(b))
+
+for k, v in pairs(t) do
+ print("k=", k, "v=", v);
+end
+
+local function confirm ( orig , new , d )
+ d = d or 1
+ local ok = true
+ for k ,v in pairs ( orig ) do
+ local nv = new [ k ]
+ --print(string.rep ( "\t" , d-1) , "KEY", type(k),k, "VAL",type(v),v,"NEWVAL",type(nv),nv)
+ if nv == v then
+ elseif type ( v ) == "table" and type ( nv ) == "table" then
+ --print(string.rep ( "\t" , d-1) , "Descending" , k )
+ ok = ok and confirm ( v , nv , d+1 )
+ else
+ print(string.rep ( "\t" , d-1) , "Failed on" , k , v , nv )
+ ok = false
+ end
+ end
+ return ok
+end
+
+assert ( confirm ( o , t ) )
+assert ( bson.to_bson ( t ) == bson.to_bson ( t ) )
+--assert ( bson.to_bson ( t ) == b )
--- /dev/null
+-- Library for reading low level data
+-- Taken from https://github.com/daurnimator/mongol under MIT license
+
+local assert = assert
+local unpack = unpack
+local floor = math.floor
+local strbyte, strchar = string.byte, string.char
+
+local ll = {}
+
+local le_uint_to_num = function(s, i, j)
+ i, j = i or 1, j or #s
+ local b = { strbyte(s, i, j) }
+ local n = 0
+ for i = #b, 1, -1 do
+ n = n * 2 ^ 8 + b[i]
+ end
+ return n
+end
+local le_int_to_num = function(s, i, j)
+ i, j = i or 1, j or #s
+ local n = le_uint_to_num(s, i, j)
+ local overflow = 2 ^ (8 * (j - i) + 7)
+ if n > 2 ^ overflow then
+ n = -(n % 2 ^ overflow)
+ end
+ return n
+end
+local num_to_le_uint = function(n, bytes)
+ bytes = bytes or 4
+ local b = {}
+ for i = 1, bytes do
+ b[i], n = n % 2 ^ 8, floor(n / 2 ^ 8)
+ end
+ assert(n == 0)
+ return strchar(unpack(b))
+end
+local num_to_le_int = function(n, bytes)
+ bytes = bytes or 4
+ if n < 0 then -- Converted to unsigned.
+ n = 2 ^ (8 * bytes) + n
+ end
+ return num_to_le_uint(n, bytes)
+end
+
+local be_uint_to_num = function(s, i, j)
+ i, j = i or 1, j or #s
+ local b = { strbyte(s, i, j) }
+ local n = 0
+ for i = 1, #b do
+ n = n * 2 ^ 8 + b[i]
+ end
+ return n
+end
+local num_to_be_uint = function(n, bytes)
+ bytes = bytes or 4
+ local b = {}
+ for i = bytes, 1, -1 do
+ b[i], n = n % 2 ^ 8, floor(n / 2 ^ 8)
+ end
+ assert(n == 0)
+ return strchar(unpack(b))
+end
+
+-- Returns (as a number); bits i to j (indexed from 0)
+local extract_bits = function(s, i, j)
+ j = j or i
+ local i_byte = floor(i / 8) + 1
+ local j_byte = floor(j / 8) + 1
+
+ local n = be_uint_to_num(s, i_byte, j_byte)
+ n = n % 2 ^ (j_byte * 8 - i)
+ n = floor(n / 2 ^ ((-(j + 1)) % 8))
+ return n
+end
+
+-- Look at ith bit in given string (indexed from 0)
+-- Returns boolean
+local le_bpeek = function(s, bitnum)
+ local byte = floor(bitnum / 8) + 1
+ local bit = bitnum % 8
+ local char = strbyte(s, byte)
+ return floor((char % 2 ^ (bit + 1)) / 2 ^ bit) == 1
+end
+-- Test with:
+--local sum = 0 for i=0,31 do v = le_bpeek ( num_to_le_uint ( N , 4 ) , i ) sum=sum + ( v and 1 or 0 )*2^i end assert ( sum == N )
+
+local be_bpeek = function(s, bitnum)
+ local byte = floor(bitnum / 8) + 1
+ local bit = 7 - bitnum % 8
+ local char = strbyte(s, byte)
+ return floor((char % 2 ^ (bit + 1)) / 2 ^ bit) == 1
+end
+-- Test with:
+--local sum = 0 for i=0,31 do v = be_bpeek ( num_to_be_uint ( N , 4 ) , i ) sum=sum + ( v and 1 or 0 )*2^(31-i) end assert ( sum == N )
+
+
+local hasffi, ffi = pcall(require, "ffi")
+local to_double, from_double
+do
+ local s, e, d
+ if hasffi then
+ d = ffi.new("double[1]")
+ else
+ -- Can't use with to_double as we can't strip debug info :(
+ d = string.dump(loadstring([[return 523123.123145345]]))
+ s, e = d:find("\3\54\208\25\126\204\237\31\65")
+ s = d:sub(1, s)
+ e = d:sub(e + 1, -1)
+ end
+
+ function to_double(n)
+ if hasffi then
+ d[0] = n
+ return ffi.string(d, 8)
+ else
+ -- Should be the 8 bytes following the second \3 (LUA_TSTRING == '\3')
+ local str = string.dump(loadstring([[return ]] .. n))
+ local loc, en, mat = str:find("\3(........)", str:find("\3") + 1)
+ return mat
+ end
+ end
+
+ function from_double(str)
+ assert(#str == 8)
+ if hasffi then
+ ffi.copy(d, str, 8)
+ return d[0]
+ else
+ str = s .. str .. e
+ return loadstring(str)()
+ end
+ end
+end
+
+return {
+ le_uint_to_num = le_uint_to_num;
+ le_int_to_num = le_int_to_num;
+ num_to_le_uint = num_to_le_uint;
+ num_to_le_int = num_to_le_int;
+
+ be_uint_to_num = be_uint_to_num;
+ num_to_be_uint = num_to_be_uint;
+
+ extract_bits = extract_bits;
+
+ le_bpeek = le_bpeek;
+ be_bpeek = be_bpeek;
+
+ to_double = to_double;
+ from_double = from_double;
+}
\ No newline at end of file