Commit 3fdf3b19 authored by François Perrad's avatar François Perrad

add an alternate implementation for LuaSQLite3 (lsqlite3)

parent 6e1712e9
......@@ -2,6 +2,7 @@ Revision history for lua-CoatPersistent
0.1.3
+ work with LuaSQL 2.3.0
+ add an alternate implementation for LuaSQLite3 (lsqlite3)
0.1.2 Sat Mar 3 22:30:00 2012
+ work with Lua 5.2.0
......
......@@ -5,6 +5,7 @@ cmake_minimum_required ( VERSION 2.6 )
include ( dist.cmake )
install_lua_module ( Coat.Persistent src/Coat/Persistent.lua )
install_lua_module ( Coat.Persistent.lsqlite3 src/Coat/Persistent/lsqlite3.lua )
install_data ( CHANGES COPYRIGHT README.md )
install_test ( test/ )
......@@ -15,10 +15,13 @@ all: dist.cmake
@echo "Nothing to build here, you can just make install"
install:
cp src/Coat/Persistent.lua $(LIBDIR)/Coat
mkdir -p $(LIBDIR)/Coat/Persistent
cp src/Coat/Persistent.lua $(LIBDIR)/Coat
cp src/Coat/Persistent/lsqlite3.lua $(LIBDIR)/Coat/Persistent
uninstall:
rm -f $(LIBDIR)/Coat/Persistent.lua
rm -f $(LIBDIR)/Coat/Persistent/lsqlite3.lua
manifest_pl := \
use strict; \
......@@ -102,6 +105,7 @@ check: test
test:
cd src && prove --exec=$(LUA) ../test/*.t
cd src && prove --exec="$(LUA) -l Coat.Persistent.lsqlite3" ../test/*.t
coverage:
rm -f src/luacov.stats.out src/luacov.report.out
......
......@@ -19,6 +19,7 @@ description = {
dependencies = {
'lua >= 5.1',
'luasql-sqlite3 >= 2.2.0',
-- 'lsqlite3 >= 0.7',
'dado >= 1.2.0',
'lua-coat >= 0.8.6',
'lua-testmore >= 0.2.3',
......@@ -26,7 +27,8 @@ dependencies = {
build = {
type = 'builtin',
modules = {
['Coat.Persistent'] = 'src/Coat/Persistent.lua',
['Coat.Persistent'] = 'src/Coat/Persistent.lua',
['Coat.Persistent.lsqlite3'] = 'src/Coat/Persistent/lsqlite3.lua',
},
copy_directories = { 'doc', 'test' },
}
--
-- lua-CoatPersistent : <http://fperrad.github.com/lua-CoatPersistent/>
--
local ipairs = ipairs
local pairs = pairs
local rawget = rawget
local rawset = rawset
local setmetatable = setmetatable
local tonumber = tonumber
local tostring = tostring
local type = type
local _G = _G
local Coat = require 'Coat'
local Meta = require 'Coat.Meta.Class'
local dado = require 'dado.sql'
local sqlite3 = require 'lsqlite3'
local error = Coat.error
local argerror = Coat.argerror
local checktype = Coat.checktype
local _class = Coat._class
local has = Coat.has
local type = Coat.type
_ENV = nil
local _M = {}
local drv = {}
local cnx = {}
local function establish_connection (class, driver, fname)
driver = driver or 'sqlite3'
drv[class] = driver
local conn = cnx[driver]
if not conn then
local err, msg
if fname then
conn, err, msg = sqlite3.open(fname)
else
conn, err, msg = sqlite3.open_memory()
end
if not conn then
error(msg)
end
cnx[driver] = conn
end
return conn
end
_M.establish_connection = establish_connection
local function connection (class)
return cnx[drv[class]]
end
_M.connection = connection
local function execute (class, sql)
local trace = _M.trace
if trace then
trace('#', sql)
end
local conn = cnx[drv[class]]
if not conn then
error("No connection for class " .. class._NAME)
end
local r = conn:exec(sql)
if r ~= sqlite3.OK then
error(r)
end
return r
end
local function next_id (class)
local conn = cnx[drv[class]]
if not conn then
error("No connection for class " .. class._NAME)
end
return 1 + conn:last_insert_rowid()
end
local function attributes (class)
local t = {}
for _, v in ipairs(class._ATTR_P) do
t[#t+1] = v
end
for _, cl in ipairs(class._PARENT) do
for _, v in ipairs(cl._ATTR_P) do
t[#t+1] = v
end
end
return t
end
local function save (class, obj)
local primary_key = class._PRIMARY_KEY
local values = {}
for _, field in ipairs(attributes(class)) do
local val = obj[field]
if val ~= nil then
values[field] = tostring(val)
end
end
if rawget(obj, '_db_exist') then
local cond = dado.AND { [primary_key] = obj[primary_key] }
execute(class, dado.update(class._TABLE_NAME, values, cond))
else
obj[primary_key] = next_id(class)
values[primary_key] = obj[primary_key]
execute(class, dado.insert(class._TABLE_NAME, values))
rawset(obj, '_db_exist', true)
end
local t = rawget(obj, '_subobjects')
if t then
for i = 1, #t do
t[i]:save()
end
rawset(obj, '_subobjects', nil)
end
return obj[primary_key]
end
_M.save = save
local function delete (class, obj)
local primary_key = class._PRIMARY_KEY
local cond = dado.AND { [primary_key] = obj[primary_key] }
return execute(class, dado.delete(class._TABLE_NAME, cond))
end
_M.delete = delete
local function create (class, val)
if type(val) == 'table' and #val > 0 then
local t = {}
for i = 1, #val do
t[#t+1] = create(class, val[i])
end
return t
else
local obj = class.new(val)
obj:save()
return obj
end
end
_M.create = create
local function find_by_sql (class, sql)
local trace = _M.trace
if trace then
trace('#', sql)
end
local conn = cnx[drv[class]]
if not conn then
error("No connection for class " .. class._NAME)
end
local f, s = conn:nrows(sql)
return function (_, var)
local row = f(s, var)
if row then
for k, v in pairs(row) do
row[k] = tonumber(v) or v
end
local obj = class.new(row)
rawset(obj, '_db_exist', true)
return obj
else
return nil
end
end
end
_M.find_by_sql = find_by_sql
local function find (class, val)
if val == nil then
return find_by_sql(class, dado.select('*', class._TABLE_NAME))
elseif type(val) == 'number' then
local cond = dado.AND { [class._PRIMARY_KEY] = val }
return find_by_sql(class, dado.select('*', class._TABLE_NAME, cond))
elseif type(val) == 'string' then
return find_by_sql(class, dado.select('*', class._TABLE_NAME, val))
else
argerror('find', 2, "number or string expected")
end
end
_M.find = find
local function has_p (class, name, options)
checktype('has_p', 1, name, 'string')
checktype('has_p', 2, options or {}, 'table')
class['find_by_' .. name] = function (val)
if val == nil then
error "Cannot find without a value"
end
local cond = dado.AND { [name] = val }
return find_by_sql(class, dado.select('*', class._TABLE_NAME, cond))
end
local t = class._ATTR_P; t[#t+1] = name
has(class, name, options)
end
_M.has_p = has_p
local function has_one (class, name, options)
checktype('has_one', 1, name, 'string')
options = options or {}
checktype('has_one', 2, options, 'table')
local owned_class_name = options.class_name or name
local owned_class = Meta.class(owned_class_name)
if not owned_class then
error("Unknown class " .. owned_class_name)
end
local owned_table_name = owned_class._TABLE_NAME
local owned_primary_key = owned_class._PRIMARY_KEY
if not owned_primary_key then
error("The class " .. owned_class_name .. " has not a primary key.")
end
local attr_name = owned_table_name
if options.class_name then
attr_name = name
end
local foreign_key = options.foreign_key or owned_table_name .. '_' .. owned_primary_key
has_p(class, foreign_key, { is = 'rw', isa = 'number' })
class['_set_' .. attr_name] = function (obj, val)
obj[foreign_key] = val[owned_primary_key]
return val
end
class['_get_' .. attr_name] = function (obj)
local id = obj[foreign_key]
if id then
return find(owned_class, id)()
end
end
class._ACCESSOR = attr_name
end
_M.has_one = has_one
local function has_many (class, name, options)
checktype('has_one', 1, name, 'string')
options = options or {}
checktype('has_one', 2, options, 'table')
local owned_class_name = options.class_name or name
local owned_class = Meta.class(owned_class_name)
if not owned_class then
error("Unknown class " .. owned_class_name)
end
local table_name = class._TABLE_NAME
local primary_key = class._PRIMARY_KEY
local owned_table_name = owned_class._TABLE_NAME
local owned_primary_key = owned_class._PRIMARY_KEY
if not owned_primary_key then
error("The class " .. owned_class_name .. " has not a primary key.")
end
local attr_name = owned_table_name .. 's'
if options.class_name then
attr_name = name
end
class['_set_' .. attr_name] = function (obj, list)
if type(list) ~= 'table' or list._CLASS then
error("Not an array of object")
end
local accessor = owned_class._ACCESSOR or table_name
local t = rawget(obj, '_subobjects')
if not t then
t = {}
rawset(obj, '_subobjects', t)
end
for i = 1, #list do
local val = list[i]
if not val:isa(owned_class) then
error("Not an object of class " .. owned_class._NAME .. " (got " .. type(val) .. ")")
end
val[accessor] = obj
t[#t+1] = val
end
end
class['_get_' .. attr_name] = function (obj)
local t = {}
local iter = owned_class['find_by_' .. table_name .. '_' .. primary_key](obj[primary_key])
for v in iter do
t[#t+1] = v
end
return t
end
end
_M.has_many = has_many
function _G.persistent (modname, options)
checktype('persistent', 1, modname, 'string')
options = options or {}
checktype('persistent', 2, options, 'table')
local primary_key = options.primary_key or 'id'
local table_name = options.table_name or modname:gsub('%.', '_')
local M = _class(modname)
M._PRIMARY_KEY = primary_key
M._TABLE_NAME = table_name:lower()
M._ATTR_P = { primary_key }
M.establish_connection = function (...) return establish_connection(M, ...) end
M.connection = function () return connection(M) end
M.save = function (...) return save(M, ...) end
M.delete = function (...) return delete(M, ...) end
M.create = function (...) return create(M, ...) end
M.find = function (...) return find(M, ...) end
M.find_by_sql = function (...) return find_by_sql(M, ...) end
M.has_p = setmetatable({}, { __newindex = function (t, k, v) has_p(M, k, v) end })
M.has_one = setmetatable({}, { __newindex = function (t, k, v) has_one(M, k, v) end })
M.has_many = setmetatable({}, { __newindex = function (t, k, v) has_many(M, k, v) end })
has(M, primary_key, { is = 'rw', isa = 'number' })
end
_G.package.loaded['Coat.Persistent'] = _M
_M._VERSION = "0.1.2"
_M._DESCRIPTION = "lua-CoatPersistent : an ORM for lua-Coat"
_M._COPYRIGHT = "Copyright (c) 2010-2012 Francois Perrad"
return _M
--
-- This library is licensed under the terms of the MIT/X11 license,
-- like Lua itself.
--
......@@ -58,5 +58,5 @@ error_like( [[Person.find(true)]],
"bad argument #2 to find %(number or string expected%)" )
error_like( [[Person.find_by_sql "syntax error"]],
'LuaSQL: near "syntax": syntax error' )
'near "syntax": syntax error' )
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment