Software / code / prosody
Comparison
util/sqlite3.lua @ 12845:f306336b7e99
util.sqlite3: SQLite3-only variant of util.sql using LuaSQLite3
http://lua.sqlite.org/
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Mon, 01 Aug 2022 15:23:33 +0200 |
| child | 12847:d6cdde74cd9b |
comparison
equal
deleted
inserted
replaced
| 12844:a3ec87ad8e48 | 12845:f306336b7e99 |
|---|---|
| 1 | |
| 2 -- luacheck: ignore 113/unpack 211 212 411 213 | |
| 3 local setmetatable, getmetatable = setmetatable, getmetatable; | |
| 4 local ipairs, unpack, select = ipairs, table.unpack or unpack, select; | |
| 5 local tonumber, tostring = tonumber, tostring; | |
| 6 local assert, xpcall, debug_traceback = assert, xpcall, debug.traceback; | |
| 7 local error = error | |
| 8 local type = type | |
| 9 local t_concat = table.concat; | |
| 10 local t_insert = table.insert; | |
| 11 local s_char = string.char; | |
| 12 local log = require "util.logger".init("sql"); | |
| 13 | |
| 14 local lsqlite3 = require "lsqlite3"; | |
| 15 local build_url = require "socket.url".build; | |
| 16 local ROW, DONE = lsqlite3.ROW, lsqlite3.DONE; | |
| 17 local err2str = { | |
| 18 [0] = "OK"; | |
| 19 "ERROR"; | |
| 20 "INTERNAL"; | |
| 21 "PERM"; | |
| 22 "ABORT"; | |
| 23 "BUSY"; | |
| 24 "LOCKED"; | |
| 25 "NOMEM"; | |
| 26 "READONLY"; | |
| 27 "INTERRUPT"; | |
| 28 "IOERR"; | |
| 29 "CORRUPT"; | |
| 30 "NOTFOUND"; | |
| 31 "FULL"; | |
| 32 "CANTOPEN"; | |
| 33 "PROTOCOL"; | |
| 34 "EMPTY"; | |
| 35 "SCHEMA"; | |
| 36 "TOOBIG"; | |
| 37 "CONSTRAINT"; | |
| 38 "MISMATCH"; | |
| 39 "MISUSE"; | |
| 40 "NOLFS"; | |
| 41 [24] = "FORMAT"; | |
| 42 [25] = "RANGE"; | |
| 43 [26] = "NOTADB"; | |
| 44 [100] = "ROW"; | |
| 45 [101] = "DONE"; | |
| 46 }; | |
| 47 | |
| 48 local assert = function(cond, errno, err) | |
| 49 return assert(cond, err or err2str[errno]); | |
| 50 end | |
| 51 local _ENV = nil; | |
| 52 -- luacheck: std none | |
| 53 | |
| 54 local column_mt = {}; | |
| 55 local table_mt = {}; | |
| 56 local query_mt = {}; | |
| 57 --local op_mt = {}; | |
| 58 local index_mt = {}; | |
| 59 | |
| 60 local function is_column(x) return getmetatable(x)==column_mt; end | |
| 61 local function is_index(x) return getmetatable(x)==index_mt; end | |
| 62 local function is_table(x) return getmetatable(x)==table_mt; end | |
| 63 local function is_query(x) return getmetatable(x)==query_mt; end | |
| 64 local function Integer(n) return "Integer()" end | |
| 65 local function String(n) return "String()" end | |
| 66 | |
| 67 local function Column(definition) | |
| 68 return setmetatable(definition, column_mt); | |
| 69 end | |
| 70 local function Table(definition) | |
| 71 local c = {} | |
| 72 for i,col in ipairs(definition) do | |
| 73 if is_column(col) then | |
| 74 c[i], c[col.name] = col, col; | |
| 75 elseif is_index(col) then | |
| 76 col.table = definition.name; | |
| 77 end | |
| 78 end | |
| 79 return setmetatable({ __table__ = definition, c = c, name = definition.name }, table_mt); | |
| 80 end | |
| 81 local function Index(definition) | |
| 82 return setmetatable(definition, index_mt); | |
| 83 end | |
| 84 | |
| 85 function table_mt:__tostring() | |
| 86 local s = { 'name="'..self.__table__.name..'"' } | |
| 87 for i,col in ipairs(self.__table__) do | |
| 88 s[#s+1] = tostring(col); | |
| 89 end | |
| 90 return 'Table{ '..t_concat(s, ", ")..' }' | |
| 91 end | |
| 92 table_mt.__index = {}; | |
| 93 function table_mt.__index:create(engine) | |
| 94 return engine:_create_table(self); | |
| 95 end | |
| 96 function table_mt:__call(...) | |
| 97 -- TODO | |
| 98 end | |
| 99 function column_mt:__tostring() | |
| 100 return 'Column{ name="'..self.name..'", type="'..self.type..'" }' | |
| 101 end | |
| 102 function index_mt:__tostring() | |
| 103 local s = 'Index{ name="'..self.name..'"'; | |
| 104 for i=1,#self do s = s..', "'..self[i]:gsub("[\\\"]", "\\%1")..'"'; end | |
| 105 return s..' }'; | |
| 106 -- return 'Index{ name="'..self.name..'", type="'..self.type..'" }' | |
| 107 end | |
| 108 | |
| 109 local function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return s_char(tonumber(c,16)); end)); end | |
| 110 local function parse_url(url) | |
| 111 local scheme, secondpart, database = url:match("^([%w%+]+)://([^/]*)/?(.*)"); | |
| 112 assert(scheme, "Invalid URL format"); | |
| 113 local username, password, host, port; | |
| 114 local authpart, hostpart = secondpart:match("([^@]+)@([^@+])"); | |
| 115 if not authpart then hostpart = secondpart; end | |
| 116 if authpart then | |
| 117 username, password = authpart:match("([^:]*):(.*)"); | |
| 118 username = username or authpart; | |
| 119 password = password and urldecode(password); | |
| 120 end | |
| 121 if hostpart then | |
| 122 host, port = hostpart:match("([^:]*):(.*)"); | |
| 123 host = host or hostpart; | |
| 124 port = port and assert(tonumber(port), "Invalid URL format"); | |
| 125 end | |
| 126 return { | |
| 127 scheme = scheme:lower(); | |
| 128 username = username; password = password; | |
| 129 host = host; port = port; | |
| 130 database = #database > 0 and database or nil; | |
| 131 }; | |
| 132 end | |
| 133 | |
| 134 local engine = {}; | |
| 135 function engine:connect() | |
| 136 if self.conn then return true; end | |
| 137 | |
| 138 local params = self.params; | |
| 139 assert(params.driver == "SQLite3", "Only sqlite3 is supported"); | |
| 140 local dbh, err = lsqlite3.open(params.database); | |
| 141 if not dbh then return nil, err2str[err]; end | |
| 142 self.conn = dbh; | |
| 143 self.prepared = {}; | |
| 144 local ok, err = self:set_encoding(); | |
| 145 if not ok then | |
| 146 return ok, err; | |
| 147 end | |
| 148 local ok, err = self:onconnect(); | |
| 149 if ok == false then | |
| 150 return ok, err; | |
| 151 end | |
| 152 return true; | |
| 153 end | |
| 154 function engine:onconnect() | |
| 155 -- Override from create_engine() | |
| 156 end | |
| 157 function engine:execute(sql, ...) | |
| 158 local success, err = self:connect(); | |
| 159 if not success then return success, err; end | |
| 160 local prepared = self.prepared; | |
| 161 | |
| 162 local stmt = prepared[sql]; | |
| 163 if not stmt then | |
| 164 local err; | |
| 165 stmt, err = self.conn:prepare(sql); | |
| 166 if not stmt then return stmt, err; end | |
| 167 prepared[sql] = stmt; | |
| 168 end | |
| 169 | |
| 170 local ret = stmt:bind_values(...); | |
| 171 if ret ~= lsqlite3.OK then return nil, self.conn:errmsg(); end | |
| 172 return stmt; | |
| 173 end | |
| 174 | |
| 175 local result_mt = { | |
| 176 __index = { | |
| 177 affected = function(self) return self.__affected; end; | |
| 178 rowcount = function(self) return self.__rowcount; end; | |
| 179 }, | |
| 180 }; | |
| 181 | |
| 182 local function iterator(table) | |
| 183 local i=0; | |
| 184 return function() | |
| 185 i=i+1; | |
| 186 local item=table[i]; | |
| 187 if item ~= nil then | |
| 188 return item; | |
| 189 end | |
| 190 end | |
| 191 end | |
| 192 | |
| 193 local function debugquery(where, sql, ...) | |
| 194 local i = 0; local a = {...} | |
| 195 sql = sql:gsub("\n?\t+", " "); | |
| 196 log("debug", "[%s] %s", where, (sql:gsub("%?", function () | |
| 197 i = i + 1; | |
| 198 local v = a[i]; | |
| 199 if type(v) == "string" then | |
| 200 v = ("'%s'"):format(v:gsub("'", "''")); | |
| 201 end | |
| 202 return tostring(v); | |
| 203 end))); | |
| 204 end | |
| 205 | |
| 206 function engine:execute_query(sql, ...) | |
| 207 local prepared = self.prepared; | |
| 208 local stmt = prepared[sql]; | |
| 209 if stmt and stmt:isopen() then | |
| 210 prepared[sql] = nil; -- Can't be used concurrently | |
| 211 else | |
| 212 stmt = assert(self.conn:prepare(sql)); | |
| 213 end | |
| 214 local ret = stmt:bind_values(...); | |
| 215 if ret ~= lsqlite3.OK then error(self.conn:errmsg()); end | |
| 216 local data, ret = {} | |
| 217 while stmt:step() == ROW do | |
| 218 t_insert(data, stmt:get_values()); | |
| 219 end | |
| 220 -- FIXME Error handling, BUSY, ERROR, MISUSE | |
| 221 if stmt:reset() == lsqlite3.OK then | |
| 222 prepared[sql] = stmt; | |
| 223 end | |
| 224 return setmetatable({ __data = data }, { __index = result_mt.__index, __call = iterator(data) }); | |
| 225 end | |
| 226 function engine:execute_update(sql, ...) | |
| 227 local prepared = self.prepared; | |
| 228 local stmt = prepared[sql]; | |
| 229 if not stmt or not stmt:isopen() then | |
| 230 stmt = assert(self.conn:prepare(sql)); | |
| 231 else | |
| 232 prepared[sql] = nil; | |
| 233 end | |
| 234 local ret = stmt:bind_values(...); | |
| 235 if ret ~= lsqlite3.OK then error(self.conn:errmsg()); end | |
| 236 local rowcount = 0; | |
| 237 repeat | |
| 238 ret = stmt:step(); | |
| 239 if ret == lsqlite3.ROW then | |
| 240 rowcount = rowcount + 1; | |
| 241 end | |
| 242 until ret ~= lsqlite3.ROW; | |
| 243 local affected = self.conn:changes(); | |
| 244 if stmt:reset() == lsqlite3.OK then | |
| 245 prepared[sql] = stmt; | |
| 246 end | |
| 247 return setmetatable({ __affected = affected, __rowcount = rowcount }, result_mt); | |
| 248 end | |
| 249 engine.insert = engine.execute_update; | |
| 250 engine.select = engine.execute_query; | |
| 251 engine.delete = engine.execute_update; | |
| 252 engine.update = engine.execute_update; | |
| 253 local function debugwrap(name, f) | |
| 254 return function (self, sql, ...) | |
| 255 debugquery(name, sql, ...) | |
| 256 return f(self, sql, ...) | |
| 257 end | |
| 258 end | |
| 259 function engine:debug(enable) | |
| 260 self._debug = enable; | |
| 261 if enable then | |
| 262 engine.insert = debugwrap("insert", engine.execute_update); | |
| 263 engine.select = debugwrap("select", engine.execute_query); | |
| 264 engine.delete = debugwrap("delete", engine.execute_update); | |
| 265 engine.update = debugwrap("update", engine.execute_update); | |
| 266 else | |
| 267 engine.insert = engine.execute_update; | |
| 268 engine.select = engine.execute_query; | |
| 269 engine.delete = engine.execute_update; | |
| 270 engine.update = engine.execute_update; | |
| 271 end | |
| 272 end | |
| 273 function engine:_(word) | |
| 274 local ret = self.conn:exec(word); | |
| 275 if ret ~= lsqlite3.OK then return nil, self.conn:errmsg(); end | |
| 276 return true; | |
| 277 end | |
| 278 function engine:_transaction(func, ...) | |
| 279 if not self.conn then | |
| 280 local a,b = self:connect(); | |
| 281 if not a then return a,b; end | |
| 282 end | |
| 283 --assert(not self.__transaction, "Recursive transactions not allowed"); | |
| 284 local ok, err = self:_"BEGIN"; | |
| 285 if not ok then return ok, err; end | |
| 286 self.__transaction = true; | |
| 287 local success, a, b, c = xpcall(func, debug_traceback, ...); | |
| 288 self.__transaction = nil; | |
| 289 if success then | |
| 290 log("debug", "SQL transaction success [%s]", tostring(func)); | |
| 291 local ok, err = self:_"COMMIT"; | |
| 292 if not ok then return ok, err; end -- commit failed | |
| 293 return success, a, b, c; | |
| 294 else | |
| 295 log("debug", "SQL transaction failure [%s]: %s", tostring(func), a); | |
| 296 if self.conn then self:_"ROLLBACK"; end | |
| 297 return success, a; | |
| 298 end | |
| 299 end | |
| 300 function engine:transaction(...) | |
| 301 local ok, ret = self:_transaction(...); | |
| 302 if not ok then | |
| 303 local conn = self.conn; | |
| 304 if not conn or not conn:isopen() then | |
| 305 self.conn = nil; | |
| 306 ok, ret = self:_transaction(...); | |
| 307 end | |
| 308 end | |
| 309 return ok, ret; | |
| 310 end | |
| 311 function engine:_create_index(index) | |
| 312 local sql = "CREATE INDEX IF NOT EXISTS \""..index.name.."\" ON \""..index.table.."\" ("; | |
| 313 for i=1,#index do | |
| 314 sql = sql.."\""..index[i].."\""; | |
| 315 if i ~= #index then sql = sql..", "; end | |
| 316 end | |
| 317 sql = sql..");" | |
| 318 if index.unique then | |
| 319 sql = sql:gsub("^CREATE", "CREATE UNIQUE"); | |
| 320 end | |
| 321 if self._debug then | |
| 322 debugquery("create", sql); | |
| 323 end | |
| 324 return self:execute(sql); | |
| 325 end | |
| 326 function engine:_create_table(table) | |
| 327 local sql = "CREATE TABLE IF NOT EXISTS \""..table.name.."\" ("; | |
| 328 for i,col in ipairs(table.c) do | |
| 329 local col_type = col.type; | |
| 330 sql = sql.."\""..col.name.."\" "..col_type; | |
| 331 if col.nullable == false then sql = sql.." NOT NULL"; end | |
| 332 if col.primary_key == true then sql = sql.." PRIMARY KEY"; end | |
| 333 if col.auto_increment == true then | |
| 334 sql = sql.." AUTOINCREMENT"; | |
| 335 end | |
| 336 if i ~= #table.c then sql = sql..", "; end | |
| 337 end | |
| 338 sql = sql.. ");" | |
| 339 if self._debug then | |
| 340 debugquery("create", sql); | |
| 341 end | |
| 342 local success,err = self:execute(sql); | |
| 343 if not success then return success,err; end | |
| 344 for i,v in ipairs(table.__table__) do | |
| 345 if is_index(v) then | |
| 346 self:_create_index(v); | |
| 347 end | |
| 348 end | |
| 349 return success; | |
| 350 end | |
| 351 function engine:set_encoding() -- to UTF-8 | |
| 352 return self:transaction(function() | |
| 353 for encoding in self:select"PRAGMA encoding;" do | |
| 354 if encoding[1] == "UTF-8" then | |
| 355 self.charset = "utf8"; | |
| 356 end | |
| 357 end | |
| 358 end); | |
| 359 end | |
| 360 local engine_mt = { __index = engine }; | |
| 361 | |
| 362 local function db2uri(params) | |
| 363 return build_url{ | |
| 364 scheme = params.driver, | |
| 365 user = params.username, | |
| 366 password = params.password, | |
| 367 host = params.host, | |
| 368 port = params.port, | |
| 369 path = params.database, | |
| 370 }; | |
| 371 end | |
| 372 | |
| 373 local function create_engine(_, params, onconnect) | |
| 374 assert(params.driver == "SQLite3", "Only SQLite3 is supported without LuaDBI"); | |
| 375 return setmetatable({ url = db2uri(params), params = params, onconnect = onconnect }, engine_mt); | |
| 376 end | |
| 377 | |
| 378 return { | |
| 379 is_column = is_column; | |
| 380 is_index = is_index; | |
| 381 is_table = is_table; | |
| 382 is_query = is_query; | |
| 383 Integer = Integer; | |
| 384 String = String; | |
| 385 Column = Column; | |
| 386 Table = Table; | |
| 387 Index = Index; | |
| 388 create_engine = create_engine; | |
| 389 db2uri = db2uri; | |
| 390 }; |