comparison database.c @ 0:15a06aa20298

misc: initial import
author David Demelier <markand@malikania.fr>
date Tue, 04 Feb 2020 13:35:52 +0100
parents
children 836a698946f8
comparison
equal deleted inserted replaced
-1:000000000000 0:15a06aa20298
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5
6 #include <sqlite3.h>
7
8 #include "database.h"
9 #include "log.h"
10 #include "paste.h"
11 #include "util.h"
12
13 /* sqlite3 use const unsigned char *. */
14 #define DUP(s) estrdup((const char *)(s))
15
16 static sqlite3 *db;
17
18 static const char *sql_init =
19 "BEGIN EXCLUSIVE TRANSACTION;\n"
20 "\n"
21 "CREATE TABLE IF NOT EXISTS paste(\n"
22 " uuid TEXT PRIMARY KEY,\n"
23 " title TEXT,\n"
24 " author TEXT,\n"
25 " language TEXT,\n"
26 " code TEXT,\n"
27 " date INT DEFAULT CURRENT_TIMESTAMP,\n"
28 " visible INTEGER DEFAULT 1,\n"
29 " duration INT\n"
30 ");\n"
31 "\n"
32 "END TRANSACTION";
33
34 static const char *sql_get =
35 "SELECT uuid\n"
36 " , title\n"
37 " , author\n"
38 " , language\n"
39 " , code\n"
40 " , strftime('%s', date)\n"
41 " , visible\n"
42 " , duration\n"
43 " FROM paste\n"
44 " WHERE id = ?";
45
46 static const char *sql_insert =
47 "INSERT INTO paste(\n"
48 " uuid,\n"
49 " title,\n"
50 " author,\n"
51 " language,\n"
52 " code,\n"
53 " visible,\n"
54 " duration\n"
55 ") VALUES (?, ?, ?, ?, ?, ?, ?)";
56
57 static const char *sql_recents =
58 "SELECT uuid\n"
59 " , title\n"
60 " , author\n"
61 " , language\n"
62 " , code\n"
63 " , strftime('%s', date) AS date\n"
64 " , visible\n"
65 " , duration\n"
66 " FROM paste\n"
67 " ORDER BY date\n"
68 " LIMIT ?\n";
69
70 static const char *sql_clear =
71 "BEGIN EXCLUSIVE TRANSACTION;\n"
72 "\n"
73 "DELETE\n"
74 " FROM paste\n"
75 " WHERE strftime('%s', 'now') - strftime('%s', date) >= duration;"
76 "\n"
77 "END TRANSACTION";
78
79 static const char *
80 create_id(void)
81 {
82 static char uuid[256];
83
84 /*
85 * Not a very strong generation but does not require to link against
86 * util-linux.
87 *
88 * See https://stackoverflow.com/questions/2174768/generating-random-uuids-in-linux
89 */
90 sprintf(uuid, "%x%x-%x-%x-%x-%x%x%x",
91 rand(), rand(),
92 rand(),
93 ((rand() & 0x0fff) | 0x4000),
94 rand() % 0x3fff + 0x8000,
95 rand(), rand(), rand());
96
97 return uuid;
98 }
99
100 static void
101 convert(sqlite3_stmt *stmt, struct paste *paste)
102 {
103 paste->uuid = DUP(sqlite3_column_text(stmt, 0));
104 paste->title = DUP(sqlite3_column_text(stmt, 1));
105 paste->author = DUP(sqlite3_column_text(stmt, 2));
106 paste->language = DUP(sqlite3_column_text(stmt, 3));
107 paste->code = DUP(sqlite3_column_text(stmt, 4));
108 paste->timestamp = sqlite3_column_int64(stmt, 5);
109 paste->visible = sqlite3_column_int(stmt, 6);
110 paste->duration = sqlite3_column_int64(stmt, 7);
111 }
112
113 bool
114 database_open(const char *path)
115 {
116 assert(path);
117
118 log_debug("opening database: %s\n", path);
119
120 if (sqlite3_open(path, &db) != SQLITE_OK) {
121 log_warn("unable to open %s: %s\n", path, sqlite3_errmsg(db));
122 return false;
123 }
124
125 if (sqlite3_exec(db, sql_init, NULL, NULL, NULL) != SQLITE_OK) {
126 log_warn("unable to initialize %s: %s\n", path, sqlite3_errmsg(db));
127 return false;
128 }
129
130 log_debug("successfully opened database: %s\n", path);
131
132 return true;
133 }
134
135 bool
136 database_recents(struct paste *pastes, size_t *max)
137 {
138 assert(pastes);
139 assert(max);
140
141 sqlite3_stmt *stmt = NULL;
142
143 memset(pastes, 0, *max * sizeof (struct paste));
144
145 if (sqlite3_prepare(db, sql_recents, -1, &stmt, NULL) != SQLITE_OK ||
146 sqlite3_bind_int64(stmt, 1, *max) != SQLITE_OK)
147 goto sqlite_err;
148
149 size_t i = 0;
150
151 for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i)
152 convert(stmt, &pastes[i]);
153
154 sqlite3_finalize(stmt);
155 *max = i;
156
157 return true;
158
159 sqlite_err:
160 log_warn("database error (recents): %s\n", sqlite3_errmsg(db));
161
162 if (stmt)
163 sqlite3_finalize(stmt);
164
165 return (*max = 0);
166 }
167
168 bool
169 database_get(struct paste *paste, const char *uuid)
170 {
171 assert(paste);
172 assert(uuid);
173
174 memset(paste, 0, sizeof (struct paste));
175
176 sqlite3_stmt* stmt = NULL;
177
178 if (sqlite3_prepare(db, sql_get, -1, &stmt, NULL) != SQLITE_OK ||
179 sqlite3_bind_text(stmt, 1, uuid, -1, NULL) != SQLITE_OK)
180 goto sqlite_err;
181
182 switch (sqlite3_step(stmt)) {
183 case SQLITE_ROW:
184 convert(stmt, paste);
185 break;
186 case SQLITE_MISUSE:
187 case SQLITE_ERROR:
188 goto sqlite_err;
189 default:
190 break;
191 }
192
193 sqlite3_finalize(stmt);
194
195 return true;
196
197 sqlite_err:
198 if (stmt)
199 sqlite3_finalize(stmt);
200
201 log_warn("database error (get): %s", sqlite3_errmsg(db));
202
203 return false;
204 }
205
206 bool
207 database_insert(struct paste *paste)
208 {
209 assert(paste);
210
211 sqlite3_stmt* stmt = NULL;
212
213 if (sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) {
214 log_warn("could not lock database: %s\n", sqlite3_errmsg(db));
215 return false;
216 }
217
218 if (sqlite3_prepare(db, sql_insert, -1, &stmt, NULL) != SQLITE_OK)
219 goto sqlite_err;
220
221 /* Create a new uuid first. */
222 paste->uuid = estrdup(create_id());
223
224 sqlite3_bind_text(stmt, 1, paste->uuid, -1, SQLITE_STATIC);
225 sqlite3_bind_text(stmt, 2, paste->title, -1, SQLITE_STATIC);
226 sqlite3_bind_text(stmt, 3, paste->author, -1, SQLITE_STATIC);
227 sqlite3_bind_text(stmt, 4, paste->language, -1, SQLITE_STATIC);
228 sqlite3_bind_text(stmt, 5, paste->code, -1, SQLITE_STATIC);
229 sqlite3_bind_int(stmt, 6, paste->visible);
230 sqlite3_bind_int64(stmt, 7, paste->duration);
231
232 if (sqlite3_step(stmt) != SQLITE_DONE)
233 goto sqlite_err;
234
235 sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
236 sqlite3_finalize(stmt);
237
238 log_debug("new paste (%s) from %s expires in one %lld seconds",
239 paste->uuid, paste->author, paste->duration);
240
241 return true;
242
243 sqlite_err:
244 log_warn("database error (insert): %s", sqlite3_errmsg(db));
245 sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL);
246
247 if (stmt)
248 sqlite3_finalize(stmt);
249
250 free(paste->uuid);
251 paste->uuid = NULL;
252
253 return false;
254 }
255
256 void
257 database_clear(void)
258 {
259 if (sqlite3_exec(db, sql_clear, NULL, NULL, NULL) != SQLITE_OK)
260 log_warn("database error (clear): %s\n", sqlite3_errmsg(db));
261 }
262
263 void
264 database_finish(void)
265 {
266 if (db)
267 sqlite3_close(db);
268 }