#51
authoradam <adamansky@gmail.com>
Mon, 4 Mar 2013 11:05:13 +0000 (18:05 +0700)
committeradam <adamansky@gmail.com>
Mon, 4 Mar 2013 11:05:13 +0000 (18:05 +0700)
luaejdb/bson.lua
luaejdb/ejdb.lua
luaejdb/ll.lua [new file with mode: 0644]
luaejdb/middleclass.lua

index 80ef1d4..cb01856 100644 (file)
---**************************************************************************************************
---  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
index ed56610..0a62ac9 100644 (file)
@@ -1,13 +1,52 @@
-
-
-
-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 )
 
 
 
diff --git a/luaejdb/ll.lua b/luaejdb/ll.lua
new file mode 100644 (file)
index 0000000..36f7e23
--- /dev/null
@@ -0,0 +1,152 @@
+-- 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
index 3a13c80..d0b6a45 100644 (file)
@@ -79,7 +79,8 @@ Object = _createClass("Object", nil)
 
 Object.static.__metamethods = {
   '__add', '__call', '__concat', '__div', '__le', '__lt',
-  '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
+  '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm',
+  '__eq'
 }
 
 function Object.static:allocate()