xref: /PHP-8.4/ext/dba/dba_lmdb.c (revision 11accb5c)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Anatol Belski <ab@php.net>                                   |
14   +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #include "php.h"
22 
23 #ifdef DBA_LMDB
24 #include "php_lmdb.h"
25 
26 #ifdef LMDB_INCLUDE_FILE
27 #include LMDB_INCLUDE_FILE
28 #endif
29 
30 struct php_lmdb_info {
31 	MDB_env *env;
32 	MDB_txn *txn;
33 	MDB_dbi dbi;
34 	MDB_cursor *cur;
35 };
36 
37 #define LMDB_IT(it) (((struct php_lmdb_info *)info->dbf)->it)
38 
DBA_OPEN_FUNC(lmdb)39 DBA_OPEN_FUNC(lmdb)
40 {
41 	MDB_env *env;
42 	MDB_txn *txn;
43 	int rc;
44 	int mode = info->file_permission;
45 	zend_long map_size = info->map_size;
46 
47 	ZEND_ASSERT(map_size >= 0);
48 
49 	/* By default use the MDB_NOSUBDIR flag */
50 	int flags = MDB_NOSUBDIR;
51 	/* Use flags passed by the user for driver flags */
52 	if (info->driver_flags != DBA_DEFAULT_DRIVER_FLAGS) {
53 		ZEND_ASSERT(info->driver_flags >= 0);
54 		switch (info->driver_flags) {
55 			case 0:
56 			case MDB_NOSUBDIR:
57 				flags = info->driver_flags;
58 				break;
59 			default:
60 				zend_argument_value_error(6, "must be either DBA_LMDB_USE_SUB_DIR or DBA_LMDB_NO_SUB_DIR for LMDB driver");
61 				return FAILURE;
62 		}
63 	}
64 
65 	/* Add readonly flag if DB is opened in read only mode */
66 	if (info->mode == DBA_READER) {
67 		flags |= MDB_RDONLY;
68 	}
69 
70 	rc = mdb_env_create(&env);
71 	if (rc) {
72 		*error = mdb_strerror(rc);
73 		return FAILURE;
74 	}
75 
76 	if (map_size > 0) {
77 		rc = mdb_env_set_mapsize(env, (size_t) map_size);
78 		if (rc) {
79 			*error = mdb_strerror(rc);
80 			return FAILURE;
81 		}
82 	}
83 
84 	rc = mdb_env_open(env, ZSTR_VAL(info->path), flags, mode);
85 	if (rc) {
86 		/* If this function [mdb_env_open()] fails, mdb_env_close() must be called to discard the MDB_env handle.
87 		 * http://www.lmdb.tech/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340 */
88 		mdb_env_close(env);
89 		*error = mdb_strerror(rc);
90 		return FAILURE;
91 	}
92 
93 	rc = mdb_txn_begin(env, NULL, /* flags */ MDB_RDONLY, &txn);
94 	if (rc) {
95 		mdb_env_close(env);
96 		*error = mdb_strerror(rc);
97 		return FAILURE;
98 	}
99 
100 	info->dbf = pemalloc(sizeof(struct php_lmdb_info), info->flags & DBA_PERSISTENT);
101 	if (!info->dbf) {
102 		*error = "Failed to allocate php_lmdb_info.";
103 		return FAILURE;
104 	}
105 	memset(info->dbf, 0, sizeof(struct php_lmdb_info));
106 
107 	rc = mdb_dbi_open(txn, NULL, 0, &LMDB_IT(dbi));
108 	if (rc) {
109 		mdb_env_close(env);
110 		pefree(info->dbf, info->flags & DBA_PERSISTENT);
111 		*error = mdb_strerror(rc);
112 		return FAILURE;
113 	}
114 
115 	LMDB_IT(env) = env;
116 	LMDB_IT(txn) = txn;
117 
118 	mdb_txn_abort(LMDB_IT(txn));
119 
120 	return SUCCESS;
121 }
122 
DBA_CLOSE_FUNC(lmdb)123 DBA_CLOSE_FUNC(lmdb)
124 {
125 	mdb_dbi_close(LMDB_IT(env), LMDB_IT(dbi));
126 	mdb_env_close(LMDB_IT(env));
127 
128 	pefree(info->dbf, info->flags & DBA_PERSISTENT);
129 }
130 
DBA_FETCH_FUNC(lmdb)131 DBA_FETCH_FUNC(lmdb)
132 {
133 	int rc;
134 	MDB_val k, v;
135 	zend_string *ret = NULL;
136 
137 	if (LMDB_IT(cur)) {
138 		rc = mdb_txn_renew(LMDB_IT(txn));
139 	} else {
140 		rc = mdb_txn_begin(LMDB_IT(env), NULL, MDB_RDONLY, &LMDB_IT(txn));
141 	}
142 	if (rc) {
143 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
144 		return NULL;
145 	}
146 
147 	k.mv_size = ZSTR_LEN(key);
148 	k.mv_data = ZSTR_VAL(key);
149 
150 	rc = mdb_get(LMDB_IT(txn), LMDB_IT(dbi), &k, &v);
151 	if (rc) {
152 		if (MDB_NOTFOUND != rc) {
153 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
154 		}
155 		mdb_txn_abort(LMDB_IT(txn));
156 		return NULL;
157 	}
158 
159 	if (v.mv_data) {
160 		ret = zend_string_init(v.mv_data, v.mv_size, /* persistent */ false);
161 	}
162 
163 	if (LMDB_IT(cur)) {
164 		mdb_txn_reset(LMDB_IT(txn));
165 	} else {
166 		mdb_txn_abort(LMDB_IT(txn));
167 	}
168 
169 	return ret;
170 }
171 
DBA_UPDATE_FUNC(lmdb)172 DBA_UPDATE_FUNC(lmdb)
173 {
174 	int rc;
175 	MDB_val k, v;
176 
177 	rc = mdb_txn_begin(LMDB_IT(env), NULL, 0, &LMDB_IT(txn));
178 	if (rc) {
179 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
180 		return FAILURE;
181 	}
182 
183 	k.mv_size = ZSTR_LEN(key);
184 	k.mv_data = ZSTR_VAL(key);
185 	v.mv_size = ZSTR_LEN(val);
186 	v.mv_data = ZSTR_VAL(val);
187 
188 	rc = mdb_put(LMDB_IT(txn), LMDB_IT(dbi), &k, &v, mode == 1 ? MDB_NOOVERWRITE : 0);
189 	if (rc) {
190 		if (MDB_KEYEXIST != rc) {
191 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
192 		}
193 		mdb_txn_abort(LMDB_IT(txn));
194 		return FAILURE;
195 	}
196 
197 	rc = mdb_txn_commit(LMDB_IT(txn));
198 	if (rc) {
199 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
200 		mdb_txn_abort(LMDB_IT(txn));
201 		return FAILURE;
202 	}
203 
204 	return SUCCESS;
205 }
206 
DBA_EXISTS_FUNC(lmdb)207 DBA_EXISTS_FUNC(lmdb)
208 {
209 	int rc;
210 	MDB_val k, v;
211 
212 	if (LMDB_IT(cur)) {
213 		rc = mdb_txn_renew(LMDB_IT(txn));
214 	} else {
215 		rc = mdb_txn_begin(LMDB_IT(env), NULL, MDB_RDONLY, &LMDB_IT(txn));
216 	}
217 	if (rc) {
218 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
219 		return FAILURE;
220 	}
221 
222 	k.mv_size = ZSTR_LEN(key);
223 	k.mv_data = ZSTR_VAL(key);
224 
225 	rc = mdb_get(LMDB_IT(txn), LMDB_IT(dbi), &k, &v);
226 	if (rc) {
227 		if (MDB_NOTFOUND != rc) {
228 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
229 		}
230 		mdb_txn_abort(LMDB_IT(txn));
231 		return FAILURE;
232 	}
233 
234 	if (LMDB_IT(cur)) {
235 		mdb_txn_reset(LMDB_IT(txn));
236 	} else {
237 		mdb_txn_abort(LMDB_IT(txn));
238 	}
239 
240 	return SUCCESS;
241 }
242 
DBA_DELETE_FUNC(lmdb)243 DBA_DELETE_FUNC(lmdb)
244 {
245 	int rc;
246 	MDB_val k;
247 
248 	rc = mdb_txn_begin(LMDB_IT(env), NULL, 0, &LMDB_IT(txn));
249 	if (rc) {
250 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
251 		return FAILURE;
252 	}
253 
254 	k.mv_size = ZSTR_LEN(key);
255 	k.mv_data = ZSTR_VAL(key);
256 
257 	rc = mdb_del(LMDB_IT(txn), LMDB_IT(dbi), &k, NULL);
258 	if (!rc) {
259 		rc = mdb_txn_commit(LMDB_IT(txn));
260 		if (rc) {
261 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
262 			mdb_txn_abort(LMDB_IT(txn));
263 			return FAILURE;
264 		}
265 		return SUCCESS;
266 	}
267 
268 	php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
269 
270 	mdb_txn_abort(LMDB_IT(txn));
271 	return FAILURE;
272 }
273 
DBA_FIRSTKEY_FUNC(lmdb)274 DBA_FIRSTKEY_FUNC(lmdb)
275 {
276 	int rc;
277 	MDB_val k, v;
278 	zend_string *ret = NULL;
279 
280 	rc = mdb_txn_begin(LMDB_IT(env), NULL, MDB_RDONLY, &LMDB_IT(txn));
281 	if (rc) {
282 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
283 		return NULL;
284 	}
285 
286 	rc = mdb_cursor_open(LMDB_IT(txn), LMDB_IT(dbi), &LMDB_IT(cur));
287 	if (rc) {
288 		mdb_txn_abort(LMDB_IT(txn));
289 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
290 		return NULL;
291 	}
292 
293 	rc = mdb_cursor_get(LMDB_IT(cur), &k, &v, MDB_FIRST);
294 	if (rc) {
295 		mdb_txn_abort(LMDB_IT(txn));
296 		mdb_cursor_close(LMDB_IT(cur));
297 		LMDB_IT(cur) = NULL;
298 		if (MDB_NOTFOUND != rc) {
299 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
300 		}
301 		return NULL;
302 	}
303 
304 	if (k.mv_data) {
305 		ret = zend_string_init(k.mv_data, k.mv_size, /* persistent */ false);
306 	}
307 
308 	mdb_txn_reset(LMDB_IT(txn));
309 
310 	return ret;
311 }
312 
DBA_NEXTKEY_FUNC(lmdb)313 DBA_NEXTKEY_FUNC(lmdb)
314 {
315 	int rc;
316 	MDB_val k, v;
317 	zend_string *ret = NULL;
318 
319 	rc = mdb_txn_renew(LMDB_IT(txn));
320 	if (rc) {
321 		php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
322 		return NULL;
323 	}
324 
325 	rc = mdb_cursor_get(LMDB_IT(cur), &k, &v, MDB_NEXT);
326 	if (rc) {
327 		mdb_txn_abort(LMDB_IT(txn));
328 		mdb_cursor_close(LMDB_IT(cur));
329 		LMDB_IT(cur) = NULL;
330 		if (MDB_NOTFOUND != rc) {
331 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
332 		}
333 		return NULL;
334 	}
335 
336 	if (k.mv_data) {
337 		ret = zend_string_init(k.mv_data, k.mv_size, /* persistent */ false);
338 	}
339 
340 	mdb_txn_reset(LMDB_IT(txn));
341 
342 	return ret;
343 }
344 
DBA_OPTIMIZE_FUNC(lmdb)345 DBA_OPTIMIZE_FUNC(lmdb)
346 {
347 	return SUCCESS;
348 }
349 
DBA_SYNC_FUNC(lmdb)350 DBA_SYNC_FUNC(lmdb)
351 {
352 	int rc;
353 
354 	rc = mdb_env_sync(LMDB_IT(env), 1);
355 	if (rc) {
356 			php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc));
357 			return FAILURE;
358 	}
359 
360 	return SUCCESS;
361 }
362 
DBA_INFO_FUNC(lmdb)363 DBA_INFO_FUNC(lmdb)
364 {
365 	return estrdup(MDB_VERSION_STRING);
366 }
367 
368 #endif
369