-
Notifications
You must be signed in to change notification settings - Fork 41
/
sqlite.lua
164 lines (128 loc) · 5.14 KB
/
sqlite.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
-- sqlite.lua
-- Declares the cSQLite class representing a single SQLite database connection, with common operations
local cSQLite = {}
cSQLite.__index = cSQLite
--- Creates the table of the specified name and columns[]
-- If the table exists, any columns missing are added; existing data is kept
function cSQLite:CreateDBTable(a_TableName, a_Columns)
-- Check params:
assert(self ~= nil)
assert(a_TableName ~= nil)
assert(a_Columns ~= nil)
-- Try to create the table first
local sql = "CREATE TABLE IF NOT EXISTS '" .. a_TableName .. "' ("
sql = sql .. table.concat(a_Columns, ", ") .. ")"
local ExecResult = self.DB:exec(sql)
if (ExecResult ~= sqlite3.OK) then
LOGWARNING(self.PluginPrefix .. "Cannot create DB Table " .. a_TableName .. ": " .. ExecResult)
LOGWARNING(self.PluginPrefix .. "Command: \"" .. sql .. "\".")
return false
end
-- SQLite doesn't inform us if it created the table or not, so we have to continue anyway
-- Check each column whether it exists
-- Remove all the existing columns from a_Columns:
local RemoveExistingColumn = function(a_Values)
if (a_Values.name ~= nil) then
local ColumnName = a_Values.name:lower()
-- Search the a_Columns if they have that column:
for j = 1, #a_Columns do
-- Cut away all column specifiers (after the first space), if any:
local SpaceIdx = string.find(a_Columns[j], " ")
if (SpaceIdx ~= nil) then
SpaceIdx = SpaceIdx - 1
end
local ColumnTemplate = string.lower(string.sub(a_Columns[j], 1, SpaceIdx))
-- If it is a match, remove from a_Columns:
if (ColumnTemplate == ColumnName) then
table.remove(a_Columns, j)
break -- for j
end
end -- for j - a_Columns[]
end -- if (a_Values.name ~= nil)
end
if (not(self:ExecuteStatement("PRAGMA table_info(" .. a_TableName .. ")", {}, RemoveExistingColumn))) then
LOGWARNING(self.PluginPrefix .. "Cannot query DB table structure")
return false
end
-- Create the missing columns
-- a_Columns now contains only those columns that are missing in the DB
if (#a_Columns > 0) then
LOGINFO(self.PluginPrefix .. "Database table \"" .. a_TableName .. "\" is missing " .. #a_Columns .. " columns, fixing now.")
for idx, ColumnName in ipairs(a_Columns) do
if (not(self:ExecuteStatement("ALTER TABLE '" .. a_TableName .. "' ADD COLUMN '" .. ColumnName .. "'", {}))) then
LOGWARNING(self.PluginPrefix .. "Cannot add DB table \"" .. a_TableName .. "\" column \"" .. ColumnName .. "\"")
return false
end
end
LOGINFO(self.PluginPrefix .. "Database table \"" .. a_TableName .. "\" columns fixed.")
end
return true
end
--- Executes the SQL statement, substituting "?" in the SQL with the specified params
-- Calls a_Callback for each row
-- The callback receives a dictionary table containing the row values (stmt:nrows())
-- Returns false and error message on failure, or true on success
function cSQLite:ExecuteStatement(a_SQL, a_Params, a_Callback, a_RowIDCallback)
-- Check params:
assert(self ~= nil)
assert(a_SQL ~= nil)
assert(a_Params ~= nil)
assert(self.DB ~= nil)
assert((a_Callback == nil) or (type(a_Callback) == "function"))
assert((a_RowIDCallback == nil) or (type(a_RowIDCallback) == "function"))
-- Prepare the statement (SQL-compile):
local Stmt, ErrCode, ErrMsg = self.DB:prepare(a_SQL)
if (Stmt == nil) then
ErrMsg = (ErrCode or "<unknown>") .. " (" .. (ErrMsg or "<no message>") .. ")"
LOGWARNING(self.PluginPrefix .. "Cannot prepare SQL \"" .. a_SQL .. "\": " .. ErrMsg)
LOGWARNING(self.PluginPrefix .. " Params = {" .. table.concat(a_Params, ", ") .. "}")
return nil, ErrMsg
end
-- Bind the values into the statement:
ErrCode = Stmt:bind_values(unpack(a_Params))
if ((ErrCode ~= sqlite3.OK) and (ErrCode ~= sqlite3.DONE)) then
ErrMsg = (ErrCode or "<unknown>") .. " (" .. (self.DB:errmsg() or "<no message>") .. ")"
LOGWARNING(self.PluginPrefix .. "Cannot bind values to statement \"" .. a_SQL .. "\": " .. ErrMsg)
Stmt:finalize()
return nil, ErrMsg
end
-- Step the statement:
if (a_Callback == nil) then
ErrCode = Stmt:step()
if ((ErrCode ~= sqlite3.ROW) and (ErrCode ~= sqlite3.DONE)) then
ErrMsg = (ErrCode or "<unknown>") .. " (" .. (self.DB:errmsg() or "<no message>") .. ")"
LOGWARNING(self.PluginPrefix .. "Cannot step statement \"" .. a_SQL .. "\": " .. ErrMsg)
Stmt:finalize()
return nil, ErrMsg
end
if (a_RowIDCallback ~= nil) then
a_RowIDCallback(self.DB:last_insert_rowid())
end
else
-- Iterate over all returned rows:
for v in Stmt:nrows() do
a_Callback(v)
end
if (a_RowIDCallback ~= nil) then
a_RowIDCallback(self.DB:last_insert_rowid())
end
end
Stmt:finalize()
return true
end
--- Returns a new cSQLite instance with the database connection open to the specified file
-- Returns false and a reason string on failure
function NewSQLiteDB(a_FileName)
-- Create a new instance:
local res = {}
setmetatable(res, cSQLite)
-- cSQLite.__index = cSQLite
res.PluginPrefix = cRoot:Get():GetPluginManager():GetCurrentPlugin():GetName() .. ": "
-- Open the DB file:
local ErrMsg
res.DB, ErrMsg = sqlite3.open(a_FileName)
if not(res.DB) then
return false, (ErrMsg or "<no details>")
end
return res
end