Skip to content

lua mongo driver implemented by cfadmin.

License

Notifications You must be signed in to change notification settings

cfadmin-cn/mongo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lua MongoDB Driver

基于cfadmin框架实现的MongoDB Driver.

特性

  • 最新版的协议(OP_MSG), 交互更加高效;

  • 封装出了统一的CURD、聚合、统计等接口方法;

  • GridFS的上传、查询、删除等操作, 并且支持分片传输;

  • 拥有社区最完善的BSON解析器, 使用更加简单、解析更加高效;

  • 更简洁的语法降低学习成本, 无需复杂的调用即可完成功能;

  • 完善的使用示例, 可以自行测试实际使用情况;

类型

  • 字符串(String)

  • 二进制类型(Binary/MD5/uuid)

  • 正则表达式(Regex)

  • 表(table/array)

  • 空(null)

  • 未定义(undefined)

  • 时间(datetime/timestamp)

  • 对象ID(objectid)

  • 整型(int32/int64)

  • 浮点型(Double)

  • 布尔(true/false)

  • 大小值(MaxKey/MinKey)

  • 代码(java script code)

效率

本库使用了纯Lua实现了bson的序列化与反序列化, 但是经过一段时间的使用与测试发现体验并不乐观; 因为特性原因必须增加复杂的反序列化流程;

而我们主要在与mongodb服务器进行交互的时候回才会使用bson, 并且交互请求的编码不会有太多内容所以主要性能问题定位在bson的反序列化上.

在经过详细的测试后发现纯Lua实现的BSON反序列化性能十分糟糕,所以最后经过使用使用C语言重写的反序列化方法类解决这方面带来的一些负面影响.

值得一提的是C语言版的实现效率是Lua100倍, 所以不用再担心性能问题了; 并且内部能自动检查用户是否有编译出C版本的bson实现, 用户有需要编译即可.

安装

  • clone项目到3rd目录下, 这样就完成了基础安装;

  • (可选) 如果您安装有GCC或者clang编译器; 那么可以进入mongo的目录运行make build编译lbson.so;

使用介绍

local mongo = require "mongo"

local bson = require "mongo.bson"

1. 构造方法

创建


function mongo:new(opt) return mongo end

  • opt.host - string类型, 服务器域名(默认是:"localhost");

  • opt.port - integer类型, 服务器端口(默认是:27017);

  • opt.SSL - boolean类型, 是否需要使用SSL协议握手;

  • opt.auth_mode - string类型, 授权验证模式(仅支持:SCRAM-SHA-1);

  • opt.db - string类型, 授权数据库名称(默认是:"admin");

  • opt.username - string类型, 授权用户账号;

  • opt.password - string类型, 授权用户密码;

调用此构造方法将会创建MongoDB对象.

连接


function mongo:connect() return true | nil, string end

开发者在创建MongoDB对象的时候如果填写了usernamepassword, 调用此方法的时候会自动完成授权认证.

但可能存在授权操作不完善(例如不可读、写)的情况, 这就可能出现鉴权成功但后续执行CRUD时提示鉴权的可能;

如遇到以上问题, 请开发者自行使用相关管理工具解决.

成功返回true, 失败返回false与失败信息string,

断开


function mongo:close() return nil end

此方法无返回值.

2. CRUD操作

查询语句


function mongo:find(database, collect, filter, option) return info, id | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • filter - table类型, 一个符合语法规范的查询条件;

  • option - table类型, 可选参数(option.sort/option.limit/option.skip/option.cursor/option.size/option.project);

filter可以用作查询的过滤条件, 例如: { nickname = "李小龙" }或一个空表; (但是不能为空数组);

option参数的组合作用为游标分页跳跃分页:

* 跳跃分页(`limit`与`skip`): 操作方式类似结构化数据库`MySQL`、`Oracle`等的`LIMIT`与`OFFSET`;

* 游标分页(`cursor`与`size`):每次迭代(包括第一次)返回`size`条数据与下次迭代的游标`ID`(游标`ID`是一次性的);

sort参数指定了排序的方式, 表达式为: {sort = {age = 1}} 或者 {sort = {age = -1}}, (1)升序、(-1)降序;

project参数指定了查询时可以筛选出需要返回的字段, 这在需要过滤集合字段的情况下使用非常有效;

成功返回table类型的infointeger类型的cursor id, 失败返回false与失败信息string.

插入语句


function mongo:insert(database, collect, documents, option) return info | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • documents - table数组类型, 包含(至少)有一个或者多个(可选)文档的数组;

  • option - table类型, 可选参数(option.ordered);

documents为文档数组, 即使只插入一条数据也应该使用这样的数组表达式: { {nickname = "名称", age = 18 }};

option.ordered默认为false(如果插入多条的时候出错, 则忽略并继续处理后续数据), 设置为true则会不再处理后续数据;

成功返回table类型的info, 失败返回false与失败信息string.

更新语句


function mongo:update(database, collect, filter, set, option) return info | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • filter - table类型, 查询过滤的条件;

  • set - table类型, 查询修改的内容;

  • option - table类型, 可选参数(option.upsert/option.multi);

filter可以用作查询的过滤条件, 例如: { nickname = "李小龙" }或一个空表; (但是不能为空数组);

set参数为一个文档或者文档更新语法, 具体使用方式类似mongo shell语法:

* `mongo:update('db', 'table', { name = "123" }, { ['$set'] = { name = "234" } } )`

* `mongo:update('db', 'table', { name = "123" }, { ['$inc'] = { age = 1 } } )`

* `mongo:update('db', 'table', { name = "123" }, { ['$unset'] = { age = 1} } )`

option.upsert默认为false; 如果设置为true, 则表示不存在set指定的记录则插入;

option.multi默认为false(只更新找到的第一条记录), 如果设置为true, 则表示更新所有记录;

成功返回table类型的info, 失败返回false与失败信息string.

删除语句


function mongo:delete(database, collect, option) return info | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • filter - table类型, 查询过滤的条件;

  • option - table类型, 可选参数(option.one);

filter可以用作查询的过滤条件, 例如: { nickname = "李小龙" }或一个空表; (但是不能为空数组);

option.one属性指定为1表示只删除1条数据, 其它值与默认情况下都表示删除所有匹配项;

成功返回table类型的info, 失败返回false与失败信息string.

3. 聚合操作

统计查询


function mongo:count(database, collect, filter) return info | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • filter - table类型, 查询过滤的条件;

根据filter参数的过滤条件(可以为空), 统计集合内符合过滤条件的数量;

成功返回table类型的info, 失败返回false与失败信息string.

聚合查询


function mongo:aggregate(database, collect, filters, option) return info | nil, string end

  • database - string类型, MongoDB的数据库名称;

  • collect - string类型, MongoDB的集合名称;

  • filters - table类型, 查询过滤的条件的数组;

此方法的返回值内容会根据实际filter内的提供的参数表达式返回不同的内容;

成功返回table类型的info, 失败返回false与失败信息string.

使用示例:

以下示例展示了基础API的使用方法.

1. CRUD操作
require"utils"

local mongo = require "mongo"
local bson = require "mongo.bson"

local m = mongo:new {
  db = "mydb",        -- 授权DB名称
  username = "admin", -- 授权用户名称
  password = "admin", -- 授权用户密码
}

require "logging":DEBUG("开始")

local ok, err = m:connect()
if not ok then
  return print(false, err)
end

local database, collect = "mydb", "table"

local tab

tab, err = m:insert(database, collect, {
  { nickname = "車先生", age = 30, ts = bson.timestamp(), nullptr = bson.null(), regex = bson.regex("/先生/i"), uuid = bson.uuid() },
  { nickname = "車太太", age = 26, ts = bson.timestamp(), nullptr = bson.null(), regex = bson.regex("/太太/i"), guid = bson.guid() },
})
if not tab then
  return print(false, err)
end
var_dump(tab)

tab, err = m:find(database, collect)
if not tab then
  return print(false, err)
end
var_dump(tab)

tab, err = m:update(database, collect, { nickname = "車太太" }, { ["$set"] = { nickname = "車先生" }})
if not tab then
  return print(false, err)
end
var_dump(tab)

tab, err = m:find(database, collect)
if not tab then
  return print(false, err)
end
var_dump(tab)

tab, err = m:delete(database, collect, { nickname = "車先生" } )
if not tab then
  return print(false, err)
end
var_dump(tab)

m:close()

require "logging":DEBUG("结束")

输出如下:

[candy@MacBookPro:~/Documents/cfadmin] $ ./cfadmin
[2021-02-28 13:26:35,947] [@script/main.lua:94] [DEBUG] : "开始"
{
      ["acknowledged"] = true,
      ["insertedCount"] = 2,
}
{
      [1] = {
            ["_id"] = "603b298bb7bacd21ef3c59d9",
            ["regex"] = "/先生/i",
            ["nickname"] = "車先生",
            ["age"] = 30,
            ["nullptr"] = userdata: 0x0,
            ["ts"] = 1614489995947,
            ["uuid"] = "ba1c1b0c-191b-4480-9d68-cb4c5755cd5d",
      },
      [2] = {
            ["_id"] = "603b298bb7bacd21ef3c59da",
            ["regex"] = "/太太/i",
            ["nickname"] = "車太太",
            ["guid"] = "f47bf20c-7a7f-4c61-603b-298b2507b289",
            ["age"] = 26,
            ["nullptr"] = userdata: 0x0,
            ["ts"] = 1614489995947,
      },
}
{
      ["matchedCount"] = 1,
      ["modifiedCount"] = 1,
      ["acknowledged"] = true,
}
{
      [1] = {
            ["_id"] = "603b298bb7bacd21ef3c59d9",
            ["regex"] = "/先生/i",
            ["nickname"] = "車先生",
            ["age"] = 30,
            ["nullptr"] = userdata: 0x0,
            ["ts"] = 1614489995947,
            ["uuid"] = "ba1c1b0c-191b-4480-9d68-cb4c5755cd5d",
      },
      [2] = {
            ["_id"] = "603b298bb7bacd21ef3c59da",
            ["regex"] = "/太太/i",
            ["nickname"] = "車先生",
            ["guid"] = "f47bf20c-7a7f-4c61-603b-298b2507b289",
            ["age"] = 26,
            ["nullptr"] = userdata: 0x0,
            ["ts"] = 1614489995947,
      },
}
{
      ["acknowledged"] = true,
      ["deletedCount"] = 2,
}
[2021-02-28 13:26:35,961] [@script/main.lua:135] [DEBUG] : "结束"
2. 聚合操作
require"utils"

local mongo = require "mongo"
local bson = require "mongo.bson"

local m = mongo:new {
  db = "mydb",        -- 授权DB名称
  username = "admin", -- 授权用户名称
  password = "admin", -- 授权用户密码
}

require "logging":DEBUG("开始")

local ok, err = m:connect()
if not ok then
  return print(false, err)
end

local database, collect = "mydb", "table"

local tab, id

tab, err = m:count(database, collect, {})
if not tab then
  return print(false, err)
end
var_dump(tab)

tab, id = m:aggregate(database, collect
  ,{
    { ["$match"] = {age = 26} },
    { ["$sort"] =  {_id = -1} },
  }
  ,{
    -- cursor = 8896207551195673826,  -- 游标的写法.
    -- size = 3, -- 聚合函数内单独指定size无意义, 如有必要请在fileters里使用$limit
  }
)
if not tab then
  return print(false, err)
end
print(tab, #tab, id)
-- var_dump(tab)

require "logging":DEBUG("结束")

输出如下:

Candy@CandyMi MSYS ~/stt_trade
$ ./cfadmin.exe
[2021-03-02 14:15:47,675] [@script/main.lua:92] [DEBUG] : "开始"
{
      ["acknowledged"] = true,
      ["count"] = 122,
}
table: 0x8000f9f30      101     4732584172034615803
[2021-03-02 14:15:47,685] [@script/main.lua:130] [DEBUG] : "结束"
3. 索引操作
local mongo = require "mongo"

require "utils"

local m = mongo:new { db = "mydb" }

if not m:connect() then
  return print(false, "连接失败")
end

local tab, info = m:create_indexes("mydb", "test", { nickname = 1, age = -1}, { unique = 1, background = 1})
if not tab then
  return print(false, info)
end
var_dump(tab)

local tab, info = m:get_indexes("mydb", "test")
if not tab then
  return print(false, info)
end
var_dump(tab)

local tab, info = m:drop_indexes("mydb", "test", "nickname_1_age_1")
if not tab then
  return print(false, info)
end
var_dump(tab)
4. GridFS操作
require "utils"
local mongo = require "mongo"

local m = mongo:new { db = "mydb" }

if not m:connect() then
  return print(false, "连接失败")
end

local tab, id = m.gridfs:gridfs_find("mydb", "fs", { })
if not tab then
  return print(false, id)
end
print(id)
var_dump(tab)

if id > 0 then
  local tab, id = m.gridfs:gridfs_find("mydb", "fs", {}, id)
  if not tab then
    return print(false, id)
  end
  print(id)
  var_dump(tab)
end

local tab, info = m.gridfs:gridfs_delete("mydb", "fs", {})
if not tab then
  return print(false, info)
end
var_dump(tab)
5. DB的使用
require "utils"
local DB = require "mongo.DB"

local m = DB:new { db = "mydb" }

if not m:connect() then
  return print(false, "连接失败")
end

local tab, id = m:find("mydb", "test", { })
if not tab then
  return print(false, id)
end
var_dump(tab)

local tab, id = m:delete("mydb", "test", { })
if not tab then
  return print(false, id)
end
var_dump(tab)

local tab, id = m:find("mydb", "test", { })
if not tab then
  return print(false, id)
end
var_dump(tab)

提示

  • 如需删除数据请一定注意使用方法的行为是否如你预期.

  • bson库的性能有一定的要求, 请务必编译lbson.so库文件出来.

  • 当某字段需要插入空数组的时候, 可以使用内置的bson.empty_array()方法进行构造.

  • 查询未指定cursor但是指定了size, 数据如果超出size就会返回游标ID.

  • 一些特殊数据类型(bson.objectid)需要bson的构造方法来编码传递, 如果有疑问可以咨询作者.

  • 授权仅支持用户名/密码授权(SCRAM-SHA-1);

  • 本驱动仅支持MongoDB 3.6及以上版本;

建议

  • 使用VS CODE安装lua-language-server插件后使用会自动得到上述代码不全与编码提示;

  • 如果您有更好的需求与建议请留言到ISSUE, 作者在收到后会尽快回复并与您进行沟通;

LICENSE

BSD LICENSE