项目中经常会使用到sqlite来存储数据,例如聊天,以及一些游戏中的配置。
Cocos2d-x引擎中是有一个storage文件夹,仔细看LocalStorage.h文件,里面说明了,值专门针对JS Bindings使用。另外没有sqlite.c文件。引擎中也没有SQLite的sqlite.c实现文件。所以我们需要到官网去下载:
luaSqlite的源代码http://lua.sqlite.org/index.cgi/index
下载sqlite的源代码 http://sqlite.org/
引擎版本:3.6
一、集成到项目中
在上面的两个网址下载文件后等到如下文件:
lsqlite3.c (lua的绑定实行已经做好了)
shell.c
sqlite3.c
sqlite3.h
sqlite3ext.h
1、在Class里面新建文件夹Sqlite3,将上述文件放在里面。编译
2、lua-bindings中的auto目录下新建lsqlite3.h文件,内容如下
#ifndef __LSQLITE3_H__
#define __LSQLITE3_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif
extern "C" int luaopen_lsqlite3(lua_State* L); //lsqlite3.c 中的C函数,这里注册C函数
#endif
3、注册SQLite相关函数
在L状态中调用lsqlite.h文件中的 luaopen_lsqlite3注册到lua虚拟机环境中。
跟其它的自定义类,或者引擎相关的注册函数调用出调用。这里我们在LuaSupportFactory::registerCustomLuaModule()中添加注册代码
void LuaSupportFactory::registerCustomLuaModule()
{
//TODO 注册自定义的Lua API
lua_State* L = LuaEngine::getInstance()->getLuaStack()->getLuaState();
register_all_ww_net(L);
register_all_ww_gui(L);
register_all_ww_data(L);
luaopen_lsqlite3(L); //注册LSQLite3相关内容。
}
4、测试验证
在lua中调用如下
local sqlite3 = require("sqlite3")
cclog("sqlite3 version"..sqlite3.version())
二、关于包体大小
集成的时候,发现,几个文件加起来达到6.78M。主要是sqlite3.c文件很大。不过其实编译后就很小了。
win32下面文件变为2.16M了,但是看看其他的cpp文件编译后大小比这大多了。
新建一个Cocos2d-x lua项目
分别打出不包含、包含lsqlite3的两个项目包
文件差不多大。所以sqlite.c文件很大,但是因为是C文件,在打包后会压缩到很小。不必担心集成包大小
三、使用API
1、引用LuaSqlite
local sqlite3 = require("sqlite3")
--打开数据库文件
local db = sqlite3.open('test.db')
--打开内存数据库
local db = sqlite3.open_memory()
四、碰到的问题
1、error loading module ‘sqlite3’
在一次更新代码的时候将sqlite注册到Lua的代码冲掉了,一直报这个错误,后来检查代码才发现是没有注册过去。
2、iOS平台下,出现shell.c报错
需要将这个文件去掉,这个文件是控制台数据库操作用的,不应该加搞项目中。
五、实例
-------------------------------------------------------------------------
-- Desc: sqlite3test.lua
-- Author: sqlite3测试
-- Copyright (c) wawagame Entertainment All right reserved.
-- local sqlite3 = require("app.dataorm.sqlite3test")
-- sqlite3:getDBVersion()
-- sqlite3:openDB()
-- sqlite3:insert('numbers', {10086, 10086,"diyal"})
-- sqlite3:test()
-- sqlite3:test2()
-- sqlite3:test3()
-- sqlite3:aggregate()
-- sqlite3:crudTest()
-- sqlite3:statement()
-- sqlite3:tracing()
-- sqlite3:batchsql()
-- sqlite3:updateHook()
-------------------------------------------------------------------------
local sqlite3 = require("sqlite3")
local sqlite3test = class('sqlite3test')
local _db, _vm --数据库句柄, 数据库状态
--[[获取版本号]]
function sqlite3test:getDBVersion()
cclog("[SQLite] DB version : "..sqlite3.version())
end
function sqlite3test:openDB()
local dbFilePath = device.writablePath..'test.db'
local isExist = cc.FileUtils:getInstance():isFileExist(dbFilePath)
_db = sqlite3.open(dbFilePath)
if isExist then
cclog('[SQLite] DB File is exist')
else
cclog('[SQLite] DB File is not exist, created it')
--初始化表结构
self:initDB()
end
end
function sqlite3test:initDB()
-- Demo表DDL语句
local t_demo_sql=
[=[
CREATE TABLE numbers(num1,num2,str);
INSERT INTO numbers VALUES(1,11,"ABC");
INSERT INTO numbers VALUES(2,22,"DEF");
INSERT INTO numbers VALUES(3,33,"UVW");
INSERT INTO numbers VALUES(4,44,"XYZ");
SELECT * FROM numbers;
]=]
local showrow = function(udata,cols,values,names)
assert(udata == 't_demo_create')
-- for i=1,cols do
-- cclog('%s |-> %s',names[i],values[i])
-- end
cclog('[SQLite] %s rows %s', udata,table.concat( values, "-"))
return 0
end
_db:exec(t_demo_sql, showrow, 't_demo_create')
end
function sqlite3test:insert( tableName, tableParas)
local t_demo_sql=
[=[
INSERT INTO tableName VALUES(tableParas);
SELECT * FROM numbers;
]=]
local showrow = function(udata,cols,values,names)
assert(udata == 't_demo_create')
-- for i=1,cols do
-- cclog('%s |-> %s',names[i],values[i])
-- end
cclog('[SQLite] %s rows %s', udata,table.concat( values, "-"))
return 0
end
local ret = _db:exec(t_demo_sql, showrow, 't_demo_create')
if ret ~= sqlite3.OK then
cclog('error')
end
end
--[[test]]
function sqlite3test:test()
local sqlite3 = require('sqlite3')
local width = 78
local function line(pref, suff) --格式化函数
pref = pref or ''
suff = suff or ''
local len = width - 2 - string.len(pref) - string.len(suff)
cclog(pref .. string.rep('_', len) .. suff)
end
local db, vm
local assert_, assert = assert, function (test)
if (not test) then
error(db:errmsg(), 2)
end
end
-- os.remove('test.db')
db = sqlite3.open('test.db')
line(nil, 'db:exec')
db:exec('CREATE TABLE t(a, b)')
line(nil, 'prepare')
vm = db:prepare('insert into t values(?, :bork)')
assert(vm, db:errmsg())
assert(vm:bind_parameter_count() == 2)
assert(vm:bind_values(2, 4) == sqlite3.OK)
assert(vm:step() == sqlite3.DONE)
assert(vm:reset() == sqlite3.OK)
assert(vm:bind_names{ 'pork', bork = 'nono' } == sqlite3.OK)
assert(vm:step() == sqlite3.DONE)
assert(vm:reset() == sqlite3.OK)
assert(vm:bind_names{ bork = 'sisi' } == sqlite3.OK)
assert(vm:step() == sqlite3.DONE)
assert(vm:reset() == sqlite3.OK)
assert(vm:bind_names{ 1 } == sqlite3.OK)
assert(vm:step() == sqlite3.DONE)
assert(vm:finalize() == sqlite3.OK)
line("select * from t", 'db:exec')
-- assert(db:exec('select * from t', function (ud, ncols, values, names)
-- cclog(table.unpack(values))
-- return sqlite3.OK
-- end) == sqlite3.OK)
db:exec('select * from t', function (ud, ncols, values, names)
cclog(
table.concat(
{ unpack(values)}
)
)
return sqlite3.OK
end)
line("select * from t", 'db:prepare')
vm = db:prepare('select * from t')
assert(vm, db:errmsg())
cclog(vm:get_unames())
while (vm:step() == sqlite3.ROW) do
cclog(vm:get_uvalues())
end
assert(vm:finalize() == sqlite3.OK)
line('udf', 'scalar')
local function do_query(sql)
local r
local vm = db:prepare(sql)
assert(vm, db:errmsg())
cclog('====================================')
cclog(vm:get_unames())
cclog('------------------------------------')
r = vm:step()
while (r == sqlite3.ROW) do
cclog(vm:get_uvalues())
r = vm:step()
end
assert(r == sqlite3.DONE)
assert(vm:finalize() == sqlite3.OK)
cclog('====================================')
end
local function udf1_scalar(ctx, v)
local ud = ctx:user_data()
ud.r = (ud.r or '') .. tostring(v)
ctx:result_text(ud.r)
end
db:create_function('udf1', 1, udf1_scalar, { })
do_query('select udf1(a) from t')
line('udf', 'aggregate')
local function udf2_aggregate(ctx, ...)
local ud = ctx:get_aggregate_data()
if (not ud) then
ud = {}
ctx:set_aggregate_data(ud)
end
ud.r = (ud.r or 0) + 2
end
local function udf2_aggregate_finalize(ctx, v)
local ud = ctx:get_aggregate_data()
ctx:result_number(ud and ud.r or 0)
end
db:create_aggregate('udf2', 1, udf2_aggregate, udf2_aggregate_finalize, { })
do_query('select udf2(a) from t')
-- if (true) then
-- line(nil, '100 insert exec')
-- db:exec('delete from t')
-- local t = os.time()
-- for i = 1, 100 do
-- db:exec('insert into t values('..i..', '..(i * 2 * -1^i)..')')
-- end
-- print('elapsed: '..(os.time() - t))
-- do_query('select count(*) from t')
-- line(nil, '100000 insert exec T')
-- db:exec('delete from t')
-- local t = os.time()
-- db:exec('begin')
-- for i = 1, 100000 do
-- db:exec('insert into t values('..i..', '..(i * 2 * -1^i)..')')
-- end
-- db:exec('commit')
-- print('elapsed: '..(os.time() - t))
-- do_query('select count(*) from t')
-- line(nil, '100000 insert prepare/bind T')
-- db:exec('delete from t')
-- local t = os.time()
-- local vm = db:prepare('insert into t values(?, ?)')
-- db:exec('begin')
-- for i = 1, 100000 do
-- vm:bind_values(i, i * 2 * -1^i)
-- vm:step()
-- vm:reset()
-- end
-- vm:finalize()
-- db:exec('commit')
-- print('elapsed: '..(os.time() - t))
-- do_query('select count(*) from t')
-- end
line(nil, "db:close")
local filePath = cc.FileUtils:getInstance():fullPathForFilename('test.db')
cclog('path: '..filePath)
assert(db:close() == sqlite3.OK)
end
function sqlite3test:test2()
_db = sqlite3.open('test.db')
cclog('[SQLite] db:exec')
_db:exec('CREATE TABLE t(a, b)')
cclog('[SQLite] prepare')
_vm = _db:prepare('insert into t values(?, :bork)')
assert(_vm, _db:errmsg())
assert(_vm:bind_parameter_count() == 2)
assert(_vm:bind_values(2, 4) == sqlite3.OK)
assert(_vm:step() == sqlite3.DONE)
assert(_vm:reset() == sqlite3.OK)
assert(_vm:bind_names{ 'pork', bork = 'nono' } == sqlite3.OK)
assert(_vm:step() == sqlite3.DONE)
assert(_vm:reset() == sqlite3.OK)
assert(_vm:bind_names{ bork = 'sisi' } == sqlite3.OK)
assert(_vm:step() == sqlite3.DONE)
assert(_vm:reset() == sqlite3.OK)
assert(_vm:bind_names{ 1 } == sqlite3.OK)
assert(_vm:step() == sqlite3.DONE)
assert(_vm:finalize() == sqlite3.OK)
cclog("[SQLite] select * from t")
-- assert(_db:exec('select * from t',
-- function (ud, ncols, values, names)
-- cclog(table.unpack(values))
-- return sqlite3.OK
-- end) == sqlite3.OK
-- )
local ret = _db:exec('select * from t',
function (ud, ncols, values, names)
cclog('[SQLite] ' .. table.unpack(values))
return sqlite3.OK
end)
end
--[[test 3]]
function sqlite3test:test3()
end
--[[聚合函数]]
function sqlite3test:aggregate()
assert( _db:exec "CREATE TABLE test (col1, col2)" )
assert( _db:exec "INSERT INTO test VALUES (1, 2)" )
assert( _db:exec "INSERT INTO test VALUES (2, 4)" )
assert( _db:exec "INSERT INTO test VALUES (3, 6)" )
assert( _db:exec "INSERT INTO test VALUES (4, 8)" )
assert( _db:exec "INSERT INTO test VALUES (5, 10)" )
do
local square_error_sum = 0
local function step(ctx, a, b)
local error = a - b
local square_error = error * error
square_error_sum = square_error_sum + square_error
end
local function final(ctx)
ctx:result_number( square_error_sum / ctx:aggregate_count() )
end
assert( _db:create_aggregate("my_stats", 2, step, final) )
end
for my_stats in _db:urows("SELECT my_stats(col1, col2) FROM test")
do
cclog("my_stats:%d", my_stats)
end
end
--[[CRUD]]
function sqlite3test:crudTest()
local sqlite3 = require("sqlite3") --加载模块
local db = sqlite3.open_memory() --开辟内存数据库
db:exec[[ CREATE TABLE test (id INTEGER PRIMARY KEY, content) ]] --执行DDL语句
local stmt = db:prepare[[ INSERT INTO test VALUES (:key, :value) ]] --前导声明
stmt:bind_names({
key = 1,
value = "Hello World"
}) --参数绑定
-- step()
-- This is the top-level implementation of sqlite3_step(). Call
-- sqlite3Step() to do most of the work. If a schema error occurs,
-- call sqlite3Reprepare() and try again.
stmt:step() --执行
stmt:reset() --重置
stmt:bind_names({ key = 2, value = "Hello Lua" } )
stmt:step()
stmt:reset()
stmt:bind_names({ key = 3, value = "Hello Sqlite3" })
stmt:step()
stmt:finalize()
for row in db:nrows("SELECT * FROM test") do
cclog("%d, %s", row.id, row.content)
end
end
--[[statement]]
function sqlite3test:statement()
local sqlite3 = require("sqlite3")
local db = sqlite3.open_memory()
db:exec[[
CREATE TABLE test (
id INTEGER PRIMARY KEY,
content VARCHAR
);
]]
local insert_stmt = assert( db:prepare("INSERT INTO test VALUES (NULL, ?)") )
local function insert(data) --封装一个insert函数
insert_stmt:bind_values(data)
insert_stmt:step()
insert_stmt:reset()
end
local select_stmt = assert( db:prepare("SELECT * FROM test") )
local function select() --封装一个查询函数
for row in select_stmt:nrows() do
cclog("%d, %s",row.id, row.content)
end
end
insert("Hello World")
cclog("First:")
select()
insert("Hello Lua")
cclog("Second:")
select()
insert("Hello Sqlite3")
cclog("Third:")
select()
end
--[[tracing]]
function sqlite3test:tracing()
local sqlite3 = require("sqlite3")
local db = sqlite3.open_memory()
db:trace( function(ud, sql)
cclog("[Sqlite Trace]: %s", sql)
end )
db:exec[=[
CREATE TABLE test ( id INTEGER PRIMARY KEY, content VARCHAR );
INSERT INTO test VALUES (NULL, 'Hello World');
INSERT INTO test VALUES (NULL, 'Hello Lua');
INSERT INTO test VALUES (NULL, 'Hello Sqlite3');
]=]
for row in db:rows("SELECT * FROM test") do
cclog(row.content)
end
end
--[[batch sql str]]
--批处理
function sqlite3test:batchsql()
local sqlite3 = require("sqlite3")
local db = sqlite3.open_memory()
local sql=[=[
CREATE TABLE numbers(num1,num2,str);
INSERT INTO numbers VALUES(1,11,"ABC");
INSERT INTO numbers VALUES(2,22,"DEF");
INSERT INTO numbers VALUES(3,33,"UVW");
INSERT INTO numbers VALUES(4,44,"XYZ");
SELECT * FROM numbers;
]=]
local showrow = function(udata,cols,values,names)
assert(udata=='test_udata')
for i=1,cols do
cclog('%s |-> %s',names[i],values[i])
end
return 0
end
db:exec(sql,showrow,'test_udata')
end
--[[update hook]]
--表事件监听 eg:有在一张表插入数据,则读取更新数据
function sqlite3test:updateHook()
local sqlite3 = require("sqlite3")
local db = sqlite3.open_memory()
local optbl = {
[sqlite3.UPDATE] = "UPDATE";
[sqlite3.INSERT] = "INSERT";
[sqlite3.DELETE] = "DELETE"
}
setmetatable(optbl,
{
__index=function(t,n)
return string.format("Unknown op %d",n)
end
})
local udtbl = {0, 0, 0}
db:update_hook( function(ud, op, dname, tname, rowid)
cclog("Sqlite Update Hook: %s,%s,%s,%d", optbl[op], dname, tname, rowid)
end, udtbl)
db:exec[[
CREATE TABLE test ( id INTEGER PRIMARY KEY, content VARCHAR );
INSERT INTO test VALUES (NULL, 'Hello World');
INSERT INTO test VALUES (NULL, 'Hello Lua');
INSERT INTO test VALUES (NULL, 'Hello Sqlite3');
UPDATE test SET content = 'Hello Again World' WHERE id = 1;
DELETE FROM test WHERE id = 2;
]]
for row in db:nrows("SELECT * FROM test") do
cclog('%d %s', row.id, row.content)
end
end
return sqlite3tes