|
1 |
|
2 -- luacheck: ignore 212/self |
|
3 |
|
4 local sql = require "util.sql"; |
|
5 local xml_parse = require "util.xml".parse; |
|
6 local resolve_relative_path = require "util.paths".resolve_relative_path; |
|
7 local stanza_preserialize = require "util.stanza".preserialize; |
|
8 |
|
9 local unpack = unpack |
|
10 local function iterator(result) |
|
11 return function(result_) |
|
12 local row = result_(); |
|
13 if row ~= nil then |
|
14 return unpack(row); |
|
15 end |
|
16 end, result, nil; |
|
17 end |
|
18 |
|
19 local default_params = { driver = "SQLite3" }; |
|
20 |
|
21 local engine; |
|
22 |
|
23 local host = module.host; |
|
24 local user, store; |
|
25 |
|
26 local function keyval_store_get() |
|
27 if store == "accounts" then |
|
28 --for row in engine:select("SELECT `password`,`created_at` FROM `users` WHERE `username`=?", user or "") do |
|
29 local result; |
|
30 for row in engine:select("SELECT `password` FROM `users` WHERE `username`=? LIMIT 1", user or "") do result = row end |
|
31 local password = result[1]; |
|
32 --local created_at = result[2]; |
|
33 return { password = password }; |
|
34 |
|
35 elseif store == "roster" then |
|
36 local roster = {}; |
|
37 local pending = nil; |
|
38 --for row in engine:select("SELECT `jid`,`nick`,`subscription`,`ask`,`askmessage`,`server`,`subscribe`,`type`,`created_at` FROM `rosterusers` WHERE `username`=?", user or "") do |
|
39 for row in engine:select("SELECT `jid`,`nick`,`subscription`,`ask` FROM `rosterusers` WHERE `username`=?", user or "") do |
|
40 local contact = row[1]; |
|
41 local name = row[2]; |
|
42 if name == "" then name = nil; end |
|
43 local subscription = row[3]; |
|
44 if subscription == "N" then |
|
45 subscription = "none" |
|
46 elseif subscription == "B" then |
|
47 subscription = "both" |
|
48 elseif subscription == "F" then |
|
49 subscription = "from" |
|
50 elseif subscription == "T" then |
|
51 subscription = "to" |
|
52 else error("Unknown subscription type: "..subscription) end; |
|
53 local ask = row[4]; |
|
54 if ask == "N" then |
|
55 ask = nil; |
|
56 elseif ask == "O" then |
|
57 ask = "subscribe"; |
|
58 elseif ask == "I" then |
|
59 if pending == nil then pending = {} end; |
|
60 pending[contact] = true; |
|
61 ask = nil; |
|
62 elseif ask == "B" then |
|
63 if pending == nil then pending = {} end; |
|
64 pending[contact] = true; |
|
65 ask = "subscribe"; |
|
66 else error("Unknown ask type: "..ask); end |
|
67 |
|
68 --local askmessage = row[5]; |
|
69 --local server = row[6]; |
|
70 --local subscribe = row[7]; |
|
71 --local type = row[8]; |
|
72 --local created_at = row[9]; |
|
73 |
|
74 local groups = {}; |
|
75 for row in engine:select("SELECT `grp` FROM `rostergroups` WHERE `username`=? AND `jid`=?", user or "", contact) do |
|
76 local group = row[1]; |
|
77 groups[group] = true; |
|
78 end |
|
79 |
|
80 roster[contact] = { name = name, ask = ask, subscription = subscription, groups = groups }; |
|
81 end |
|
82 return roster; |
|
83 |
|
84 elseif store == "vcard" then |
|
85 local result = nil; |
|
86 for row in engine:select("SELECT `vcard` FROM `vcard` WHERE `username`=? LIMIT 1", user or "") do result = row end |
|
87 if not result then |
|
88 return nil; |
|
89 end |
|
90 local data, err = xml_parse(result[1]); |
|
91 if data then |
|
92 return stanza_preserialize(data); |
|
93 end |
|
94 |
|
95 elseif store == "private" then |
|
96 local private = nil; |
|
97 local result; |
|
98 for row in engine:select("SELECT `namespace`,`data` FROM `private_storage` WHERE `username`=?", user or "") do |
|
99 if private == nil then private = {} end; |
|
100 local namespace = row[1]; |
|
101 local data, err = xml_parse(row[2]); |
|
102 if data then |
|
103 private[namespace] = stanza_preserialize(data); |
|
104 end |
|
105 end |
|
106 return private; |
|
107 end |
|
108 end |
|
109 |
|
110 --- Key/value store API (default store type) |
|
111 |
|
112 local keyval_store = {}; |
|
113 keyval_store.__index = keyval_store; |
|
114 function keyval_store:get(username) |
|
115 user, store = username, self.store; |
|
116 local ok, result = engine:transaction(keyval_store_get); |
|
117 if not ok then |
|
118 module:log("error", "Unable to read from database %s store for %s: %s", store, username or "<host>", result); |
|
119 return nil, result; |
|
120 end |
|
121 return result; |
|
122 end |
|
123 |
|
124 function keyval_store:users() |
|
125 local ok, result = engine:transaction(function() |
|
126 return engine:select("SELECT `username` FROM `users`"); |
|
127 end); |
|
128 if not ok then return ok, result end |
|
129 return iterator(result); |
|
130 end |
|
131 |
|
132 local stores = { |
|
133 keyval = keyval_store; |
|
134 }; |
|
135 |
|
136 --- Implement storage driver API |
|
137 |
|
138 -- FIXME: Some of these operations need to operate on the archive store(s) too |
|
139 |
|
140 local driver = {}; |
|
141 |
|
142 function driver:open(store, typ) |
|
143 local store_mt = stores[typ or "keyval"]; |
|
144 if store_mt then |
|
145 return setmetatable({ store = store }, store_mt); |
|
146 end |
|
147 return nil, "unsupported-store"; |
|
148 end |
|
149 |
|
150 function driver:stores(username) |
|
151 local query = "SELECT 'accounts', 'roster', 'vcard', 'private'"; |
|
152 if username == true or not username then |
|
153 username = ""; |
|
154 end |
|
155 local ok, result = engine:transaction(function() |
|
156 return engine:select(query, host, username); |
|
157 end); |
|
158 if not ok then return ok, result end |
|
159 return iterator(result); |
|
160 end |
|
161 |
|
162 --- Initialization |
|
163 |
|
164 |
|
165 local function normalize_params(params) |
|
166 if params.driver == "SQLite3" then |
|
167 if params.database ~= ":memory:" then |
|
168 params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); |
|
169 end |
|
170 end |
|
171 assert(params.driver and params.database, "Configuration error: Both the SQL driver and the database need to be specified"); |
|
172 return params; |
|
173 end |
|
174 |
|
175 function module.load() |
|
176 if prosody.prosodyctl then return; end |
|
177 local engines = module:shared("/*/sql/connections"); |
|
178 local params = normalize_params(module:get_option("sql", default_params)); |
|
179 engine = engines[sql.db2uri(params)]; |
|
180 if not engine then |
|
181 module:log("debug", "Creating new engine"); |
|
182 engine = sql:create_engine(params); |
|
183 engines[sql.db2uri(params)] = engine; |
|
184 end |
|
185 |
|
186 module:provides("storage", driver); |
|
187 end |