first commit

This commit is contained in:
Souti
2025-03-06 11:09:58 +01:00
commit 11f7d440ff
330 changed files with 38306 additions and 0 deletions

View File

@@ -0,0 +1,843 @@
--[[--------------------------------------------------------------------------
Author: Michael Roth <mroth@nessie.de>
Copyright (c) 2004, 2005 Michael Roth <mroth@nessie.de>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]--------------------------------------------------------------------------
--[[
TODO:
collation set_collation(name, func)
collation_needed set_collation_handler(func)
progress_handler set_progress_handler(func)
authorizer set_auth_handler(func)
commit_hook set_commit_handler(func)
--]]
require "libluasqlite3-loader"
local api, ERR, TYPE, AUTH = load_libluasqlite3()
local db_class = { }
local stmt_class = { }
local function check_stmt(stmt)
assert(type(stmt.handles) == "table", "Prepared statement expected")
return stmt
end
local function check_single_stmt(stmt)
assert(type(stmt.handles) == "table" and table.getn(stmt.handles) == 1, "Single prepared statement expected")
return stmt.handles[1]
end
local function check_db(db)
assert(db.handle, "Open database handle expected")
return db
end
local function check_table(tab, msg)
assert(type(tab) == "table", msg)
return tab
end
local function check_string(str, msg)
assert( type(str) == "string", msg )
return str
end
local function check_number(num, msg)
assert( type(num) == "number", msg )
return num
end
-----------------------------------------------------
-- Error test und report helper for sqlite3 errors --
-----------------------------------------------------
local function is_error(status)
return not ( status == ERR.OK or status == ERR.ROW or status == ERR.DONE )
end
local function is_row(status)
return status == ERR.ROW
end
local function is_done(status)
return status == ERR.DONE
end
local function errmsg(db_handle)
return api.errmsg(db_handle) or "Unknown error"
end
-------------------------------------------------------------------------
-- Creates an oject. An object is a table with itself set as metatable --
-------------------------------------------------------------------------
local function object()
local t = { }
setmetatable(t, t)
return t
end
-----------------------------------
-- Registry for tables (objects) --
-----------------------------------
local function create_registry()
return { 0 }
end
local function register(registry, object)
local id = registry[1]
if id == 0 then
table.insert(registry, object)
return table.getn(registry)
else
registry[1] = registry[id]
registry[id] = object
return id
end
end
local function unregister(registry, id)
registry[id] = registry[1]
registry[1] = id
end
-------------
-- sqlite3 --
-------------
sqlite3 = { }
function sqlite3.open(filename)
check_string(filename, "Filename as string expected")
local status, handle = api.open(filename)
if is_error(status) then
local errmsg = errmsg(handle)
api.close(handle)
return nil, errmsg
end
local db = object()
db.__gc = db_class.close
db.__index = db_class
db.filename = filename
db.handle = handle
db.stmts = create_registry()
return db
end
function sqlite3.open_memory()
return sqlite3.open(":memory:")
end
--------------------
-- Database Class --
--------------------
function db_class.close(db)
check_db(db)
for _, obj in ipairs(db.stmts) do
if type(obj) == "table" then
obj:close()
end
end
local status = api.close(db.handle)
if is_error(status) then
return nil, errmsg(db.handle)
end
db.handle = nil
db.stmts = nil
db.__gc = nil
return db
end
function db_class.interrupt(db)
check_db(db)
local status = api.interrupt(db.handle)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.last_insert_rowid(db)
check_db(db)
return api.last_insert_rowid(db.handle)
end
function db_class.changes(db)
check_db(db)
return api.changes(db.handle)
end
function db_class.total_changes(db)
check_db(db)
return api.total_changes(db.handle)
end
function db_class.exec(db, sql)
check_db(db)
check_string(sql)
local status = api.exec(db.handle, sql)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.irows(db, sql, tab)
check_db(db)
return db:prepare(sql):irows(tab, true)
end
function db_class.rows(db, sql, tab)
check_db(db)
return db:prepare(sql):rows(tab, true)
end
function db_class.cols(db, sql)
check_db(db)
return db:prepare(sql):cols(true)
end
function db_class.first_irow(db, sql, tab)
check_db(db)
return db:prepare(sql):first_irow(tab, true)
end
function db_class.first_row(db, sql, tab)
check_db(db)
return db:prepare(sql):first_row(tab, true)
end
function db_class.first_cols(db, sql)
check_db(db)
return db:prepare(sql):first_cols(true)
end
function db_class.prepare(db, paranames, sql)
check_db(db)
if sql == nil then
sql = paranames
paranames = nil
end
check_string(sql, "db:prepare: SQL statement as string expected")
local function cleanup(handles)
for _, handle in ipairs(handles) do
api.finalize(handle)
end
end
local function count_parameters(handles)
local parameter_count = 0
for _, handle in ipairs(handles) do
parameter_count = parameter_count + api.bind_parameter_count(handle)
end
return parameter_count
end
local function build_handles(sql)
local status, handle
local remaining = sql
local handles = { }
while remaining do
status, handle, remaining = api.prepare(db.handle, remaining)
if is_error(status) then
local errmsg = errmsg(db.handle)
cleanup(handles)
return nil, errmsg
end
table.insert(handles, handle)
end
return handles
end
local function anonymous_parameters(handles)
for _, handle in ipairs(handles) do
for i = 1, api.bind_parameter_count(handle) do
if api.bind_parameter_name_x(handle, i) then
return false
end
end
end
return true
end
local function named_parameters(handles)
for _, handle in ipairs(handles) do
for i = 1, api.bind_parameter_count(handle) do
if not api.bind_parameter_name_x(handle, i) then
return false
end
end
end
return true
end
local function create_mapping(handles, paranames)
local invers = { }
for index, name in ipairs(paranames) do
invers[name] = index
end
local mapping = { }
for _, handle in ipairs(handles) do
for index = 1, api.bind_parameter_count(handle) do
local parameter_name = api.bind_parameter_name_x(handle, index)
local pos = invers[parameter_name]
if pos == nil then
cleanup(handles)
return nil, "db:prepare: Unknown parameter name '" .. parameter_name .. "'in statement."
end
table.insert(mapping, pos)
end
end
return mapping
end
local function collect_parameter_names(handles)
local seen = { }
local names = { }
for _, handle in ipairs(handles) do
for index = 1, api.bind_parameter_count(handle) do
local parameter_name = api.bind_parameter_name_x(handle, index)
if not seen[parameter_name] then
table.insert(names, parameter_name)
seen[parameter_name] = true
end
end
end
return names
end
local function fix_parameter_names(unfixed_parameters)
local fixed_parameters = { }
for _, unfixed_name in ipairs(unfixed_parameters) do
local _, _, fixed_name = string.find(unfixed_name, "^[:$]?(%a%w*)$")
if not fixed_name then
return nil, "db:prepare: Invalid parameter name: '" .. unfixed_name .."'."
end
table.insert(fixed_parameters, fixed_name)
end
return fixed_parameters
end
local function create_stmt(db, handles, parameter_count)
local stmt = object()
stmt.__gc = stmt_class.close
stmt.__index = stmt_class
stmt.handles = handles
stmt.db = db
stmt.reg_id = register(db.stmts, stmt)
stmt.parameter_count= parameter_count
return stmt
end
local handles, errmsg = build_handles(sql)
if errmsg then
return nil, errmsg
end
local parameter_count = count_parameters(handles)
if parameter_count == 0 then -- No parameters at all
return create_stmt(db, handles, 0)
else
if anonymous_parameters(handles) then -- All parameters are anonymous ("?")
return create_stmt(db, handles, parameter_count)
elseif named_parameters(handles) then -- All parameters are named (":foobar" & "$foobar")
if paranames then -- Fixed mapping of parameter names
check_table(paranames, "db:prepare: Names of parameters expected as strings")
local fixed_parameter_names, errmsg = fix_parameter_names(paranames)
if errmsg then
cleanup(handles)
return nil, errmgs
end
local mapping, errmsg = create_mapping(handles, fixed_parameter_names)
if errmsg then
cleanup(handles)
return nil, errmsg
end
local stmt = create_stmt(db, handles, table.getn(fixed_parameter_names))
stmt.mapping = mapping
stmt.paranames = fixed_parameter_names
return stmt
else -- Automatic mapping of paramter names
local parameter_names = collect_parameter_names(handles)
local mapping = create_mapping(handles, parameter_names)
local stmt = create_stmt(db, handles, table.getn(parameter_names))
stmt.mapping = mapping
stmt.paranames = parameter_names
return stmt
end
else -- Mixed paramters are not allowed
cleanup(handles)
return nil, "db:prepare: Mixed anonymous and named parameters are not allowed."
end
end
end
local function call_user_func(context, func, num_values, values)
-- Don't use table.insert() because of nils in lua-5.1
local arg = { }
for index = 1, num_values do
arg[index] = api.value(values, index-1)
end
-- Make lua-5.0.2 unpack() happy
arg.n = num_values
-- lua-5.1 unpack() style / lua-5.0.2 ignores additional arguments
local ok, result = pcall(func, unpack(arg, 1, num_values))
if not ok then
api.result_error(context, tostring(result))
else
api.result(context, result)
end
end
function db_class.set_function(db, name, num_args, func)
check_db(db)
local function xfunc(context, num_values, values)
call_user_func(context, func, num_values, values)
end
local status = api.create_function(db.handle, name, num_args, xfunc, nil, nil)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.set_aggregate(db, name, num_args, create_funcs)
check_db(db)
local step, final
local function xstep(context, num_values, values)
if not step and not final then
step, final = create_funcs()
end
call_user_func(context, step, num_values, values)
end
local function xfinal(context)
local ok, result = pcall(final, api.aggregate_count(context))
if not ok then
api.result_error(context, tostring(result))
else
api.result(context, result)
end
end
local status = api.create_function(db.handle, name, num_args, nil, xstep, xfinal)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.set_trace_handler(db, func)
check_db(db)
local status = api.trace(db.handle, func)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.set_busy_timeout(db, ms)
check_db(db)
local status = api.busy_timeout(db.handle, ms)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
function db_class.set_busy_handler(db, func)
check_db(db)
local status = api.busy_handler(db.handle, func)
if is_error(status) then
return nil, errmsg(db.handle)
end
return db
end
---------------------
-- Statement Class --
---------------------
function stmt_class.bind(stmt, ...)
local function bind_with_mapping(parameters)
local mapping = stmt.mapping
local map_index = 1
for _, handle in ipairs(stmt.handles) do
for index = 1, api.bind_parameter_count(handle) do
local status = api.bind(handle, index, parameters[mapping[map_index]])
if is_error(status) then
return nil, errmsg(stmt.db.handle)
end
map_index = map_index + 1
end
end
end
local function bind_without_mapping(parameters)
local parameter_index = 1
for _, handle in ipairs(stmt.handles) do
for index = 1, api.bind_parameter_count(handle) do
local status = api.bind(handle, index, parameters[parameter_index])
if is_error(status) then
return nil, errmsg(stmt.db.handle)
end
parameter_index = parameter_index + 1
end
end
end
local function bind_by_names(parameters)
local parameter_names = stmt.paranames
local mapping = stmt.mapping
for _, handle in ipairs(stmt.handles) do
for index = 1, api.bind_parameter_count(handle) do
local status = api.bind(handle, index, parameters[parameter_names[mapping[index]]])
if is_error(status) then
return nil, errmsg(stmt.db.handle)
end
end
end
end
check_stmt(stmt)
if stmt.parameter_count == 0 then error("stmt:bind: statement contains no parameters.") end
if type(arg[1]) == "table" and arg.n == 1 and stmt.mapping and stmt.paranames then
bind_by_names(arg[1])
else
if arg.n < stmt.parameter_count then error("stmt:bind: to few parameters.") end
if arg.n > stmt.parameter_count then error("stmt:bind: to many parameters.") end
if stmt.mapping then
bind_with_mapping(arg)
else
bind_without_mapping(arg)
end
end
return stmt
end
function stmt_class.reset(stmt)
check_stmt(stmt)
for _, handle in ipairs(stmt.handles) do
api.reset(handle)
end
return stmt
end
function stmt_class.close(stmt)
check_stmt(stmt)
for _, handle in ipairs(stmt.handles) do
api.finalize(handle)
end
unregister(stmt.db.stmts, stmt.reg_id)
stmt.db = nil
stmt.handles = nil
stmt.mapping = nil
stmt.__gc = nil
return stmt
end
local no_parameter_names = { }
function stmt_class.parameter_names(stmt)
check_stmt(stmt)
if not stmt.paranames then
return no_parameter_names
else
return stmt.paranames
end
end
local function stmt_column_info(stmt, info_func)
local handle = check_single_stmt(stmt)
local info = { }
for index = 1, api.column_count(handle) do
table.insert(info, info_func(handle, index-1) )
end
return info
end
function stmt_class.column_names(stmt)
return stmt_column_info(stmt, api.column_name)
end
function stmt_class.column_decltypes(stmt)
return stmt_column_info(stmt, api.column_decltype)
end
function stmt_class.column_count(stmt)
local handle = check_single_stmt(stmt)
return api.column_count(handle)
end
function stmt_class.exec(stmt)
check_stmt(stmt)
stmt:reset()
for _, handle in ipairs(stmt.handles) do
while true do
local status = api.step(handle)
if is_error(status) then
local errmsg = errmsg(stmt.db.handle)
api.reset(handle)
return nil, errmsg
end
if is_done(status) then
break
end
end
api.reset(handle)
end
return stmt
end
local function stmt_rows(stmt, get_row_func, tab, autoclose)
local handle = check_single_stmt(stmt)
api.reset(handle)
local function check_autoclose()
if autoclose == true then
stmt:close()
else
api.reset(handle)
end
end
local iterator = function()
local status = api.step(handle)
if is_error(status) then
local errmsg = errmsg(stmt.db.handle)
check_autoclose()
return nil, errmsg
end
if is_row(status) then
return get_row_func(handle, tab)
end
if is_done(status) then
check_autoclose()
return nil
end
return nil, "stmt:rows: Internal error!"
end
return iterator
end
function stmt_class.irows(stmt, tab, autoclose)
return stmt_rows(stmt, api.irow, tab, autoclose)
end
function stmt_class.rows(stmt, tab, autoclose)
return stmt_rows(stmt, api.arow, tab, autoclose)
end
function stmt_class.cols(stmt, autoclose)
return stmt_rows(stmt, api.drow, nil, autoclose)
end
local function first_row(stmt, get_row_func, tab, autoclose)
local handle = check_single_stmt(stmt)
api.reset(handle)
local function check_autoclose()
if autoclose == true then
stmt:close()
else
api.reset(handle)
end
end
local status = api.step(handle)
if is_error(status) then
local errmsg = errmsg(stmt.db.handle)
check_autoclose()
return nil, errmsg
end
if is_row(status) then
local row = get_row_func(handle, tab)
check_autoclose()
return row
end
if is_done(status) then
check_autoclose()
return nil, "No row returned."
end
return nil, "stmt:first_row: Internal error!"
end
function stmt_class.first_irow(stmt, tab, autoclose)
return first_row(stmt, api.irow, tab, autoclose)
end
function stmt_class.first_row(stmt, tab, autoclose)
return first_row(stmt, api.arow, tab, autoclose)
end
function stmt_class.first_cols(stmt, autoclose)
local count = api.column_count(stmt.handles[1])
local row, errmsg = first_row(stmt, api.irow, nil, autoclose)
if errmsg then
return nil, errmsg
else
row.n = count -- Make lua-5.0.2 unpack() happy
return unpack(row, 1, count) -- lua-5.1 style / lua-5.0.2 ignores additional arguments
end
end