21ebd4f01351dbb76d0f4731b2fd5ada7d051359
[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 res->data = malloc(res->len);
103 # else
104 res->data = store_get(res->len, GET_TAINTED);
105 # endif
106 memcpy(res->data, sqlite3_column_blob(statement, 0), res->len);
107 res->data[res->len] = '\0';
108 /* fprintf(stderr, "res %d bytes: '%.*s'\n", (int)res->len, (int)res->len, res->data); */
109 sqlite3_finalize(statement);
110 return TRUE;
111 }
112
113 static inline BOOL
114 exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res)
115 {
116 # define FMT "SELECT dat FROM tbl WHERE ky = '%.*s';"
117 uschar * qry;
118 int i;
119 BOOL ret;
120
121 # ifdef COMPILE_UTILITY
122 /* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
123 qry = malloc(i = snprintf(NULL, 0, FMT, (int) key->len, key->data));
124 snprintf(CS qry, i, FMT, (int) key->len, key->data);
125 ret = exim_dbget__(dbp, qry, res);
126 free(qry);
127 # else
128 /* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
129 qry = string_sprintf(FMT, (int) key->len, key->data);
130 ret = exim_dbget__(dbp, qry, res);
131 # endif
132
133 return ret;
134 # undef FMT
135 }
136
137 /**/
138 # define EXIM_DBPUTB_OK  0
139 # define EXIM_DBPUTB_DUP (-1)
140
141 static inline int
142 exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt)
143 {
144 int hlen = data->len * 2, off = 0, res;
145 # define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%.*s', X'%.*s');"
146 # ifdef COMPILE_UTILITY
147 uschar * hex = malloc(hlen+1);
148 # else
149 uschar * hex = store_get(hlen+1, data->data);
150 # endif
151 uschar * qry;
152
153 for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2)
154   sprintf(CS hex + off, "%02X", *s);
155
156 # ifdef COMPILE_UTILITY
157 res = snprintf(CS hex, 0, FMT, alt, (int) key->len, key->data, hlen, hex);
158 qry = malloc(res);
159 snprintf(CS qry, res, FMT, alt, (int) key->len, key->data, hlen, hex);
160 /* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
161 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
162 free(qry);
163 free(hex);
164 # else
165 qry = string_sprintf(FMT, alt, (int) key->len, key->data, hlen, hex);
166 /* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
167 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
168 /* fprintf(stderr, "exim_s_dbp res %d\n", res); */
169 # endif
170
171 if (res != SQLITE_OK)
172   fprintf(stderr, "sqlite3_exec: %s\n", sqlite3_errmsg(dbp));
173
174 return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP;
175 # undef FMT
176 }
177
178 /* EXIM_DBPUT - returns nothing useful, assumes replace mode */
179
180 static inline int
181 exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
182 {
183 /* fprintf(stderr, "exim_dbput()\n"); */
184 (void) exim_s_dbp(dbp, key, data, US"REPLACE");
185 return 0;
186 }
187
188 /* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
189
190 /* Returns from EXIM_DBPUTB */
191
192 static inline int
193 exim_dbputb(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
194 {
195 return exim_s_dbp(dbp, key, data, US"ABORT");
196 }
197
198 /* EXIM_DBDEL */
199 static inline int
200 exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key)
201 {
202 # define FMT "DELETE FROM tbl WHERE ky = '%.*s';"
203 uschar * qry;
204 int res;
205
206 # ifdef COMPILE_UTILITY
207 res = snprintf(NULL, 0, FMT, (int) key->len, key->data); /* res excludes nul */
208 qry = malloc(res);
209 snprintf(CS qry, res, FMT, (int) key->len, key->data);
210 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
211 free(qry);
212 # else
213 qry = string_sprintf(FMT, (int) key->len, key->data);
214 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
215 # endif
216
217 return res;
218 # undef FMT
219 }
220
221
222 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
223 /* Cursors are inefficiently emulated by repeating searches */
224
225 static inline EXIM_CURSOR *
226 exim_dbcreate_cursor(EXIM_DB * dbp)
227 {
228 # ifdef COMPILE_UTILITY
229 EXIM_CURSOR * c = malloc(sizeof(int));
230 # else
231 EXIM_CURSOR * c = store_malloc(sizeof(int));
232 # endif
233 *c = 0;
234 return c;
235 }
236
237 /* EXIM_DBSCAN */
238 /* Note that we return the (next) key, not the record value */
239 static inline BOOL
240 exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res, BOOL first,
241   EXIM_CURSOR * cursor)
242 {
243 # define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;"
244 uschar * qry;
245 int i;
246 BOOL ret;
247
248 # ifdef COMPILE_UTILITY
249 if (!(qry = malloc((i = snprintf(NULL, 0, FMT, *cursor))+1))) return FALSE;
250 snprintf(CS qry, i, FMT, *cursor);
251 /* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
252 ret = exim_dbget__(dbp, qry, key);
253 free(qry);
254 /* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
255 # else
256 qry = string_sprintf(FMT, *cursor);
257 /* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
258 ret = exim_dbget__(dbp, qry, key);
259 /* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
260 # endif
261 if (ret) *cursor = *cursor + 1;
262 return ret;
263 # undef FMT
264 }
265
266 /* EXIM_DBDELETE_CURSOR - terminate scanning operation. */
267 static inline void
268 exim_dbdelete_cursor(EXIM_CURSOR * cursor)
269 {
270 # ifdef COMPILE_UTILITY
271 free(cursor);
272 # else
273 store_free(cursor);
274 # endif
275 }
276
277
278 /* EXIM_DBCLOSE */
279 static inline void
280 exim_dbclose_multi(EXIM_DB * dbp)
281 {
282 sqlite3_close(dbp);
283 }
284 static inline void
285 exim_dbtransaction_commit(EXIM_DB * dbp)
286 {
287 (void) sqlite3_exec(dbp, "COMMIT TRANSACTION;", NULL, NULL, NULL);
288 }
289 static inline void
290 exim_dbclose__(EXIM_DB * dbp)
291 {
292 exim_dbtransaction_commit(dbp);
293 exim_dbclose_multi(dbp);
294 }
295
296
297 /* Datum access */
298
299 static uschar *
300 exim_datum_data_get(EXIM_DATUM * dp)
301 { return US dp->data; }
302 static void
303 exim_datum_data_set(EXIM_DATUM * dp, void * s)
304 { dp->data = s; }
305  
306 static unsigned
307 exim_datum_size_get(EXIM_DATUM * dp)
308 { return dp->len; }
309 static void
310 exim_datum_size_set(EXIM_DATUM * dp, unsigned n)
311 { dp->len = n; }
312
313
314
315 static inline void
316 exim_datum_init(EXIM_DATUM * dp)
317 { dp->data = NULL; }                    /* compiler quietening */
318
319 /* No free needed for a datum */
320
321 static inline void
322 exim_datum_free(EXIM_DATUM * dp)
323 { }
324
325 /* size limit */
326
327 # define EXIM_DB_RLIMIT 150
328
329
330 /* End of hints_sqlite.h */
331 /* vi: aw ai sw=2
332 */