da3bc2bffeb16eae2b26d95d0102e2412021f813
[exim.git] / src / src / hintsdb / hints_sqlite.h
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* This header file contains macro definitions for one possible hintsdb
11 backend provider. */
12
13 /* ********************* sqlite3 interface ************************ */
14
15 # include <sqlite3.h>
16
17 /* Basic DB type */
18 # define EXIM_DB sqlite3
19
20 # define EXIM_CURSOR int
21
22 # /* The datum type used for queries */
23 # define EXIM_DATUM blob
24
25 /* Some text for messages */
26 # define EXIM_DBTYPE "sqlite3"
27
28 # /* Access functions */
29
30 static inline BOOL
31 exim_lockfile_needed(void)
32 {
33 return FALSE;   /* We do transaction; no extra locking needed */
34 }
35
36 /* EXIM_DBOPEN - return pointer to an EXIM_DB, NULL if failed */
37 static inline EXIM_DB *
38 exim_dbopen_multi(const uschar * name, const uschar * dirname, int flags,
39   unsigned mode)
40 {
41 EXIM_DB * dbp;
42 int ret, sflags = flags & O_RDWR ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY;
43 if (flags & O_CREAT) sflags |= SQLITE_OPEN_CREATE;
44 if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK)
45   {
46   sqlite3_busy_timeout(dbp, 5000);
47   if (flags & O_CREAT)
48     ret = sqlite3_exec(dbp,
49             "CREATE TABLE IF NOT EXISTS tbl (ky TEXT PRIMARY KEY, dat BLOB);",
50             NULL, NULL, NULL);
51   if (ret != SQLITE_OK)
52     sqlite3_close(dbp);
53   }
54 //else
55 //  fprintf(stderr, "sqlite3_open_v2: %s\n", sqlite3_errmsg(dbp));
56 return ret == SQLITE_OK ? dbp : NULL;
57 }
58
59 static inline BOOL
60 exim_dbtransaction_start(EXIM_DB * dbp)
61 {
62 return sqlite3_exec(dbp, "BEGIN TRANSACTION;", NULL, NULL, NULL) == SQLITE_OK;
63 }
64
65 static inline EXIM_DB *
66 exim_dbopen__(const uschar * name, const uschar * dirname, int flags,
67   unsigned mode)
68 {
69 EXIM_DB * dbp = exim_dbopen_multi(name, dirname, flags, mode);
70 if (!dbp || exim_dbtransaction_start(dbp))
71   return dbp;
72 sqlite3_close(dbp);
73 return NULL;
74 }
75
76 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
77 /* note we alloc'n'copy - the caller need not do so */
78 /* result has a NUL appended, but the length is as per the DB */
79
80 static inline BOOL
81 exim_dbget__(EXIM_DB * dbp, const uschar * s, EXIM_DATUM * res)
82 {
83 sqlite3_stmt * statement;
84 int ret;
85
86 res->len = (size_t) -1;
87 /* fprintf(stderr, "exim_dbget__(%s)\n", s); */
88 if ((ret = sqlite3_prepare_v2(dbp, CCS s, -1, &statement, NULL)) != SQLITE_OK)
89   {
90 /* fprintf(stderr, "prepare fail: %s\n", sqlite3_errmsg(dbp)); */
91   return FALSE;
92   }
93 if (sqlite3_step(statement) != SQLITE_ROW)
94   {
95 /* fprintf(stderr, "step fail: %s\n", sqlite3_errmsg(dbp)); */
96   sqlite3_finalize(statement);
97   return FALSE;
98   }
99
100 res->len = sqlite3_column_bytes(statement, 0);
101 # ifdef COMPILE_UTILITY
102 if (!(res->data = malloc(res->len +1)))
103   { sqlite3_finalize(statement); return FALSE; }
104 # else
105 res->data = store_get(res->len +1, GET_TAINTED);
106 # endif
107 memcpy(res->data, sqlite3_column_blob(statement, 0), res->len);
108 res->data[res->len] = '\0';
109 /* fprintf(stderr, "res %d bytes: '%.*s'\n", (int)res->len, (int)res->len, res->data); */
110 sqlite3_finalize(statement);
111 return TRUE;
112 }
113
114 static inline BOOL
115 exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res)
116 {
117 # define FMT "SELECT dat FROM tbl WHERE ky = '%.*s';"
118 uschar * qry;
119 int i;
120 BOOL ret;
121
122 # ifdef COMPILE_UTILITY
123 /* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
124 i = snprintf(NULL, 0, FMT, (int) key->len, key->data)+1;
125 if (!(qry = malloc(i)))
126   return FALSE;
127 snprintf(CS qry, i, FMT, (int) key->len, key->data);
128 ret = exim_dbget__(dbp, qry, res);
129 free(qry);
130 # else
131 /* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
132 qry = string_sprintf(FMT, (int) key->len, key->data);
133 ret = exim_dbget__(dbp, qry, res);
134 # endif
135
136 return ret;
137 # undef FMT
138 }
139
140 /* Note that we return claiming a duplicate record for any error.
141 It seem not uncommon to get a "database is locked" error. */
142 # define EXIM_DBPUTB_OK  0
143 # define EXIM_DBPUTB_DUP (-1)
144
145 static inline int
146 exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt)
147 {
148 int hlen = data->len * 2, off = 0, res;
149 # define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%.*s', X'%.*s');"
150 uschar * qry;
151 # ifdef COMPILE_UTILITY
152 uschar * hex = malloc(hlen+1);
153 if (!hex) return EXIM_DBPUTB_DUP;       /* best we can do */
154 # else
155 uschar * hex = store_get(hlen+1, data->data);
156 # endif
157
158 for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2)
159   sprintf(CS hex + off, "%02X", *s);
160
161 # ifdef COMPILE_UTILITY
162 res = snprintf(CS hex, 0, FMT, alt, (int) key->len, key->data, hlen, hex) +1;
163 if (!(qry = malloc(res))) return EXIM_DBPUTB_DUP;
164 snprintf(CS qry, res, FMT, alt, (int) key->len, key->data, hlen, hex);
165 /* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
166 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
167 free(qry);
168 free(hex);
169 # else
170 qry = string_sprintf(FMT, alt, (int) key->len, key->data, hlen, hex);
171 /* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
172 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
173 /* fprintf(stderr, "exim_s_dbp res %d\n", res); */
174 # endif
175
176 # ifdef COMPILE_UTILITY
177 if (res != SQLITE_OK)
178   fprintf(stderr, "sqlite3_exec: %s\n", sqlite3_errmsg(dbp));
179 # endif
180
181 return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP;
182 # undef FMT
183 }
184
185 /* EXIM_DBPUT - returns nothing useful, assumes replace mode */
186
187 static inline int
188 exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
189 {
190 /* fprintf(stderr, "exim_dbput()\n"); */
191 (void) exim_s_dbp(dbp, key, data, US"REPLACE");
192 return 0;
193 }
194
195 /* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
196
197 /* Returns from EXIM_DBPUTB */
198
199 static inline int
200 exim_dbputb(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
201 {
202 return exim_s_dbp(dbp, key, data, US"ABORT");
203 }
204
205 /* EXIM_DBDEL */
206 static inline int
207 exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key)
208 {
209 # define FMT "DELETE FROM tbl WHERE ky = '%.*s';"
210 uschar * qry;
211 int res;
212
213 # ifdef COMPILE_UTILITY
214 res = snprintf(NULL, 0, FMT, (int) key->len, key->data) +1; /* res includes nul */
215 if (!(qry = malloc(res))) return SQLITE_NOMEM;
216 snprintf(CS qry, res, FMT, (int) key->len, key->data);
217 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
218 free(qry);
219 # else
220 qry = string_sprintf(FMT, (int) key->len, key->data);
221 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
222 # endif
223
224 return res;
225 # undef FMT
226 }
227
228
229 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
230 /* Cursors are inefficiently emulated by repeating searches */
231
232 static inline EXIM_CURSOR *
233 exim_dbcreate_cursor(EXIM_DB * dbp)
234 {
235 # ifdef COMPILE_UTILITY
236 EXIM_CURSOR * c = malloc(sizeof(int));
237 if (!c) return NULL;
238 # else
239 EXIM_CURSOR * c = store_malloc(sizeof(int));
240 # endif
241 *c = 0;
242 return c;
243 }
244
245 /* EXIM_DBSCAN */
246 /* Note that we return the (next) key, not the record value */
247 static inline BOOL
248 exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res, BOOL first,
249   EXIM_CURSOR * cursor)
250 {
251 # define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;"
252 uschar * qry;
253 int i;
254 BOOL ret;
255
256 # ifdef COMPILE_UTILITY
257 i = snprintf(NULL, 0, FMT, *cursor)+1;
258 if (!(qry = malloc(i))) return FALSE;
259 snprintf(CS qry, i, FMT, *cursor);
260 /* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
261 ret = exim_dbget__(dbp, qry, key);
262 free(qry);
263 /* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
264 # else
265 qry = string_sprintf(FMT, *cursor);
266 /* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
267 ret = exim_dbget__(dbp, qry, key);
268 /* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
269 # endif
270 if (ret) *cursor = *cursor + 1;
271 return ret;
272 # undef FMT
273 }
274
275 /* EXIM_DBDELETE_CURSOR - terminate scanning operation. */
276 static inline void
277 exim_dbdelete_cursor(EXIM_CURSOR * cursor)
278 {
279 # ifdef COMPILE_UTILITY
280 free(cursor);
281 # else
282 store_free(cursor);
283 # endif
284 }
285
286
287 /* EXIM_DBCLOSE */
288 static inline void
289 exim_dbclose_multi(EXIM_DB * dbp)
290 {
291 sqlite3_close(dbp);
292 }
293 static inline void
294 exim_dbtransaction_commit(EXIM_DB * dbp)
295 {
296 (void) sqlite3_exec(dbp, "COMMIT TRANSACTION;", NULL, NULL, NULL);
297 }
298 static inline void
299 exim_dbclose__(EXIM_DB * dbp)
300 {
301 exim_dbtransaction_commit(dbp);
302 exim_dbclose_multi(dbp);
303 }
304
305
306 /* Datum access */
307
308 static uschar *
309 exim_datum_data_get(EXIM_DATUM * dp)
310 { return US dp->data; }
311 static void
312 exim_datum_data_set(EXIM_DATUM * dp, void * s)
313 { dp->data = s; }
314  
315 static unsigned
316 exim_datum_size_get(EXIM_DATUM * dp)
317 { return dp->len; }
318 static void
319 exim_datum_size_set(EXIM_DATUM * dp, unsigned n)
320 { dp->len = n; }
321
322
323
324 static inline void
325 exim_datum_init(EXIM_DATUM * dp)
326 { dp->data = NULL; }                    /* compiler quietening */
327
328 /* No free needed for a datum */
329
330 static inline void
331 exim_datum_free(EXIM_DATUM * dp)
332 { }
333
334 /* size limit */
335
336 # define EXIM_DB_RLIMIT 150
337
338
339 /* End of hints_sqlite.h */
340 /* vi: aw ai sw=2
341 */