├── .gitignore ├── LICENSE ├── README.md ├── init.lua ├── main.lua ├── package.lua ├── src ├── bson.lua ├── collection.lua ├── cursor.lua ├── get.lua ├── init.lua ├── md5.lua ├── objectId.lua └── utils.lua └── test ├── Date.lua └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /deps/ 2 | luvit-mongodb 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cyril Hou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luvit MongoDB Driver 2 | 3 | [![Join the chat at https://gitter.im/cyrilis/luvit-mongodb](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cyrilis/luvit-mongodb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | This is a pure luvit Mongodb driver with async Api. 6 | 7 | ## What is Luvit? 8 | 9 | [Luvit](https://luvit.io) is a Node.js like, with Asynchronous I/O support language for Lua. 10 | 11 | Learn more a: [Luvit](https://luvit.io/docs.html) 12 | 13 | ## Install 14 | 15 | - #### With [lit](https://luvit.io/lit.html): 16 | 17 | Run 18 | 19 | ```bash 20 | lit install cyrilis/luvit-mongodb 21 | ``` 22 | 23 | in terminal under your project path, then `require("luvit-mongodb")` in your project. 24 | 25 | - #### Or copy source files: 26 | 27 | Just put source file in your project, then require it with `./path/to/luvit-mongodb` 28 | 29 | 30 | ## Getting started 31 | 32 | ```lua 33 | local Mongo = require("luvit-mongodb") 34 | local db = Mongo:new({db = "DATABASE_NAME"}) 35 | -- replace `DATABASE_NAME` above with a DB name. 36 | db:on("connect", function() 37 | -- Do stuff here. 38 | local Post = db:collection("post") 39 | local page = 1 40 | Post:insert({ 41 | title = "Hello word!", 42 | content = "Here is the first blog post ....", 43 | author = "Cyril Hou" 44 | },function(err, res) 45 | p(res) 46 | end) 47 | local posts = Post:find({author = "Cyril Hou"}) 48 | posts:limit(10):skip(page * 10):update({authorAge = 25}):exec(function(err, res) 49 | p(err, res) 50 | end) 51 | 52 | Post:distinct("category", function(err, res) 53 | p("All distinct value of `category` in post collections: ", res) 54 | end) 55 | end) 56 | ``` 57 | 58 | ## Cursor 59 | 60 | - ### update 61 | 62 | `cursor:update(doc[, callback])` 63 | 64 | - `doc`: *table*, **required**, The modifications to apply. 65 | - `callback`: **optional**, callback function 66 | 67 | ```lua 68 | post:update({abc = 123}, function(err, res) 69 | p(err, res) 70 | end) 71 | -- same as bellow ⬇️ 72 | post:update({abc = 123}):exec(function(err, res) 73 | p(err, res) 74 | end) 75 | ``` 76 | 77 | - ### find 78 | 79 | `cursor:find(query[, callback])` 80 | 81 | - `query`: *table*, **optional** , Specifies selection criteria using query operators. To return all documents in a collection, omit this parameter or pass an empty document ({}). 82 | - `callback`: **optional**, callback function 83 | 84 | ```lua 85 | post:find({abc = 123}, function(err, res) 86 | p(err, res) 87 | end) 88 | -- same as bellow ⬇️ 89 | post:find({abc = 123}):exec(function(err, res) 90 | p(err, res) 91 | end) 92 | ``` 93 | 94 | - ### remove 95 | 96 | `cursor:remove([callback])` 97 | 98 | - `callback`: **optional**, callback function 99 | 100 | ```lua 101 | post:remove(function(err, res) 102 | p(err, res) 103 | end) 104 | -- same as bellow ⬇️ 105 | post:remove():exec(function(err, res) 106 | p(err, res) 107 | end) 108 | ``` 109 | 110 | - ### skip 111 | 112 | `cursor:skip(skip[, callback])` 113 | 114 | - `skip`: *number*, docs to skip in query. 115 | - `callback`: **optional**, callback function 116 | 117 | - ### limit 118 | 119 | `cursor:limit(limit[, callback])` 120 | 121 | - `limit`: *number*, limit return docs count. 122 | - `callback`: **optional**, callback function 123 | 124 | - ### Sort 125 | 126 | `cursor:sort(doc[, callback])` 127 | 128 | - `doc`: **table**, 129 | - `callback`: optional, callback function 130 | 131 | Example: 132 | 133 | ```lua 134 | local User = db:collection("user") 135 | User:find():sort({age = 1}):exec(function(err, res) 136 | p(err, res) 137 | end) 138 | ``` 139 | 140 | - ### count 141 | 142 | `cursor:count([callback])` 143 | 144 | - `callback`: **optional**, callback function 145 | 146 | ```lua 147 | post:count(function(err, res) 148 | p(err, res) 149 | end) 150 | -- same as bellow ⬇️ 151 | post:remove():exec(function(err, res) 152 | p(err, res) 153 | end) 154 | ``` 155 | 156 | - ### exec 157 | 158 | `cursor:exec(callback)` 159 | 160 | - `callback`: **required**, callback function 161 | 162 | Execute callback function after set query and limit skip params. 163 | 164 | ## Collection 165 | 166 | - ### initialize 167 | 168 | A collection should initialize with db instance. 169 | 170 | `db:collection(collectionName)` 171 | 172 | - collectionName: *string*, name for collection in name. 173 | 174 | ```lua 175 | local coll = db:collection("post") 176 | ``` 177 | 178 | - ### find 179 | 180 | `coll:find(query[, callback])` 181 | 182 | - query: *table*, **optional**, Specifies selection criteria using query operators. To return all documents in a collection, omit this parameter or pass an empty document ({}). 183 | - callback: *function*, **optional**, callback function. 184 | 185 | ```lua 186 | coll:find({id = 1}):exec(function(err, res) 187 | p(err, res) 188 | end) 189 | -- same as bellow ⬇️ 190 | coll:find({id = 1}, function(err, res) 191 | p(err, res) 192 | end) 193 | ``` 194 | 195 | - ### distinct 196 | 197 | ``` 198 | `coll:distinct(field[, query][, callback])` 199 | ``` 200 | 201 | - field: *string*, **required**, The field for which to return distinct values. 202 | 203 | - query: *table*, **optional**, A query that specifies the documents from which to retrieve the distinct values. 204 | 205 | - callback: *function*, **optional**, Callback function. 206 | 207 | ```lua 208 | coll:distinct("category", {public = true}, function(err, res) 209 | p(err, res) 210 | end) 211 | ``` 212 | 213 | 214 | - ### drop 215 | 216 | `coll:drop(callback)` 217 | 218 | - callback: *function*, **required**, callback function 219 | 220 | ```lua 221 | coll:drop(function(err, res) 222 | p(err, res) 223 | end) 224 | ``` 225 | 226 | - ### findAndModify 227 | 228 | `coll:findAndModify(query, update[, callback])` 229 | 230 | - query: *table*, **required**, Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the `collection.find()` method. Although the query may match multiple documents, `findAndModify()` will only select one document to modify. 231 | - update: *table*, **required**, The update field employs the same update operators or field: value specifications to modify the selected document. 232 | - callback: *function*, **optional**, Callback function. 233 | 234 | ```lua 235 | coll:findAndModify({abc = 123}, {def = true}, function(err, res) 236 | p(err, res) 237 | end) 238 | ``` 239 | 240 | - ### findOne 241 | 242 | `coll:findOne(query[, callback])` 243 | 244 | - query: *table*, **required**, Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the `collection.find()` method. Although the query may match multiple documents, `findAndModify()` will only select one document to modify. 245 | - callback: *function*, **optional**, Callback function. 246 | 247 | ```lua 248 | coll:findOne({abc = 123}, function(err, res) 249 | p(err, res) 250 | end) 251 | ``` 252 | 253 | 254 | - ### remove 255 | 256 | `coll:remove(query[, callback])` 257 | 258 | - query: *table*, **required**, Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the `collection.find()` method 259 | - callback: *function*, **optional**, Callback function. 260 | 261 | ```lua 262 | coll:remove({public = false}, function(err, res) 263 | p(err, res) 264 | end) 265 | ``` 266 | 267 | - ### insert 268 | 269 | `coll:insert(doc[, callback])` 270 | 271 | - doc: *table*, **required**, A document or array of documents to insert into the collection. 272 | - callback: *function*, **optional**, Callback function. 273 | 274 | ```lua 275 | coll:insert({title = "Hello World!"}, function(err, res) 276 | p(err, res) 277 | end) 278 | ``` 279 | 280 | #### chainable cursor example: 281 | 282 | ```lua 283 | coll:find():update({public = true}):exec(function(err, res) 284 | p(err, res) 285 | end) 286 | ``` 287 | 288 | ## Database 289 | 290 | - ### insert: 291 | 292 | ``` 293 | `db:insert(collection, document, continue, callback)` 294 | - `collection`: Database collection name, **required** 295 | - `document`: lua table, **required** 296 | ``` 297 | 298 | - `continue`: if continue when error occured 299 | - `callback`: function, with inserted document as #1 parameter 300 | 301 | - ### find: 302 | 303 | ``` 304 | `db:find(collection, query, fields, skip, limit, callback)` 305 | ``` 306 | 307 | - `collection`: Database collection name, **required** 308 | - `query`: mongodb query eg: `{ type: { $in: [ 'food', 'snacks' ] } }` or just `{ type: "snacks" }`, **required** 309 | - `fields`: limit field for return, eg: `{ item: 1, qty: 1, _id:0 }` 310 | - `skip`: skip counts, *int* 311 | - `limit`: limit return counts, *int* 312 | - `callback`: function, with found documents array as #1 parameter 313 | 314 | - ### update: 315 | 316 | ``` 317 | `db:update(collection, query, update, upsert, single, callback)` 318 | - `collection`: Database collection name, **required** 319 | ``` 320 | 321 | - `query`: mongodb query eg: `{ type: { $in: [ 'food', 'snacks' ] } }` or just `{ type: "snacks" }`, **required** 322 | - `update`: mongodb update, eg: `{ $set: { "detail": "14Q2" } }`, **required** 323 | - `upsert`: if not find result with `query` params, insert a record with `update` params. 324 | - `single`: update multiple records or single record, **boolean**. 325 | - `callback`: function, without parameters 326 | 327 | - ### remove 328 | 329 | ``` 330 | `(collection, query, single, callback)` 331 | - `collection`: Database collection name, **required** 332 | ``` 333 | 334 | - `query`: mongodb query eg: `{ type: { $in: [ 'food', 'snacks' ] } }` or just `{ type: "snacks" }`, **required** 335 | 336 | - `single`: remove multiple records or single, **boolean**. 337 | 338 | ``` 339 | - `callback`: function, without parameters 340 | ``` 341 | 342 | - ### count 343 | 344 | ``` 345 | `(collection, query, callback)` 346 | - `collection`: Database collection name, **required** 347 | ``` 348 | 349 | - `query`: mongodb query eg: `{ type: { $in: [ 'food', 'snacks' ] } }` or just `{ type: "snacks" }`, **required** 350 | 351 | ``` 352 | - `callback`: function, without parameters 353 | ``` 354 | 355 | - ### findOne: 356 | 357 | ``` 358 | `db:find(collection, query, fields, skip, callback)` 359 | ``` 360 | 361 | - `collection`: Database collection name, **required** 362 | - `query`: mongodb query eg: `{ type: { $in: [ 'food', 'snacks' ] } }` or just `{ type: "snacks" }`, **required** 363 | - `fields`: limit field for return, eg: `{ item: 1, qty: 1, _id:0 }` 364 | - `skip`: skip counts, *int* 365 | - `callback`: function, without found document as #1 parameter 366 | 367 | - ### createIndex: 368 | 369 | - ### dropIndex: 370 | 371 | - ### dropIndexes 372 | 373 | - ### getIndexes 374 | 375 | 376 | ## Test and example 377 | 378 | See files in "test/" Folder 379 | 380 | If you have any issue while use this library please let me know. thanks. 381 | 382 | ## TODO: 383 | 384 | - [x] Bit64 support 385 | - [x] Bit32 support 386 | - [x] Date type support 387 | - [x] Cursor 388 | - [x] Raw Command support 389 | - [x] Write concern 390 | - [ ] Auth 391 | - [x] Create index 392 | - [x] Get index 393 | - [x] Remove index 394 | 395 | ## License 396 | 397 | The MIT License (MIT) 398 | 399 | Copyright (c) 2015 Cyril Hou<houshoushuai@gmail.com> 400 | 401 | Permission is hereby granted, free of charge, to any person obtaining a copy 402 | 403 | of this software and associated documentation files (the "Software"), to deal 404 | 405 | in the Software without restriction, including without limitation the rights 406 | 407 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 408 | 409 | copies of the Software, and to permit persons to whom the Software is 410 | 411 | furnished to do so, subject to the following conditions: 412 | 413 | The above copyright notice and this permission notice shall be included in all 414 | 415 | copies or substantial portions of the Software. 416 | 417 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 418 | 419 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 420 | 421 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 422 | 423 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 424 | 425 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 426 | 427 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 428 | 429 | SOFTWARE. 430 | 431 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/21 上午1:51 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | return require("./src/") -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/11/21 下午4:19 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | require("./init") 8 | 9 | -------------------------------------------------------------------------------- /package.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "cyrilis/luvit-mongodb", 3 | version = "0.0.24", 4 | homepage = "https://github.com/cyrilis/luvit-mongodb", 5 | description = "Mongodb Driver in pure luvit.", 6 | tags = {"luvit", "mongodb", "database"}, 7 | license = "MIT", 8 | author = { name = "Cyril Hou" }, 9 | dependencies = { 10 | "luvit/require", 11 | "luvit/pretty-print", 12 | "luvit/core", 13 | "luvit/net" 14 | }, 15 | files = { 16 | "**.lua", 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/bson.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 下午2:37 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | 8 | local assert, error = assert, error 9 | local pairs = pairs 10 | local getmetatable = getmetatable 11 | local type = type 12 | local tonumber, tostring = tonumber, tostring 13 | local t_insert = table.insert 14 | local t_concat = table.concat 15 | local strformat = string.format 16 | local strmatch = string.match 17 | local strbyte = string.byte 18 | 19 | local function toLSB(bytes, value) 20 | local str = "" 21 | for j = 1, bytes do 22 | str = str .. string.char(value % 256) 23 | value = math.floor(value / 256) 24 | end 25 | return str 26 | end 27 | 28 | local ffi = require("ffi") 29 | if pcall(ffi.typeof, "struct timeval") then 30 | else 31 | ffi.cdef[[ 32 | typedef long time_t; 33 | 34 | typedef struct timeval { 35 | time_t tv_sec; 36 | time_t tv_usec; 37 | } timeval; 38 | 39 | int gettimeofday(struct timeval* t, void* tzp); 40 | ]] 41 | end 42 | 43 | local gettimeofday_struct = ffi.new("timeval") 44 | local function getTime() 45 | ffi.C.gettimeofday(gettimeofday_struct, nil) 46 | return tonumber(gettimeofday_struct.tv_sec) * 1000 + tonumber(gettimeofday_struct.tv_usec / 1000) 47 | end 48 | 49 | local function toLSB32(value) return toLSB(4,value) end 50 | local function toLSB64(value) return toLSB(8,value) end 51 | local function to_int32(n,v) return "\016"..n.."\000"..toLSB32(v) end 52 | local function to_int64(n,v) return "\018"..n.."\000"..toLSB64(v) end 53 | local function to_date(n,v) return "\09"..n.."\000"..toLSB64(v) end 54 | 55 | local bit64_meta = { 56 | __tonumber = function(obj) 57 | return obj.value 58 | end, 59 | __tostring = function(obj) 60 | return tostring(obj.value) 61 | end, 62 | __eq = function(a, b) 63 | return getmetatable(a) == getmetatable(b) and a.value == b.value 64 | end, 65 | metatype = "bit64" 66 | } 67 | 68 | local bit32_meta = { 69 | __tonumber = function(obj) 70 | return obj.value 71 | end, 72 | __tostring = function(obj) 73 | return tostring(obj.value) 74 | end, 75 | __eq = function(a, b) 76 | return getmetatable(a) == getmetatable(b) and a.value == b.value 77 | end, 78 | metatype = "bit32" 79 | } 80 | 81 | local date_meta = { 82 | __tonumber = function(obj) 83 | return obj.value 84 | end, 85 | __tostring = function(obj) 86 | return tostring(obj.value) 87 | end, 88 | eq = function(a, b) 89 | return getmetatable(a) == getmetatable(b) and a.value == b.value 90 | end, 91 | metatype = "date" 92 | } 93 | 94 | function Bit64(value) 95 | return setmetatable({ value = value }, bit64_meta) 96 | end 97 | 98 | function Bit32(value) 99 | return setmetatable({ value = value }, bit32_meta) 100 | end 101 | 102 | function Date(value) 103 | value = value or getTime() 104 | value = tonumber(value) 105 | if #tostring(value) < 13 then 106 | value = value * 1000 107 | end 108 | return setmetatable({ value = value }, date_meta) 109 | end 110 | 111 | p(Date()) 112 | 113 | local ll = require("./utils") 114 | local le_uint_to_num = ll.le_uint_to_num 115 | local le_int_to_num = ll.le_int_to_num 116 | local num_to_le_uint = ll.num_to_le_uint 117 | local from_double = ll.from_double 118 | local to_double = ll.to_double 119 | 120 | local getlib = require("./get") 121 | local read_terminated_string = getlib.read_terminated_string 122 | 123 | local obid = require("./objectId") 124 | local ObjectId = obid 125 | local new_object_id = obid.new 126 | local object_id_mt = obid.metatable 127 | 128 | 129 | local function read_document(get, numerical) 130 | local bytes = le_uint_to_num(get(4)) 131 | 132 | local ho, hk, hv = false, false, false 133 | local t = {} 134 | while true do 135 | local op = get(1) 136 | if op == "\0" then break end 137 | local e_name = read_terminated_string ( get ) 138 | local v 139 | if op == "\1" then -- Double 140 | v = from_double ( get ( 8 ) ) 141 | elseif op == "\2" then -- String 142 | local len = le_uint_to_num ( get ( 4 ) ) 143 | v = get ( len - 1 ) 144 | assert ( get ( 1 ) == "\0" ) 145 | elseif op == "\3" then -- Embedded document 146 | v = read_document ( get , false ) 147 | elseif op == "\4" then -- Array 148 | v = read_document ( get , true ) 149 | elseif op == "\5" then -- Binary 150 | local len = le_uint_to_num ( get ( 4 ) ) 151 | local subtype = get ( 1 ) 152 | v = get ( len ) 153 | elseif op == "\7" then -- ObjectId 154 | v = new_object_id(get(12)) 155 | if _G.usePureObjectId == true then 156 | v = tostring(v) 157 | end 158 | elseif op == "\8" then -- false 159 | local f = get ( 1 ) 160 | if f == "\0" then 161 | v = false 162 | elseif f == "\1" then 163 | v = true 164 | else 165 | error ( f:byte ( ) ) 166 | end 167 | elseif op == "\9" then -- unix time 168 | v = get(8) 169 | if v == nil then 170 | v = nil 171 | else 172 | v = Date(le_uint_to_num ( v , 1 , 8 )) 173 | end 174 | elseif op == "\10" then -- Null 175 | v = nil 176 | elseif op == "\16" then --int32 177 | v = le_int_to_num ( get ( 4 ) , 1 , 4 ) 178 | elseif op == "\18" then --int64 179 | v = le_int_to_num ( get ( 8 ) , 1 , 8 ) 180 | else 181 | error("Unknown BSON type " .. strbyte(op)) 182 | end 183 | if numerical then 184 | -- tg add 185 | -- t [ tonumber ( e_name ) ] = v 186 | -- for lua array, start from 1, always +1 187 | t[tonumber(e_name) + 1] = v 188 | else 189 | t[e_name] = v 190 | end 191 | 192 | -- Check for special universal map 193 | if e_name == "_keys" then 194 | hk = v 195 | elseif e_name == "_vals" then 196 | hv = v 197 | else 198 | ho = true 199 | end 200 | end 201 | 202 | if not ho and hk and hv then 203 | t = {} 204 | for i = 1, #hk do 205 | t[hk[i]] = hv[i] 206 | end 207 | end 208 | 209 | return t 210 | end 211 | 212 | local function from_bson(get) 213 | local t = read_document(get, false) 214 | return t 215 | end 216 | 217 | local to_bson 218 | local function pack(k, v) 219 | local ot = type(v) 220 | local mt = getmetatable(v) 221 | 222 | if ot == "number" then 223 | if v ~= 0 and v * 2 == v then 224 | if v == math.huge then 225 | return "\001" .. k .. "\000\000\000\000\000\000\000\240\127" 226 | elseif v == -math.huge then 227 | return "\001" .. k .. "\000\000\000\000\000\000\000\240\255" 228 | else 229 | return "\001" .. k .. "\000\001\000\000\000\000\000\240\127" 230 | end 231 | end 232 | if math.floor(v) ~= v then 233 | return "\1" .. k .. "\0" .. to_double(v) 234 | elseif v > 2147483647 or v < -2147483648 then 235 | return to_int64(k, v) 236 | else 237 | return to_int32(k, v) 238 | end 239 | 240 | elseif ot == "nil" then 241 | return "\10" .. k .. "\0" 242 | elseif ot == "string" then 243 | return "\2" .. k .. "\0" .. num_to_le_uint(#v + 1) .. v .. "\0" 244 | elseif ot == "boolean" then 245 | if v == false then 246 | return "\8" .. k .. "\0\0" 247 | else 248 | return "\8" .. k .. "\0\1" 249 | end 250 | elseif mt == object_id_mt then 251 | return "\7" .. k .. "\0" .. v.id 252 | elseif mt == bit32_meta then 253 | return to_int32(k, v.value) 254 | elseif mt == bit64_meta then 255 | return to_int64(k, v.value) 256 | elseif mt == date_meta then 257 | return to_date(k, v.value) 258 | elseif ot == "table" then 259 | local doc, array = to_bson(v) 260 | if array then 261 | return "\4" .. k .. "\0" .. doc 262 | else 263 | return "\3" .. k .. "\0" .. doc 264 | end 265 | else 266 | error("Failure converting " .. ot .. ": " .. tostring(v)) 267 | end 268 | end 269 | 270 | function to_bson(ob) 271 | -- Find out if ob if an array; string->value map; or general table 272 | local onlyarray = true 273 | local seen_n, high_n = {}, 0 274 | local onlystring = true 275 | local isEmpty = false 276 | local i = 0 277 | for k, v in pairs(ob) do 278 | local t_k = type(k) 279 | onlystring = onlystring and (t_k == "string") 280 | if onlyarray then 281 | if t_k == "number" and k >= 0 then 282 | if k > high_n then 283 | high_n = k 284 | seen_n[k - 1] = v 285 | end 286 | else 287 | onlyarray = false 288 | end 289 | end 290 | if not onlyarray and not onlystring then break end 291 | i = i + 1 292 | end 293 | -- for empty table, we consider it as array, rather than object 294 | if i == 0 then 295 | onlystring = false 296 | isEmpty = true 297 | end 298 | 299 | local retarray, m = false, nil 300 | if onlystring then -- Do string first so the case of an empty table is done properly 301 | local r = {} 302 | for k, v in pairs(ob) do 303 | t_insert(r, pack(k, v)) 304 | end 305 | m = t_concat(r) 306 | elseif onlyarray then 307 | local r = {} 308 | 309 | local low = 0 310 | -- if seen_n [ 0 ] then low = 0 end 311 | 312 | local ordermap = true 313 | for i, v in ipairs(ob) do 314 | local vtype = type(v) 315 | if vtype ~= 'table' or #v ~= 3 or v[1] ~= '_order_' then ordermap = false; break end 316 | end 317 | if ordermap then 318 | for i, v in ipairs(ob) do 319 | if v[2] and v[3] then 320 | t_insert(r, pack(v[2], v[3])) 321 | end 322 | end 323 | if isEmpty then 324 | retarray = true 325 | end 326 | m = t_concat(r) 327 | else 328 | -- for mongo internal, array start from 0 index 329 | for i = 0, high_n - 1 do 330 | r[i] = pack(i, seen_n[i]) 331 | end 332 | 333 | -- print('low, high_n', low, high_n) 334 | m = t_concat(r, "", low, high_n - 1) 335 | retarray = true 336 | end 337 | else 338 | local ni = 1 339 | local keys, vals = {}, {} 340 | for k, v in pairs(ob) do 341 | keys[ni] = k 342 | vals[ni] = v 343 | ni = ni + 1 344 | end 345 | return to_bson({ _keys = keys, _vals = vals }) 346 | end 347 | 348 | return num_to_le_uint(#m + 4 + 1) .. m .. "\0", retarray 349 | end 350 | 351 | return { 352 | from_bson = from_bson; 353 | to_bson = to_bson; 354 | Bit32 = Bit32; 355 | Bit64 = Bit64; 356 | Date = Date; 357 | } 358 | -------------------------------------------------------------------------------- /src/collection.lua: -------------------------------------------------------------------------------- 1 | local Emitter = require('core').Emitter 2 | 3 | local Cursor = require("./cursor") 4 | 5 | 6 | local Collection = Emitter:extend() 7 | 8 | function Collection:initialize(db, collectionName) 9 | self.collectionName = collectionName 10 | self.db = db 11 | end 12 | 13 | function Collection:find(query, cb) 14 | return Cursor:new(self, query, cb) 15 | end 16 | 17 | function Collection:distinct(field, query, cb) 18 | if type(query) == "function" and not cb then 19 | cb = query 20 | query = nil 21 | end 22 | local cmd = {{"_order_", "distinct", self.collectionName}, {"_order_", "key", field} } 23 | if query then 24 | table.insert(cmd, {"_order_", "query", query}) 25 | end 26 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 27 | if err then 28 | cb(err, nil) 29 | return 30 | end 31 | if res[1]["errmsg"] or res[1]["err"] then 32 | cb(res[1]) 33 | return 34 | end 35 | cb(err, res[1].values) 36 | end, nil) 37 | end 38 | 39 | function Collection:drop(cb) 40 | local cmd = {["drop"] = self.collectionName } 41 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 42 | if err then 43 | cb(err, nil) 44 | return 45 | end 46 | if res[1]["errmsg"] or res[1]["err"] then 47 | cb(res[1]) 48 | return 49 | end 50 | cb(nil, res[1]) 51 | end, nil) 52 | end 53 | 54 | function Collection:dropIndex(index, cb) 55 | local collectionName = self.collectionName 56 | local indexes = index 57 | local cmd = { 58 | { "_order_", "dropIndexes", collectionName }, 59 | { "_order_", "index", indexes} 60 | } 61 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 62 | local result = res[1] 63 | if result.ok == 1 then 64 | cb(err, result) 65 | else 66 | cb(err or result) 67 | end 68 | end, nil) 69 | end 70 | 71 | function Collection:dropIndexes(cb) 72 | local collectionName = self.collectionName 73 | local indexes = "*" 74 | local cmd = { 75 | { "_order_", "dropIndexes", collectionName }, 76 | { "_order_", "index", indexes} 77 | } 78 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 79 | local result = res[1] 80 | if result.ok == 1 then 81 | cb(err, result) 82 | else 83 | cb(err or result) 84 | end 85 | end, nil) 86 | end 87 | 88 | function Collection:ensureIndex(index, cb) 89 | self:createIndex(index, cb) 90 | end 91 | 92 | function Collection:createIndex(index, options, cb) 93 | local collectionName = self.collectionName 94 | local indexes 95 | local name = "" 96 | 97 | if type(options) == "function" then 98 | cb = options 99 | options = {} 100 | end 101 | 102 | if type(index) == "string" then 103 | indexes = index 104 | else 105 | if #index > 0 then 106 | for k,v in pairs(index) do 107 | if type(k) == "number" then 108 | k = v[2] 109 | end 110 | name = name .. "_" .. k 111 | end 112 | else 113 | for k,v in pairs(index) do 114 | name = name .. "_" .. k 115 | end 116 | end 117 | local indexElem = { key = index, name = name } 118 | for k, v in pairs(options) do 119 | indexElem[k] = v 120 | end 121 | indexes = {indexElem} 122 | end 123 | 124 | local cmd = { 125 | { "_order_", "createIndexes", collectionName }, 126 | { "_order_", "indexes", indexes} 127 | } 128 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 129 | local result = res[1] 130 | if result.ok == 1 then 131 | cb(err, result) 132 | else 133 | cb(err or result) 134 | end 135 | end, nil) 136 | end 137 | 138 | function Collection:getIndex(cb) 139 | local collectionName = self.collectionName 140 | local indexes = "*" 141 | local cmd = { 142 | ["listIndexes"] = collectionName 143 | } 144 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 145 | local result = res[1] 146 | if result.ok == 1 then 147 | cb(err, result) 148 | else 149 | cb(err or result) 150 | end 151 | end, nil) 152 | end 153 | 154 | function Collection:findAndModify(query, update, cb) 155 | local cursor = Cursor:new(self,query) 156 | return cursor:update(update, cb) 157 | end 158 | 159 | function Collection:findOne(query, cb) 160 | assert(cb and type(cb) == "function", "Callback should pass as 2nd param.") 161 | return Cursor:new(self, query):limit(1):exec(function (err, res) 162 | if err then 163 | cb(err) 164 | return false 165 | else 166 | cb(nil, res[1]) 167 | end 168 | end) 169 | end 170 | 171 | function Collection:insert(doc, cb) 172 | self.db:insert(self.collectionName, doc, nil, cb) 173 | end 174 | 175 | function Collection:remove(query, cb) 176 | local cursor = Cursor:new(self, query) 177 | return cursor:remove(cb) 178 | end 179 | 180 | function Collection:renameCollection(newName, cb) 181 | local oldCollectionName = self.db.db .. "." ..self.collectionName .. "\0" 182 | local newCollectionName = self.db.db .. "." ..newName .. "\0" 183 | local cmd = { 184 | {"_order_", "renameCollection", oldCollectionName}, 185 | {"_order_", "to", newCollectionName } 186 | } 187 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 188 | cb(err, res) 189 | end, nil) 190 | end 191 | 192 | return Collection 193 | -------------------------------------------------------------------------------- /src/cursor.lua: -------------------------------------------------------------------------------- 1 | local Emitter = require('core').Emitter 2 | local ObjectId= require ( "./objectId" ).ObjectId 3 | local Cursor = Emitter:extend() 4 | 5 | function Cursor:initialize(collection, query, cb) 6 | self.db = collection.db 7 | self.collectionName = collection.collectionName 8 | self.collection = collection 9 | self.query = query or {} 10 | if cb and type(cb) == "function" then 11 | self.cb = cb 12 | self:_exec() 13 | end 14 | end 15 | 16 | function Cursor:remove(cb) 17 | self.action = "REMOVE" 18 | if cb and type(cb) == "function" then 19 | self.cb = cb 20 | self:_exec() 21 | end 22 | return self 23 | end 24 | 25 | function Cursor:find(query, cb) 26 | self.query = query or {} 27 | if cb and type(cb) == "function" then 28 | self.cb = cb 29 | self:_exec() 30 | end 31 | return self 32 | end 33 | 34 | function Cursor:fields(fields, cb) 35 | self._fields = fields or {} 36 | if cb and type(cb) == "function" then 37 | self.cb = cb 38 | self:_exec() 39 | end 40 | return self 41 | end 42 | 43 | function Cursor:skip(skip, cb) 44 | if skip then 45 | self._skip = skip 46 | end 47 | if cb and type(cb) == "function" then 48 | self.cb = cb 49 | self:_exec() 50 | end 51 | return self 52 | end 53 | 54 | function Cursor:limit(limit, cb) 55 | if limit then 56 | self._limit = limit 57 | end 58 | if cb and type(cb) == "function" then 59 | self.cb = cb 60 | self:_exec() 61 | end 62 | return self 63 | end 64 | 65 | function Cursor:sort(doc, cb) 66 | if doc then 67 | self._sort = doc 68 | end 69 | if cb and type(cb) == "function" then 70 | self.cb = cb 71 | self:_exec() 72 | end 73 | return self 74 | end 75 | 76 | function Cursor:count(cb) 77 | self.action = "COUNT" 78 | if cb and type(cb) == "function" then 79 | self.cb = cb 80 | self:_exec() 81 | end 82 | end 83 | 84 | function Cursor:update(update, cb) 85 | self.action = "UPDATE" 86 | if update then 87 | self._update = update 88 | end 89 | if cb and type(cb) == "function" then 90 | self.cb = cb 91 | self:_exec() 92 | end 93 | return self 94 | end 95 | 96 | function Cursor:_exec() 97 | if self.action == "COUNT" then 98 | local cmd = {{"_order_", "count", self.collectionName}, {"_order_", "query", self.query} } 99 | self.db:query("$cmd", cmd, nil, nil, 1, function(err, res) 100 | if err then 101 | self.cb(err, nil) 102 | return 103 | end 104 | if res[1]["errmsg"] or res[1]["err"] then 105 | self.cb(res[1]) 106 | return 107 | end 108 | self.cb(err, res[1].n) 109 | end) 110 | return self 111 | end 112 | if self._sort then 113 | if self.query and not self.query["$query"] or not self.query then 114 | self.query = {{"_order_", "$query", self.query}, {"_order_", "$orderby", self._sort} } 115 | end 116 | end 117 | self.db:find(self.collectionName, self.query, self._fields, self._skip, self._limit, function(err, res) 118 | if self._update and self.action == "UPDATE" then 119 | if next(res) == nil then 120 | p("No result match: ", self.query, " for update") 121 | self.cb(err, {}) 122 | else 123 | local ids = {} 124 | for _, v in pairs(res) do 125 | table.insert(ids, {_id = ObjectId(v._id)}) 126 | end 127 | self.db:update(self.collectionName, {["$or"]=ids}, self._update, nil, 1, self.cb) 128 | end 129 | elseif self.action == "REMOVE" then 130 | if next(res) == nil then 131 | p("No result match: ", self.query, " for remove") 132 | self.cb(err, {}) 133 | else 134 | local ids = {} 135 | for _, v in pairs(res) do 136 | table.insert(ids, {_id = ObjectId(v._id)}) 137 | end 138 | self.db:remove(self.collectionName, {["$or"]=ids}, nil, self.cb) 139 | end 140 | else 141 | self.cb(err, res) 142 | end 143 | end) 144 | end 145 | 146 | function Cursor:exec(cb) 147 | assert(type(cb) == "function") 148 | if cb and type(cb) == "function" then 149 | self.cb = cb 150 | self:_exec() 151 | end 152 | return self 153 | end 154 | 155 | return Cursor -------------------------------------------------------------------------------- /src/get.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 下午2:38 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | local strsub = string.sub 8 | local t_insert = table.insert 9 | local t_concat = table.concat 10 | 11 | local function get_from_string ( s , i ) 12 | i = i or 1 13 | return function ( n ) 14 | if not n then -- Rest of string 15 | n = #s - i + 1 16 | end 17 | i = i + n 18 | assert ( i-1 <= #s , "Unable to read enough characters" ) 19 | return strsub ( s , i-n , i-1 ) 20 | end , function ( new_i ) 21 | if new_i then i = new_i end 22 | return i 23 | end 24 | end 25 | 26 | local function string_to_array_of_chars ( s ) 27 | local t = { } 28 | for i = 1 , #s do 29 | t [ i ] = strsub ( s , i , i ) 30 | end 31 | return t 32 | end 33 | 34 | local function read_terminated_string ( get , terminators ) 35 | local terminators = string_to_array_of_chars ( terminators or "\0" ) 36 | local str = { } 37 | local found = 0 38 | while found < #terminators do 39 | local c = get ( 1 ) 40 | if c == terminators [ found + 1 ] then 41 | found = found + 1 42 | else 43 | found = 0 44 | end 45 | t_insert ( str , c ) 46 | end 47 | return t_concat ( str , "" , 1 , #str - #terminators ) 48 | end 49 | 50 | return { 51 | get_from_string = get_from_string ; 52 | read_terminated_string = read_terminated_string ; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/init.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 上午12:18 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | Emitter = require('core').Emitter 8 | local net = require("net") 9 | local ll = require ( "./utils" ) 10 | local num_to_le_uint = ll.num_to_le_uint 11 | local num_to_le_int = ll.num_to_le_int 12 | local le_uint_to_num = ll.le_uint_to_num 13 | local le_bpeek = ll.le_bpeek 14 | local bson = require("./bson") 15 | local to_bson = bson.to_bson 16 | local from_bson = bson.from_bson 17 | 18 | local getlib = require ( "./get" ) 19 | local get_from_string = getlib.get_from_string 20 | local ObjectId = require "./objectId" 21 | 22 | local Collection = require("./collection") 23 | 24 | Mongo = Emitter:extend() 25 | 26 | _id = 0 27 | function Mongo:initialize(options) 28 | options = options or {} 29 | self.options = options 30 | _id = _id + 1 31 | p("[Info] - Create connection.......") 32 | self.port = options.port or 27017 33 | self.host = options.host or '127.0.0.1' 34 | assert(options.db or options.dbname, "Should specify a database name.") 35 | self.db = options.db or options.dbname 36 | self.requestId = 0 37 | self.queues = {} 38 | self.callbacks = {} 39 | self:connect() 40 | end 41 | 42 | local opcodes = { 43 | REPLY = 1 ; 44 | MSG = 1000 ; 45 | UPDATE = 2001 ; 46 | INSERT = 2002 ; 47 | QUERY = 2004 ; 48 | GET_MORE = 2005 ; 49 | DELETE = 2006 ; 50 | KILL_CURSORS = 2007 ; 51 | } 52 | 53 | 54 | local function composeMessage (reqId, resId, opcode, message) 55 | local reqId = num_to_le_uint(reqId) 56 | resId = resId or "\255\255\255\255" 57 | opcode = num_to_le_uint( assert( opcodes[opcode] ) ) 58 | return num_to_le_uint (#message + 16 ) .. reqId .. resId .. opcode .. message 59 | end 60 | 61 | local function getCollectionName (self, collection) 62 | local db = self.db 63 | return db .. "." .. collection .. "\0" 64 | end 65 | 66 | 67 | local function parseMsgHeader(data) 68 | local header = assert(get_from_string(data)(16)) 69 | local length = le_uint_to_num(header, 1, 4) 70 | local reqId = le_uint_to_num(header, 5,8) 71 | local resId = le_uint_to_num(header, 9, 12) 72 | local opcode = le_uint_to_num(header, 13, 16) 73 | return length, reqId, resId, opcode 74 | end 75 | 76 | local function parseData ( data ) 77 | local offset_i = 0 78 | local length , reqId , resId , opcode = parseMsgHeader( data ) 79 | assert ( opcode == opcodes.REPLY ) 80 | local data = assert ( data ) 81 | local get = get_from_string ( data ) 82 | local abadon = get(16) 83 | local responseFlags = get ( 4 ) 84 | local cursorId = le_uint_to_num(get ( 8 )) 85 | local tags = { } 86 | tags.startingFrom = le_uint_to_num ( get ( 4 ) ) 87 | tags.numberReturned = le_uint_to_num ( get ( 4 ) ) 88 | tags.CursorNotFound = le_bpeek ( responseFlags , 0 ) 89 | tags.QueryFailure = le_bpeek ( responseFlags , 1 ) 90 | tags.ShardConfigStale = le_bpeek ( responseFlags , 2 ) 91 | tags.AwaitCapable = le_bpeek ( responseFlags , 3 ) 92 | local res = {} 93 | for i = 1 , tags.numberReturned do 94 | res[ i + offset_i ] = from_bson ( get ) 95 | end 96 | return resId, cursorId , res, tags, length, reqId 97 | end 98 | 99 | function Mongo:command (opcode, message, callback, collection, query) 100 | self.requestId = self.requestId + 1 101 | local message = composeMessage(self.requestId, nil, opcode, message) 102 | local requestId = self.requestId 103 | self.queues[requestId] = { 104 | ["message"] = message, 105 | ["requestId"] = self.requestId, 106 | ["opcode"] = opcode, 107 | ["query"] = query, 108 | ["collection"] = collection, 109 | ["callback"] = (callback or function() p("-------") end) 110 | } 111 | local currentQueue = self.queues[requestId] 112 | self.socket:write(currentQueue["message"], "hex") 113 | local opcode = currentQueue["opcode"] 114 | -- Insert Update, and Delete method has no response 115 | if opcode == "UPDATE" or opcode == "INSERT" or opcode == "DELETE" then 116 | local query = currentQueue.query 117 | local collection = currentQueue.collection 118 | self:getLastError(function(error) 119 | if error then 120 | currentQueue.callback(error) 121 | self.queues[requestId] = nil 122 | else 123 | self:find(collection, query, nil, nil, nil, function(err, result) 124 | currentQueue.callback(nil, result) 125 | self.queues[requestId] = nil 126 | end) 127 | end 128 | end) 129 | end 130 | end 131 | 132 | function Mongo:getLastError(cb) 133 | self:query("$cmd", {getLastError = true}, nil, nil, 1, function(err, res) 134 | if res[1].err or res[1]["errmsg"] then 135 | cb(res[1]) 136 | else 137 | cb() 138 | end 139 | end, nil) 140 | end 141 | 142 | function Mongo:query ( collection , query , fields , skip , limit , callback, options ) 143 | skip = skip or 0 144 | local flags = 0 145 | if options then 146 | flags = 2^1*( options.TailableCursor and 1 or 0 ) 147 | + 2^2*( options.SlaveOk and 1 or 0 ) 148 | + 2^3*( options.OplogReplay and 1 or 0 ) 149 | + 2^4*( options.NoCursorTimeout and 1 or 0 ) 150 | + 2^5*( options.AwaitData and 1 or 0 ) 151 | + 2^6*( options.Exhaust and 1 or 0 ) 152 | + 2^7*( options.Partial and 1 or 0 ) 153 | end 154 | query = to_bson(query) 155 | if fields then 156 | fields = to_bson(fields) 157 | else 158 | fields = "" 159 | end 160 | local m = num_to_le_uint ( flags ) .. getCollectionName ( self , collection ) 161 | .. num_to_le_uint ( skip ) .. num_to_le_int ( limit or 0 ) 162 | .. query .. fields 163 | self:command("QUERY", m, callback) 164 | end 165 | 166 | function Mongo:update (collection, query, update, upsert, single, callback) 167 | local flags = 2^0*( upsert and 1 or 0 ) + 2^1*( single and 0 or 1 ) 168 | local queryBson = to_bson(query) 169 | update = to_bson(update) 170 | local m = "\0\0\0\0" .. getCollectionName ( self , collection ) .. num_to_le_uint ( flags ) .. queryBson .. update 171 | self:command("UPDATE", m, callback, collection, query) 172 | end 173 | 174 | function Mongo:insert (collection, docs, continue, callback) 175 | if not(#docs >= 1) and docs then 176 | docs = {docs} 177 | end 178 | assert(#docs >= 1) 179 | local flags = 2^0*( continue and 1 or 0 ) 180 | local t = {} 181 | local ids = {} 182 | for i , v in ipairs ( docs ) do 183 | if not v._id then 184 | v._id = ObjectId.new() 185 | end 186 | table.insert(ids, v._id) 187 | t [ i ] = to_bson ( v ) 188 | end 189 | local m = num_to_le_uint ( flags ) .. getCollectionName ( self , collection ) .. table.concat( t ) 190 | 191 | local query = {_id = {["$in"] = ids}} 192 | 193 | self:command("INSERT", m, callback, collection, query) 194 | end 195 | 196 | function Mongo:remove(collection, query, singleRemove, callback) 197 | local flags = 2^0*( singleRemove and 1 or 0 ) 198 | local queryBson = to_bson(query) 199 | local m = "\0\0\0\0" .. getCollectionName ( self , collection ) .. num_to_le_uint ( flags ) .. queryBson 200 | self:command("DELETE", m, callback, collection, query) 201 | end 202 | 203 | function Mongo:findOne(collection, query, fields, skip, cb) 204 | local function callback(err, res) 205 | if res and #res >= 1 then 206 | cb(err, res[1]) 207 | else 208 | cb(err, nil) 209 | end 210 | end 211 | self:query(collection, query, fields, skip, 1, callback) 212 | end 213 | 214 | function Mongo:find(collection, query, fields, skip, limit, callback) 215 | self:query(collection, query, fields, skip, limit, callback) 216 | end 217 | 218 | function Mongo:count(collection, query, callback) 219 | local function cb(err, r) 220 | callback(err, #r) 221 | end 222 | self:query(collection, query, nil, nil, nil, cb) 223 | end 224 | 225 | function Mongo:_onData(data) 226 | local stringToParse = "" 227 | if #self.tempData > 0 then 228 | stringToParse = self.tempData .. data 229 | else 230 | stringToParse = data 231 | end 232 | 233 | local status, docLength = pcall(function() return parseMsgHeader(stringToParse) end) 234 | if not status then 235 | return 236 | end 237 | 238 | if docLength == #stringToParse then 239 | local reqId, cursorId, res, tags = parseData(stringToParse) 240 | self.tempData = "" 241 | self.queues[reqId].callback(nil, res, tags, cursorId) 242 | self.queues[reqId] = nil 243 | elseif docLength > #stringToParse then 244 | self.tempData = stringToParse 245 | elseif docLength < #stringToParse then 246 | local stringToParseSub = stringToParse:sub(1, docLength) 247 | local reqId, cursorId, res, tags = parseData(stringToParseSub) 248 | self.queues[reqId].callback(nil, res, tags, cursorId) 249 | self.queues[reqId] = nil 250 | self.tempData = stringToParse:sub(docLength + 1) 251 | self:_onData("") 252 | end 253 | end 254 | 255 | function Mongo:connect() 256 | local socket 257 | socket = net.createConnection(self.port, self.host) 258 | socket:on("connect", function() 259 | p("[Info] - Database is connected.......") 260 | self.tempData = "" 261 | socket:on("data", function(data) 262 | self:_onData(data) 263 | end) 264 | 265 | socket:on('end', function() 266 | socket:destroy() 267 | self:emit("end") 268 | self:emit("close") 269 | p('client end') 270 | end) 271 | 272 | socket:on("error", function(err) 273 | p("Error!", err) 274 | self:emit("error", err) 275 | end) 276 | 277 | self:emit("connect") 278 | end) 279 | socket:on("error", function (code) 280 | if(code == "ECONNREFUSED") then 281 | self:emit("error", code) 282 | print("\x1b[37;41m Database connection failed. \x1b[0m") 283 | end 284 | end) 285 | self.socket = socket 286 | end 287 | 288 | function Mongo:collection(collectionName) 289 | return Collection:new(self, collectionName) 290 | end 291 | 292 | Mongo.ObjectId = ObjectId 293 | Mongo.Bit32 = bson.Bit32 294 | Mongo.Bit64 = bson.Bit64 295 | Mongo.Date = bson.Date 296 | 297 | return Mongo 298 | -------------------------------------------------------------------------------- /src/md5.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 下午2:51 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | --------------------------------------------------------- 8 | --- md5 library in pure lua 9 | --- credits go to the original creator 10 | --------------------------------------------------------- 11 | 12 | -- Luvit libraries 13 | local math = require('math') 14 | 15 | math.mod = function(n, v) 16 | return n % v 17 | end 18 | 19 | local string = require('string') 20 | local table = require('table') 21 | 22 | -- Original code only below this line 23 | --------------------------------------------------------- 24 | 25 | local md5 = { 26 | _VERSION = "md5.lua 0.5.0", 27 | _DESCRIPTION = "MD5 computation in Lua (5.1)", 28 | _URL = "https://github.com/kikito/md5.lua", 29 | _LICENSE = [[ 30 | MIT LICENSE 31 | 32 | Copyright (c) 2013 Enrique Garc�a Cota + Adam Baldwin + hanzao + Equi 4 Software 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a 35 | copy of this software and associated documentation files (the 36 | "Software"), to deal in the Software without restriction, including 37 | without limitation the rights to use, copy, modify, merge, publish, 38 | distribute, sublicense, and/or sell copies of the Software, and to 39 | permit persons to whom the Software is furnished to do so, subject to 40 | the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included 43 | in all copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 46 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 47 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 48 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 49 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 50 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 51 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 52 | ]] 53 | } 54 | 55 | -- bit lib implementions 56 | 57 | local floor, abs, max = math.floor, math.abs, math.max 58 | local char, byte, format, rep, sub = 59 | string.char, string.byte, string.format, string.rep, string.sub 60 | 61 | local function check_int(n) 62 | -- checking not float 63 | if(n - floor(n) > 0) then 64 | error("trying to use bitwise operation on non-integer!") 65 | end 66 | end 67 | 68 | local function tbl2number(tbl) 69 | local n = #tbl 70 | 71 | local rslt = 0 72 | local power = 1 73 | for i = 1, n do 74 | rslt = rslt + tbl[i]*power 75 | power = power*2 76 | end 77 | 78 | return rslt 79 | end 80 | 81 | local function expand(tbl_m, tbl_n) 82 | local big = {} 83 | local small = {} 84 | if(#tbl_m > #tbl_n) then 85 | big = tbl_m 86 | small = tbl_n 87 | else 88 | big = tbl_n 89 | small = tbl_m 90 | end 91 | -- expand small 92 | for i = #small + 1, #big do 93 | small[i] = 0 94 | end 95 | 96 | end 97 | 98 | local to_bits -- needs to be declared before bit_not 99 | 100 | local function bit_not(n) 101 | local tbl = to_bits(n) 102 | local size = max(#tbl, 32) 103 | for i = 1, size do 104 | if(tbl[i] == 1) then 105 | tbl[i] = 0 106 | else 107 | tbl[i] = 1 108 | end 109 | end 110 | return tbl2number(tbl) 111 | end 112 | 113 | -- defined as local above 114 | to_bits = function (n) 115 | check_int(n) 116 | if(n < 0) then 117 | -- negative 118 | return to_bits(bit_not(abs(n)) + 1) 119 | end 120 | -- to bits table 121 | local tbl = {} 122 | local cnt = 1 123 | while (n > 0) do 124 | local last = math.mod(n, 2) 125 | if(last == 1) then 126 | tbl[cnt] = 1 127 | else 128 | tbl[cnt] = 0 129 | end 130 | n = (n-last)/2 131 | cnt = cnt + 1 132 | end 133 | 134 | return tbl 135 | end 136 | 137 | local function bit_or(m, n) 138 | local tbl_m = to_bits(m) 139 | local tbl_n = to_bits(n) 140 | expand(tbl_m, tbl_n) 141 | 142 | local tbl = {} 143 | local rslt = max(#tbl_m, #tbl_n) 144 | for i = 1, rslt do 145 | if(tbl_m[i]== 0 and tbl_n[i] == 0) then 146 | tbl[i] = 0 147 | else 148 | tbl[i] = 1 149 | end 150 | end 151 | 152 | return tbl2number(tbl) 153 | end 154 | 155 | local function bit_and(m, n) 156 | local tbl_m = to_bits(m) 157 | local tbl_n = to_bits(n) 158 | expand(tbl_m, tbl_n) 159 | 160 | local tbl = {} 161 | local rslt = max(#tbl_m, #tbl_n) 162 | for i = 1, rslt do 163 | if(tbl_m[i]== 0 or tbl_n[i] == 0) then 164 | tbl[i] = 0 165 | else 166 | tbl[i] = 1 167 | end 168 | end 169 | 170 | return tbl2number(tbl) 171 | end 172 | 173 | local function bit_xor(m, n) 174 | local tbl_m = to_bits(m) 175 | local tbl_n = to_bits(n) 176 | expand(tbl_m, tbl_n) 177 | 178 | local tbl = {} 179 | local rslt = max(#tbl_m, #tbl_n) 180 | for i = 1, rslt do 181 | if(tbl_m[i] ~= tbl_n[i]) then 182 | tbl[i] = 1 183 | else 184 | tbl[i] = 0 185 | end 186 | end 187 | 188 | return tbl2number(tbl) 189 | end 190 | 191 | local function bit_rshift(n, bits) 192 | check_int(n) 193 | 194 | local high_bit = 0 195 | if(n < 0) then 196 | -- negative 197 | n = bit_not(abs(n)) + 1 198 | high_bit = 2147483648 -- 0x80000000 199 | end 200 | 201 | for i=1, bits do 202 | n = n/2 203 | n = bit_or(floor(n), high_bit) 204 | end 205 | return floor(n) 206 | end 207 | 208 | local function bit_lshift(n, bits) 209 | check_int(n) 210 | 211 | if(n < 0) then 212 | -- negative 213 | n = bit_not(abs(n)) + 1 214 | end 215 | 216 | for i=1, bits do 217 | n = n*2 218 | end 219 | return bit_and(n, 4294967295) -- 0xFFFFFFFF 220 | end 221 | 222 | -- convert little-endian 32-bit int to a 4-char string 223 | local function lei2str(i) 224 | local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end 225 | return f(0)..f(8)..f(16)..f(24) 226 | end 227 | 228 | -- convert raw string to big-endian int 229 | local function str2bei(s) 230 | local v=0 231 | for i=1, #s do 232 | v = v * 256 + byte(s, i) 233 | end 234 | return v 235 | end 236 | 237 | -- convert raw string to little-endian int 238 | local function str2lei(s) 239 | local v=0 240 | for i = #s,1,-1 do 241 | v = v*256 + byte(s, i) 242 | end 243 | return v 244 | end 245 | 246 | -- cut up a string in little-endian ints of given size 247 | local function cut_le_str(s,...) 248 | local o, r = 1, {} 249 | local args = {...} 250 | for i=1, #args do 251 | table.insert(r, str2lei(sub(s, o, o + args[i] - 1))) 252 | o = o + args[i] 253 | end 254 | return r 255 | end 256 | 257 | local swap = function (w) return str2bei(lei2str(w)) end 258 | 259 | local function hex2binaryaux(hexval) 260 | return char(tonumber(hexval, 16)) 261 | end 262 | 263 | local function hex2binary(hex) 264 | local result, _ = hex:gsub('..', hex2binaryaux) 265 | return result 266 | end 267 | 268 | -- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh) 269 | -- 10/02/2001 jcw@equi4.com 270 | 271 | local FF = 0xffffffff 272 | local CONSTS = { 273 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 274 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 275 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 276 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 277 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 278 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 279 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 280 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 281 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 282 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 283 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 284 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 285 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 286 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 287 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 288 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, 289 | 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 290 | } 291 | 292 | local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end 293 | local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end 294 | local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end 295 | local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end 296 | local z=function (f,a,b,c,d,x,s,ac) 297 | a=bit_and(a+f(b,c,d)+x+ac,FF) 298 | -- be *very* careful that left shift does not cause rounding! 299 | return bit_or(bit_lshift(bit_and(a,bit_rshift(FF,s)),s),bit_rshift(a,32-s))+b 300 | end 301 | 302 | local function transform(A,B,C,D,X) 303 | local a,b,c,d=A,B,C,D 304 | local t=CONSTS 305 | 306 | a=z(f,a,b,c,d,X[ 0], 7,t[ 1]) 307 | d=z(f,d,a,b,c,X[ 1],12,t[ 2]) 308 | c=z(f,c,d,a,b,X[ 2],17,t[ 3]) 309 | b=z(f,b,c,d,a,X[ 3],22,t[ 4]) 310 | a=z(f,a,b,c,d,X[ 4], 7,t[ 5]) 311 | d=z(f,d,a,b,c,X[ 5],12,t[ 6]) 312 | c=z(f,c,d,a,b,X[ 6],17,t[ 7]) 313 | b=z(f,b,c,d,a,X[ 7],22,t[ 8]) 314 | a=z(f,a,b,c,d,X[ 8], 7,t[ 9]) 315 | d=z(f,d,a,b,c,X[ 9],12,t[10]) 316 | c=z(f,c,d,a,b,X[10],17,t[11]) 317 | b=z(f,b,c,d,a,X[11],22,t[12]) 318 | a=z(f,a,b,c,d,X[12], 7,t[13]) 319 | d=z(f,d,a,b,c,X[13],12,t[14]) 320 | c=z(f,c,d,a,b,X[14],17,t[15]) 321 | b=z(f,b,c,d,a,X[15],22,t[16]) 322 | 323 | a=z(g,a,b,c,d,X[ 1], 5,t[17]) 324 | d=z(g,d,a,b,c,X[ 6], 9,t[18]) 325 | c=z(g,c,d,a,b,X[11],14,t[19]) 326 | b=z(g,b,c,d,a,X[ 0],20,t[20]) 327 | a=z(g,a,b,c,d,X[ 5], 5,t[21]) 328 | d=z(g,d,a,b,c,X[10], 9,t[22]) 329 | c=z(g,c,d,a,b,X[15],14,t[23]) 330 | b=z(g,b,c,d,a,X[ 4],20,t[24]) 331 | a=z(g,a,b,c,d,X[ 9], 5,t[25]) 332 | d=z(g,d,a,b,c,X[14], 9,t[26]) 333 | c=z(g,c,d,a,b,X[ 3],14,t[27]) 334 | b=z(g,b,c,d,a,X[ 8],20,t[28]) 335 | a=z(g,a,b,c,d,X[13], 5,t[29]) 336 | d=z(g,d,a,b,c,X[ 2], 9,t[30]) 337 | c=z(g,c,d,a,b,X[ 7],14,t[31]) 338 | b=z(g,b,c,d,a,X[12],20,t[32]) 339 | 340 | a=z(h,a,b,c,d,X[ 5], 4,t[33]) 341 | d=z(h,d,a,b,c,X[ 8],11,t[34]) 342 | c=z(h,c,d,a,b,X[11],16,t[35]) 343 | b=z(h,b,c,d,a,X[14],23,t[36]) 344 | a=z(h,a,b,c,d,X[ 1], 4,t[37]) 345 | d=z(h,d,a,b,c,X[ 4],11,t[38]) 346 | c=z(h,c,d,a,b,X[ 7],16,t[39]) 347 | b=z(h,b,c,d,a,X[10],23,t[40]) 348 | a=z(h,a,b,c,d,X[13], 4,t[41]) 349 | d=z(h,d,a,b,c,X[ 0],11,t[42]) 350 | c=z(h,c,d,a,b,X[ 3],16,t[43]) 351 | b=z(h,b,c,d,a,X[ 6],23,t[44]) 352 | a=z(h,a,b,c,d,X[ 9], 4,t[45]) 353 | d=z(h,d,a,b,c,X[12],11,t[46]) 354 | c=z(h,c,d,a,b,X[15],16,t[47]) 355 | b=z(h,b,c,d,a,X[ 2],23,t[48]) 356 | 357 | a=z(i,a,b,c,d,X[ 0], 6,t[49]) 358 | d=z(i,d,a,b,c,X[ 7],10,t[50]) 359 | c=z(i,c,d,a,b,X[14],15,t[51]) 360 | b=z(i,b,c,d,a,X[ 5],21,t[52]) 361 | a=z(i,a,b,c,d,X[12], 6,t[53]) 362 | d=z(i,d,a,b,c,X[ 3],10,t[54]) 363 | c=z(i,c,d,a,b,X[10],15,t[55]) 364 | b=z(i,b,c,d,a,X[ 1],21,t[56]) 365 | a=z(i,a,b,c,d,X[ 8], 6,t[57]) 366 | d=z(i,d,a,b,c,X[15],10,t[58]) 367 | c=z(i,c,d,a,b,X[ 6],15,t[59]) 368 | b=z(i,b,c,d,a,X[13],21,t[60]) 369 | a=z(i,a,b,c,d,X[ 4], 6,t[61]) 370 | d=z(i,d,a,b,c,X[11],10,t[62]) 371 | c=z(i,c,d,a,b,X[ 2],15,t[63]) 372 | b=z(i,b,c,d,a,X[ 9],21,t[64]) 373 | 374 | return A+a,B+b,C+c,D+d 375 | end 376 | 377 | ---------------------------------------------------------------- 378 | 379 | function md5.sumhexa(s) 380 | local msgLen = #s 381 | local padLen = 56 - msgLen % 64 382 | 383 | if msgLen % 64 > 56 then padLen = padLen + 64 end 384 | 385 | if padLen == 0 then padLen = 64 end 386 | 387 | s = s .. char(128) .. rep(char(0),padLen-1) .. lei2str(8*msgLen) .. lei2str(0) 388 | 389 | assert(#s % 64 == 0) 390 | 391 | local t = CONSTS 392 | local a,b,c,d = t[65],t[66],t[67],t[68] 393 | 394 | for i=1,#s,64 do 395 | local X = cut_le_str(sub(s,i,i+63),4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4) 396 | assert(#X == 16) 397 | X[0] = table.remove(X,1) -- zero based! 398 | a,b,c,d = transform(a,b,c,d,X) 399 | end 400 | 401 | return format("%08x%08x%08x%08x",swap(a),swap(b),swap(c),swap(d)) 402 | end 403 | 404 | function md5.sum(s) 405 | return hex2binary(md5.sumhexa(s)) 406 | end 407 | 408 | return md5 409 | 410 | -------------------------------------------------------------------------------- /src/objectId.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 下午2:39 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | 8 | local setmetatable = setmetatable 9 | local strbyte, strchar = string.byte, string.char 10 | local strformat = string.format 11 | local strsub = string.sub 12 | local t_insert = table.insert 13 | local t_concat = table.concat 14 | local openssl = require("openssl") 15 | local ll = require ( "./utils" ) 16 | local num_to_le_uint = ll.num_to_le_uint 17 | local num_to_be_uint = ll.num_to_be_uint 18 | 19 | local object_id_mt = { 20 | __tostring = function ( ob ) 21 | local t = { } 22 | for i = 1 , 12 do 23 | t_insert ( t , strformat ( "%02x" , strbyte ( ob.id , i , i ) ) ) 24 | end 25 | --return "ObjectId('" .. t_concat ( t ) .. "')" 26 | return t_concat ( t ) 27 | end ; 28 | __eq = function ( a , b ) return a.id == b.id end ; 29 | } 30 | 31 | local machineid = assert ( io.popen ( "uname -n" ) ):read ( "*l" ) 32 | machineid = openssl.digest.digest("md5", machineid):sub ( 1 , 3 ) 33 | 34 | local pid = process.pid 35 | pid = num_to_le_uint ( pid , 2 ) 36 | 37 | local inc = math.floor(math.random() * 1000) 38 | local function generate_id () 39 | inc = inc + 1 40 | -- "A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a 3-byte counter. Note that the timestamp and counter fields must be stored big endian unlike the rest of BSON" 41 | return num_to_be_uint ( os.time ( ) , 4 ) .. machineid .. pid .. num_to_be_uint ( inc , 3 ) 42 | 43 | end 44 | 45 | local function new_object_id ( str ) 46 | if str then 47 | assert ( #str == 12 ) 48 | else 49 | str = generate_id ( ) 50 | end 51 | return setmetatable ( { id = str } , object_id_mt ) 52 | end 53 | 54 | local function makeObjectId (str) 55 | if str then 56 | str = tostring(str) 57 | if not(type(str) == 'string' and #str == 24) then 58 | str = object_id_mt.__tostring({id = str}) 59 | end 60 | assert(type(str) == 'string' and #str == 24, 'wrong ObjectId string') 61 | local t 62 | local time = tonumber(strsub(str, 1, 8), 16) 63 | local part1 = num_to_be_uint ( time , 4 ) 64 | local r_t = {} 65 | for i=4, 8 do 66 | t = tonumber(strsub(str, 2*i+1, 2*i+2), 16) 67 | t_insert(r_t, strchar(t)) 68 | end 69 | local part2 = t_concat(r_t) 70 | local part3 = num_to_be_uint(tonumber(strsub(str, 19, 24), 16), 3) 71 | 72 | local binstr = part1 .. part2 .. part3 73 | return setmetatable({id=binstr}, object_id_mt) 74 | else 75 | -- create new object id 76 | return new_object_id(); 77 | end 78 | end 79 | 80 | return { 81 | ObjectId = makeObjectId, 82 | new = makeObjectId; 83 | metatable = object_id_mt ; 84 | } 85 | -------------------------------------------------------------------------------- /src/utils.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/20 下午2:05 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | -- Library for reading low level data 8 | 9 | local assert = assert 10 | local unpack = unpack 11 | local floor = math.floor 12 | local strbyte , strchar = string.byte , string.char 13 | 14 | 15 | local le_uint_to_num = function ( s , i , j ) 16 | i , j = i or 1 , j or #s 17 | local b = { strbyte ( s , i , j ) } 18 | local n = 0 19 | for i=#b , 1 , -1 do 20 | n = n*2^8 + b [ i ] 21 | end 22 | return n 23 | end 24 | local le_int_to_num = function ( s , i , j ) 25 | i , j = i or 1 , j or #s 26 | local n = le_uint_to_num ( s , i , j ) 27 | local overflow = 2^(8*(j-i) + 7) 28 | if n > overflow then 29 | n = ( n % overflow ) - overflow 30 | end 31 | return n 32 | end 33 | local num_to_le_uint = function ( n , bytes ) 34 | bytes = bytes or 4 35 | local b = { } 36 | for i=1 , bytes do 37 | b [ i ] , n = n % 2^8 , floor ( n / 2^8 ) 38 | end 39 | return strchar ( unpack ( b ) ) 40 | end 41 | local num_to_le_int = function ( n , bytes ) 42 | bytes = bytes or 4 43 | if n < 0 then -- Converted to unsigned. 44 | n = 2^(8*bytes) + n 45 | end 46 | return num_to_le_uint ( n , bytes ) 47 | end 48 | 49 | local be_uint_to_num = function ( s , i , j ) 50 | i , j = i or 1 , j or #s 51 | local b = { strbyte ( s , i , j ) } 52 | local n = 0 53 | for i=1 , #b do 54 | n = n*2^8 + b [ i ] 55 | end 56 | return n 57 | end 58 | local num_to_be_uint = function ( n , bytes ) 59 | bytes = bytes or 4 60 | local b = { } 61 | for i=bytes , 1 , -1 do 62 | b [ i ] , n = n % 2^8 , floor ( n / 2^8 ) 63 | end 64 | assert ( n == 0 ) 65 | return strchar ( unpack ( b ) ) 66 | end 67 | 68 | -- Returns (as a number); bits i to j (indexed from 0) 69 | local extract_bits = function ( s , i , j ) 70 | j = j or i 71 | local i_byte = floor ( i / 8 ) + 1 72 | local j_byte = floor ( j / 8 ) + 1 73 | 74 | local n = be_uint_to_num ( s , i_byte , j_byte ) 75 | n = n % 2^( j_byte*8 - i ) 76 | n = floor ( n / 2^( (-(j+1) ) % 8 ) ) 77 | return n 78 | end 79 | 80 | -- Look at ith bit in given string (indexed from 0) 81 | -- Returns boolean 82 | local le_bpeek = function ( s , bitnum ) 83 | local byte = floor ( bitnum / 8 ) + 1 84 | local bit = bitnum % 8 85 | local char = strbyte ( s , byte ) 86 | return floor ( ( char % 2^(bit+1) ) / 2^bit ) == 1 87 | end 88 | -- Test with: 89 | --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 ) 90 | 91 | local be_bpeek = function ( s , bitnum ) 92 | local byte = floor ( bitnum / 8 ) + 1 93 | local bit = 7-bitnum % 8 94 | local char = strbyte ( s , byte ) 95 | return floor ( ( char % 2^(bit+1) ) / 2^bit ) == 1 96 | end 97 | -- Test with: 98 | --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 ) 99 | 100 | 101 | local hasffi , ffi = pcall ( require , "ffi" ) 102 | local to_double , from_double 103 | do 104 | local s , e , d 105 | if hasffi then 106 | d = ffi.new ( "double[1]" ) 107 | else 108 | -- Can't use with to_double as we can't strip debug info :( 109 | d = string.dump ( loadstring ( [[return 523123.123145345]] ) ) 110 | s , e = d:find ( "\3\54\208\25\126\204\237\31\65" ) 111 | s = d:sub ( 1 , s ) 112 | e = d:sub ( e+1 , -1 ) 113 | end 114 | 115 | function to_double ( n ) 116 | if hasffi then 117 | d [ 0 ] = n 118 | return ffi.string ( d , 8 ) 119 | else 120 | -- Should be the 8 bytes following the second \3 (LUA_TSTRING == '\3') 121 | local str = string.dump ( loadstring ( [[return ]] .. n ) ) 122 | local loc , en , mat = str:find ( "\3(........)" , str:find ( "\3" ) + 1 ) 123 | return mat 124 | end 125 | end 126 | function from_double ( str ) 127 | assert ( #str == 8 ) 128 | if hasffi then 129 | ffi.copy ( d , str , 8 ) 130 | return d [ 0 ] 131 | else 132 | str = s .. str .. e 133 | return loadstring ( str ) ( ) 134 | end 135 | end 136 | end 137 | 138 | return { 139 | le_uint_to_num = le_uint_to_num ; 140 | le_int_to_num = le_int_to_num ; 141 | num_to_le_uint = num_to_le_uint ; 142 | num_to_le_int = num_to_le_int ; 143 | 144 | be_uint_to_num = be_uint_to_num ; 145 | num_to_be_uint = num_to_be_uint ; 146 | 147 | extract_bits = extract_bits ; 148 | 149 | le_bpeek = le_bpeek ; 150 | be_bpeek = be_bpeek ; 151 | 152 | to_double = to_double ; 153 | from_double = from_double ; 154 | } 155 | -------------------------------------------------------------------------------- /test/Date.lua: -------------------------------------------------------------------------------- 1 | local Mongo = require("../") 2 | local Bit64 = Mongo.Bit64 3 | local Bit32 = Mongo.Bit32 4 | local Date = Mongo.Date 5 | local ObjectId = Mongo.ObjectId 6 | 7 | local db = Mongo:new({db = "test"}) 8 | 9 | db:on("connect", function() 10 | db:collection("post"):drop(function() 11 | db:collection("post"):insert({ 12 | created_at = Date(), 13 | title = "Title One" 14 | }, function(err, result) 15 | p("1", err, result) 16 | local post = result[1] 17 | post.updated_at = Date() 18 | db:collection("post"):find({title = "Title One"}):update({["$set"] = post}):exec(function(err, result) 19 | p("2", err, result) 20 | end) 21 | end) 22 | end) 23 | 24 | end) -------------------------------------------------------------------------------- /test/init.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by: Cyril. 3 | -- Created at: 15/6/21 上午2:05 4 | -- Email: houshoushuai@gmail.com 5 | -- 6 | 7 | local Mongo = require("../") 8 | local Bit64 = Mongo.Bit64 9 | local Bit32 = Mongo.Bit32 10 | local Date = Mongo.Date 11 | local ObjectId = Mongo.ObjectId 12 | 13 | m = Mongo:new({db = "test"}) 14 | 15 | m:on("connect", function() 16 | m:collection("abc"):createIndex({name = 1, age = 1}, function(err, res) 17 | p(err, res) 18 | end) 19 | 20 | m:collection("abc"):getIndex(function(err, res) 21 | p(err, res) 22 | end) 23 | 24 | m:insert("abc", {name = "a", age = 2}, nil, function(err, res) 25 | local _id = res[1]._id 26 | m:find("abc", {_id = res[1]._id},nil, nil, nil, function(err, res) 27 | if assert(#res == 1, "Should be 1") then 28 | p("Insert Pass!") 29 | end 30 | m:update("abc", {_id = _id}, {["$set"] = {height = "Hello World!"}}, true, nil,function(err, ures) 31 | m:find("abc", {_id = _id}, nil, nil, nil, function(err, res) 32 | if assert(res[1].height == "Hello World!", "Update faied") then 33 | p("Update Passed!!!") 34 | m:remove("abc", {_id = _id}, nil, function() 35 | p("Deleted") 36 | m:count("abc", {_id = _id}, function(err, res) 37 | if assert(res == 0, "Should be 0") then 38 | p("Remove and count pass!") 39 | end 40 | end) 41 | end) 42 | end 43 | end) 44 | end) 45 | end) 46 | end) 47 | 48 | 49 | -- for i = 1,500 do 50 | -- local insert = { 51 | -- uid = math.floor(math.random(1000)), 52 | -- bean = math.floor(math.random(1000)) 53 | -- } 54 | -- p( "INSERT: ----------->", i, insert) 55 | -- m:insert("Leaderboard", insert, nil, function(err, res) 56 | -- p("Result: ----------->", err, res) 57 | -- end) 58 | -- end 59 | 60 | m:insert("abc", {content = "Content"}, nil, function(err, result) 61 | m:find("abc", {_id = result[1]._id}, nil, nil, nil, function(err, res) 62 | p(res, "1") 63 | m:find("abc", {["$or"]=res}, nil, nil, nil, function(errs, ress) 64 | p(ress, "2") 65 | end) 66 | p("Wow , PASS") 67 | end) 68 | end ) 69 | 70 | -- sort test 71 | m:collection("number"):insert({{number = 1},{number = 2},{number = 3}}, function(err, res) 72 | p("SORT------", err, res) 73 | m:collection("number"):find():sort({number = 1}):exec(function(err, res) 74 | p("SORT------ TEST ---------", err, res) 75 | print("\n\n") 76 | m:collection("number"):drop(function() end) 77 | end) 78 | end) 79 | 80 | local coll = m:collection("abc") 81 | coll:insert({{abc = 123}, {abc = 222}, {abc = 123}}, function(err, res) 82 | if err then 83 | p(err) 84 | return false 85 | end 86 | coll:find({abc = 123}):fields({_id = 1}):exec(function(err, res) 87 | p(err, res, "collection:find") 88 | end) 89 | 90 | coll:findAndModify({abc = 123}, {["$set"] = { mark = "xxxxxxx"}}, function(err, res) 91 | p(err, res, "Cursor:limit, Cursor:update") 92 | coll:find({mark = {["$ne"] = "xxx"}}):exec(function(err, res) 93 | p(err, res, "Cursor:find for Cursor limit and update") 94 | coll:find({abc = 123}):count(function(err, res) 95 | p(err, res, "Cursor:count") 96 | coll:drop(function(err, res) 97 | p(err, res, "DROP") -- Test Passed!!! 98 | end) 99 | end) 100 | end) 101 | end) 102 | 103 | 104 | coll:distinct("abc",nil, function(err, res) 105 | p(err, res) 106 | end) 107 | 108 | end) 109 | 110 | m:insert("abc", { 111 | _id = ObjectId.new(), 112 | long = Bit64(123.2), 113 | int = Bit32(23840.3), 114 | time = Date(os.time()), 115 | longTime = Bit64(os.time() * 1000), 116 | float = 123.4 117 | }, nil, function(err, result) 118 | p("Result:", result) 119 | end) 120 | 121 | local User = m:collection("user") 122 | local insertAndRemove = function() 123 | p("Inserting......") 124 | User:insert({ 125 | name = "Cyril Hou", 126 | id = 1, 127 | birthday = os.date(), 128 | bio = "Weak tables is a good mechanism, but how to find proper objects to use in weak tables? There is about 2Mb of lua code in project and it's problematically to find what objects are not deallocated and still has a reference. Transforming all tables into weak tables will cause objects premature destroying. Also tables are mostly used to take ownership of objects and to free them only when it's necessary. Problem is that not all references are released and i cant' find them" 129 | }, function(err, users) 130 | p("Removing.......") 131 | User:remove(users[1]):exec(function(err, res) 132 | p("Done") 133 | end) 134 | end) 135 | end 136 | insertAndRemove() 137 | 138 | end) 139 | 140 | --------------------------------------------------------------------------------