mdbx: Merge branch 'devel' branch.

Change-Id: I7430c4078a4cba86b885db25643ddb57bf5fc6a8
This commit is contained in:
Leo Yuriev 2017-02-09 11:56:48 +03:00
commit 284712a9d4
7 changed files with 427 additions and 120 deletions

132
README.md
View File

@ -3,37 +3,36 @@ libmdbx
Extended LMDB, aka "Расширенная LMDB". Extended LMDB, aka "Расширенная LMDB".
*The Future will Positive. Всё будет хорошо.* *The Future will Positive. Всё будет хорошо.*
[![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=master)](https://travis-ci.org/ReOpen/libmdbx) [![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=master)](https://travis-ci.org/ReOpen/libmdbx)
English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/master). English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/master).
## Кратко ## Кратко
_libmdbx_ - это встраиваемый key-value движок хранения со
специфическим набором возможностей, которые при правильном
применении позволяют создавать уникальные решения с чемпионской
производительностью, идеально сочетаясь с технологией
[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
_libmdbx_ обновляет совместно используемый набор данных, никак _libmdbx_ - это встраиваемый key-value движок хранения со специфическим
не мешая при этом параллельным операциям чтения, не применяя набором возможностей, которые при правильном применении позволяют
атомарных операций к самим данным, и обеспечивая создавать уникальные решения с чемпионской производительностью, идеально
согласованность при аварийной остановке в любой момент. Поэтому сочетаясь с технологией [MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
_libmdbx_ позволяя строить системы с линейным масштабированием
производительности чтения/поиска по ядрам CPU и амортизационной _libmdbx_ обновляет совместно используемый набор данных, никак не мешая
стоимостью любых операций Olog(N). при этом параллельным операциям чтения, не применяя атомарных операций к
самим данным, и обеспечивая согласованность при аварийной остановке в
любой момент. Поэтому _libmdbx_ позволяя строить системы с линейным
масштабированием производительности чтения/поиска по ядрам CPU и
амортизационной стоимостью любых операций Olog(N).
### История ### История
_libmdbx_ является потомком "Lightning Memory-Mapped Database", _libmdbx_ является потомком "Lightning Memory-Mapped Database",
известной под аббревиатурой известной под аббревиатурой
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
Изначально доработка производилась в составе проекта Изначально доработка производилась в составе проекта
[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за [ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год
год работы внесенные изменения приобрели самостоятельную работы внесенные изменения приобрели самостоятельную ценность. Осенью
ценность. Осенью 2015 доработанный движок был выделен в 2015 доработанный движок был выделен в отдельный проект, который был
отдельный проект, который был [представлен на конференции [представлен на конференции Highload++
Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). 2015](http://www.highload.ru/2015/abstracts/1831.html).
Характеристики и ключевые особенности Характеристики и ключевые особенности
@ -283,48 +282,85 @@ RECLAIM` в _libmdbx_.
формированием сильных точек фиксации. формированием сильных точек фиксации.
4. Возможность автоматического формирования контрольных точек 4. Возможность автоматического формирования контрольных точек
(сброса данных на диск) при накоплении заданного объёма (сброса данных на диск) при накоплении заданного объёма изменений,
изменений, устанавливаемого функцией устанавливаемого функцией `mdbx_env_set_syncbytes()`.
`mdbx_env_set_syncbytes()`.
5. Возможность получить отставание текущей транзакции чтения от 5. Возможность получить отставание текущей транзакции чтения от
последней версии данных в БД посредством последней версии данных в БД посредством `mdbx_txn_straggler()`.
`mdbx_txn_straggler()`.
6. Утилита mdbx_chk для проверки БД и функция 6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для
`mdbx_env_pgwalk()` для обхода всех страниц БД. обхода всех страниц БД.
7. Управление отладкой и получение отладочных сообщений 7. Управление отладкой и получение отладочных сообщений посредством
посредством `mdbx_setup_debug()`. `mdbx_setup_debug()`.
8. Возможность связать с каждой завершаемой транзакцией до 3 8. Возможность связать с каждой завершаемой транзакцией до 3
дополнительных маркеров посредством `mdbx_canary_put()`, и дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
прочитать их в транзакции чтения посредством в транзакции чтения посредством `mdbx_canary_get()`.
`mdbx_canary_get()`.
9. Возможность узнать есть ли за текущей позицией курсора 9. Возможность узнать есть ли за текущей позицией курсора строка данных
строка данных посредством `mdbx_cursor_eof()`. посредством `mdbx_cursor_eof()`.
10. Возможность явно запросить обновление существующей записи, 10. Возможность явно запросить обновление существующей записи, без
без создания новой посредством флажка `MDB_CURRENT` для создания новой посредством флажка `MDB_CURRENT` для `mdbx_put()`.
`mdb_put()`.
11. Возможность обновить или удалить запись с получением 11. Возможность обновить или удалить запись с получением предыдущего
предыдущего значения данных посредством `mdbx_replace()`. значения данных посредством `mdbx_replace()`.
12. Поддержка ключей нулевого размера. 12. Поддержка ключей и значений нулевой длины. Включая сортированные
дубликаты, в том числе вне зависимости от порядка их добавления или
обновления.
13. Исправленный вариант `mdb_cursor_count()`, возвращающий 13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
корректное количество дубликатов для всех типов таблиц и любого количество дубликатов для всех типов таблиц и любого положения курсора.
положения курсора.
14. Возможность открыть БД в эксклюзивном режиме посредством 14. Возможность открыть БД в эксклюзивном режиме посредством
`mdbx_env_open_ex()`, например в целях её проверки. `mdbx_env_open_ex()`, например в целях её проверки.
15. Возможность закрыть БД в "грязном" состоянии (без сброса 15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
данных и формирования сильной точки фиксации) посредством формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
`mdbx_env_close_ex()`.
16. Возможность получить посредством `mdbx_env_info()` 16. Возможность получить посредством `mdbx_env_info()` дополнительную
дополнительную информацию, включая номер самой старой версии БД информацию, включая номер самой старой версии БД (снимка данных),
(снимка данных), который используется одним из читателей. который используется одним из читателей.
17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
аргумент `data` для таблиц без дубликатов (без флажка `MDB_DUPSORT`), а
при его ненулевом значении всегда использует его для сверки с удаляемой
записью.
18. Возможность открыть dbi-таблицу, одновременно с установкой
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом избегаю
лишнего копирования данных перед выполнением модифицирующих операций
(значения в размещенные "грязных" страницах могут быть перезаписаны при
изменениях, иначе они будут неизменны).
20. Корректное обновление текущей записи, в том числе сортированного
дубликата, при использовании режима `MDB_CURRENT` в `mdbx_cursor_put()`.
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
ОСВОБОЖДАТЬСЯ ЯВНО.
>
> ## _ВАЖНО_, Обратите внимание!
>
> Это единственное изменение в API, которое значимо меняет
> семантику управления курсорами и может приводить к утечкам
> памяти. Следует отметить, что это изменение вынужденно.
> Так устраняется неоднозначность с массой тяжких последствий:
>
> - обращение к уже освобожденной памяти;
> - попытки повторного освобождения памяти;
> - memory corruption and segfaults.
22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
`mdbx_put()` и `mdbx_replace()` при попытке выполнять неоднозначное
обновление или удаления одного из нескольких значений с одним ключом,
т.е. когда невозможно однозначно идентифицировать одно целевое значение
из нескольких.
23. Возможность посредством `mdbx_get_ex()` получить значение по
заданному ключу, одновременно с количеством дубликатов.

18
lmdb.h
View File

@ -1,7 +1,7 @@
/** @file lmdb.h /** @file lmdb.h
* @brief Extended Lightning memory-mapped database library * @brief Extended Lightning memory-mapped database library
* *
* @mainpage Extended Lightning Memory-Mapped Database Manager (MDBX) * @mainpage Extended Lightning Memory-Mapped Database (MDBX)
* *
* @section intro_sec Introduction * @section intro_sec Introduction
* MDBX is a Btree-based database management library modeled loosely on the * MDBX is a Btree-based database management library modeled loosely on the
@ -1387,12 +1387,20 @@ int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data,
/** @brief Delete items from a database. /** @brief Delete items from a database.
* *
* This function removes key/data pairs from the database. * This function removes key/data pairs from the database.
*
* MDBX-mode:
* The data parameter is NOT ignored regardless the database does
* support sorted duplicate data items or not. If the data parameter
* is non-NULL only the matching data item will be deleted.
*
* LMDB-compatible mode:
* If the database does not support sorted duplicate data items * If the database does not support sorted duplicate data items
* (#MDB_DUPSORT) the data parameter is ignored. * (#MDB_DUPSORT) the data parameter is ignored.
* If the database supports sorted duplicates and the data parameter * If the database supports sorted duplicates and the data parameter
* is NULL, all of the duplicate data items for the key will be * is NULL, all of the duplicate data items for the key will be
* deleted. Otherwise, if the data parameter is non-NULL * deleted. Otherwise, if the data parameter is non-NULL
* only the matching data item will be deleted. * only the matching data item will be deleted.
*
* This function will return #MDB_NOTFOUND if the specified key/data * This function will return #MDB_NOTFOUND if the specified key/data
* pair is not in the database. * pair is not in the database.
* @param[in] txn A transaction handle returned by #mdb_txn_begin() * @param[in] txn A transaction handle returned by #mdb_txn_begin()
@ -1414,6 +1422,13 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
* A cursor cannot be used when its database handle is closed. Nor * A cursor cannot be used when its database handle is closed. Nor
* when its transaction has ended, except with #mdb_cursor_renew(). * when its transaction has ended, except with #mdb_cursor_renew().
* It can be discarded with #mdb_cursor_close(). * It can be discarded with #mdb_cursor_close().
*
* MDBX-mode:
* A cursor must be closed explicitly always, before
* or after its transaction ends. It can be reused with
* #mdb_cursor_renew() before finally closing it.
*
* LMDB-compatible mode:
* A cursor in a write-transaction can be closed before its transaction * A cursor in a write-transaction can be closed before its transaction
* ends, and will otherwise be closed when its transaction ends. * ends, and will otherwise be closed when its transaction ends.
* A cursor in a read-only transaction must be closed explicitly, before * A cursor in a read-only transaction must be closed explicitly, before
@ -1421,6 +1436,7 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
* #mdb_cursor_renew() before finally closing it. * #mdb_cursor_renew() before finally closing it.
* @note Earlier documentation said that cursors in every transaction * @note Earlier documentation said that cursors in every transaction
* were closed when the transaction committed or aborted. * were closed when the transaction committed or aborted.
*
* @param[in] txn A transaction handle returned by #mdb_txn_begin() * @param[in] txn A transaction handle returned by #mdb_txn_begin()
* @param[in] dbi A database handle returned by #mdb_dbi_open() * @param[in] dbi A database handle returned by #mdb_dbi_open()
* @param[out] cursor Address where the new #MDB_cursor handle will be stored * @param[out] cursor Address where the new #MDB_cursor handle will be stored

147
mdb.c
View File

@ -70,6 +70,10 @@
# warning "ReOpenMDBX required at least GLIBC 2.12." # warning "ReOpenMDBX required at least GLIBC 2.12."
#endif #endif
#if MDB_DEBUG
# undef NDEBUG
#endif
#include "./reopen.h" #include "./reopen.h"
#include "./barriers.h" #include "./barriers.h"
@ -934,6 +938,8 @@ struct MDB_xcursor;
*/ */
struct MDB_cursor { struct MDB_cursor {
#define MDBX_MC_SIGNATURE (0xFE05D5B1^MDBX_MODE_SALT) #define MDBX_MC_SIGNATURE (0xFE05D5B1^MDBX_MODE_SALT)
#define MDBX_MC_READY4CLOSE (0x2817A047^MDBX_MODE_SALT)
#define MDBX_MC_WAIT4EOT (0x90E297A7^MDBX_MODE_SALT)
unsigned mc_signature; unsigned mc_signature;
/** Next cursor on this DB in this txn */ /** Next cursor on this DB in this txn */
MDB_cursor *mc_next; MDB_cursor *mc_next;
@ -1178,7 +1184,6 @@ static void mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node);
static void mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int force); static void mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int force);
static int mdb_drop0(MDB_cursor *mc, int subs); static int mdb_drop0(MDB_cursor *mc, int subs);
static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi);
static int mdb_reader_check0(MDB_env *env, int rlocked, int *dead); static int mdb_reader_check0(MDB_env *env, int rlocked, int *dead);
/** @cond */ /** @cond */
@ -2687,7 +2692,7 @@ mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst)
* @return 0 on success, non-zero on failure. * @return 0 on success, non-zero on failure.
*/ */
static void static void
mdb_cursors_close(MDB_txn *txn, unsigned merge) mdb_cursors_eot(MDB_txn *txn, unsigned merge)
{ {
MDB_cursor **cursors = txn->mt_cursors, *mc, *next, *bk; MDB_cursor **cursors = txn->mt_cursors, *mc, *next, *bk;
MDB_xcursor *mx; MDB_xcursor *mx;
@ -2695,6 +2700,8 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge)
for (i = txn->mt_numdbs; --i >= 0; ) { for (i = txn->mt_numdbs; --i >= 0; ) {
for (mc = cursors[i]; mc; mc = next) { for (mc = cursors[i]; mc; mc = next) {
mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE
|| mc->mc_signature == MDBX_MC_WAIT4EOT);
next = mc->mc_next; next = mc->mc_next;
if ((bk = mc->mc_backup) != NULL) { if ((bk = mc->mc_backup) != NULL) {
if (merge) { if (merge) {
@ -2707,16 +2714,30 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge)
if ((mx = mc->mc_xcursor) != NULL) if ((mx = mc->mc_xcursor) != NULL)
mx->mx_cursor.mc_txn = bk->mc_txn; mx->mx_cursor.mc_txn = bk->mc_txn;
} else { } else {
/* Abort nested txn */ /* Abort nested txn, but save current cursor's stage */
unsigned stage = mc->mc_signature;
*mc = *bk; *mc = *bk;
mc->mc_signature = stage;
if ((mx = mc->mc_xcursor) != NULL) if ((mx = mc->mc_xcursor) != NULL)
*mx = *(MDB_xcursor *)(bk+1); *mx = *(MDB_xcursor *)(bk+1);
} }
#if MDBX_MODE_ENABLED
bk->mc_signature = 0;
free(bk);
}
if (mc->mc_signature == MDBX_MC_WAIT4EOT) {
mc->mc_signature = 0;
free(mc);
} else {
mc->mc_signature = MDBX_MC_READY4CLOSE;
}
#else
mc = bk; mc = bk;
} }
/* Only malloced cursors are permanently tracked. */ /* Only malloced cursors are permanently tracked. */
mc->mc_signature = 0; mc->mc_signature = 0;
free(mc); free(mc);
#endif
} }
cursors[i] = NULL; cursors[i] = NULL;
} }
@ -3163,7 +3184,7 @@ mdb_txn_end(MDB_txn *txn, unsigned mode)
pgno_t *pghead = env->me_pghead; pgno_t *pghead = env->me_pghead;
if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */ if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */
mdb_cursors_close(txn, 0); mdb_cursors_eot(txn, 0);
if (!(env->me_flags & MDB_WRITEMAP)) { if (!(env->me_flags & MDB_WRITEMAP)) {
mdb_dlist_free(txn); mdb_dlist_free(txn);
} }
@ -3761,7 +3782,7 @@ mdb_txn_commit(MDB_txn *txn)
parent->mt_flags = txn->mt_flags; parent->mt_flags = txn->mt_flags;
/* Merge our cursors into parent's and close them */ /* Merge our cursors into parent's and close them */
mdb_cursors_close(txn, 1); mdb_cursors_eot(txn, 1);
/* Update parent's DB table. */ /* Update parent's DB table. */
memcpy(parent->mt_dbs, txn->mt_dbs, txn->mt_numdbs * sizeof(MDB_db)); memcpy(parent->mt_dbs, txn->mt_dbs, txn->mt_numdbs * sizeof(MDB_db));
@ -3880,7 +3901,7 @@ mdb_txn_commit(MDB_txn *txn)
goto fail; goto fail;
} }
mdb_cursors_close(txn, 0); mdb_cursors_eot(txn, 0);
if (!txn->mt_u.dirty_list[0].mid && if (!txn->mt_u.dirty_list[0].mid &&
!(txn->mt_flags & (MDB_TXN_DIRTY|MDB_TXN_SPILLS))) !(txn->mt_flags & (MDB_TXN_DIRTY|MDB_TXN_SPILLS)))
@ -6753,11 +6774,25 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data,
mdb_debug("==> put db %d key [%s], size %zu, data size %zu", mdb_debug("==> put db %d key [%s], size %zu, data size %zu",
DDBI(mc), DKEY(key), key ? key->mv_size : 0, data->mv_size); DDBI(mc), DKEY(key), key ? key->mv_size : 0, data->mv_size);
dkey.mv_size = 0; int dupdata_flag = 0;
if (flags & MDB_CURRENT) { if (flags & MDB_CURRENT) {
if (unlikely(!(mc->mc_flags & C_INITIALIZED))) if (unlikely(!(mc->mc_flags & C_INITIALIZED)))
return EINVAL; return EINVAL;
#if MDBX_MODE_ENABLED
if (F_ISSET(mc->mc_db->md_flags, MDB_DUPSORT)) {
MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdb_cassert(mc, mc->mc_xcursor != NULL
&& (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED));
if (mc->mc_xcursor->mx_db.md_entries > 1) {
rc = mdbx_cursor_del(mc, 0);
if (rc != MDB_SUCCESS)
return rc;
flags -= MDB_CURRENT;
}
}
}
#endif /* MDBX_MODE_ENABLED */
rc = MDB_SUCCESS; rc = MDB_SUCCESS;
} else if (mc->mc_db->md_root == P_INVALID) { } else if (mc->mc_db->md_root == P_INVALID) {
/* new database, cursor has nothing to point to */ /* new database, cursor has nothing to point to */
@ -6897,8 +6932,11 @@ more:
/* Was a single item before, must convert now */ /* Was a single item before, must convert now */
if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
/* Just overwrite the current item */ /* Just overwrite the current item */
if (flags == MDB_CURRENT) if (flags & MDB_CURRENT) {
if ((flags & MDB_NODUPDATA) && !mc->mc_dbx->md_dcmp(data, &olddata))
return MDB_KEYEXIST;
goto current; goto current;
}
/* does data match? */ /* does data match? */
if (!mc->mc_dbx->md_dcmp(data, &olddata)) { if (!mc->mc_dbx->md_dcmp(data, &olddata)) {
@ -6909,6 +6947,7 @@ more:
} }
/* Back up original data item */ /* Back up original data item */
dupdata_flag = 1;
dkey.mv_size = olddata.mv_size; dkey.mv_size = olddata.mv_size;
dkey.mv_data = memcpy(fp+1, olddata.mv_data, olddata.mv_size); dkey.mv_data = memcpy(fp+1, olddata.mv_data, olddata.mv_size);
@ -7137,7 +7176,7 @@ new_sub:
* size limits on dupdata. The actual data fields of the child * size limits on dupdata. The actual data fields of the child
* DB are all zero size. */ * DB are all zero size. */
if (do_sub) { if (do_sub) {
int xflags, new_dupdata; int xflags;
size_t ecount; size_t ecount;
put_sub: put_sub:
xdata.mv_size = 0; xdata.mv_size = 0;
@ -7153,9 +7192,8 @@ put_sub:
} }
if (sub_root) if (sub_root)
mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root;
new_dupdata = (int)dkey.mv_size;
/* converted, write the original data first */ /* converted, write the original data first */
if (dkey.mv_size) { if (dupdata_flag) {
rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags);
if (unlikely(rc)) if (unlikely(rc))
goto bad_sub; goto bad_sub;
@ -7175,7 +7213,7 @@ put_sub:
if (!(m2->mc_flags & C_INITIALIZED)) continue; if (!(m2->mc_flags & C_INITIALIZED)) continue;
if (m2->mc_pg[i] == mp) { if (m2->mc_pg[i] == mp) {
if (m2->mc_ki[i] == mc->mc_ki[i]) { if (m2->mc_ki[i] == mc->mc_ki[i]) {
mdb_xcursor_init2(m2, mx, new_dupdata); mdb_xcursor_init2(m2, mx, dupdata_flag);
} else if (!insert_key && m2->mc_ki[i] < nkeys) { } else if (!insert_key && m2->mc_ki[i] < nkeys) {
XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]); XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]);
} }
@ -7761,9 +7799,7 @@ mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata)
mx->mx_cursor.mc_flags |= C_INITIALIZED; mx->mx_cursor.mc_flags |= C_INITIALIZED;
mx->mx_cursor.mc_ki[0] = 0; mx->mx_cursor.mc_ki[0] = 0;
mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA; mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA;
#if UINT_MAX < SIZE_MAX
mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp; mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
#endif
} else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) { } else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) {
return; return;
} }
@ -7849,16 +7885,31 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc)
if (unlikely(!mc || !txn)) if (unlikely(!mc || !txn))
return EINVAL; return EINVAL;
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|| mc->mc_signature != MDBX_MC_SIGNATURE))
return MDB_VERSION_MISMATCH; return MDB_VERSION_MISMATCH;
if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE
&& mc->mc_signature != MDBX_MC_READY4CLOSE))
return EINVAL;
if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))) if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID)))
return EINVAL; return EINVAL;
if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) if (unlikely(mc->mc_backup))
return EINVAL; return EINVAL;
if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) {
#if MDBX_MODE_ENABLED
MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi];
while (*prev && *prev != mc) prev = &(*prev)->mc_next;
if (*prev == mc)
*prev = mc->mc_next;
mc->mc_signature = MDBX_MC_READY4CLOSE;
#else
return EINVAL;
#endif
}
if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED)) if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED))
return MDB_BAD_TXN; return MDB_BAD_TXN;
@ -7894,17 +7945,14 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp)
return MDB_NOTFOUND; return MDB_NOTFOUND;
} }
if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) {
*countp = 1; *countp = 1;
} else { if (mc->mc_xcursor != NULL) {
MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
*countp = 1; mdb_cassert(mc, mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED));
else if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)))
return EINVAL;
else
*countp = mc->mc_xcursor->mx_db.md_entries; *countp = mc->mc_xcursor->mx_db.md_entries;
} }
}
#else #else
if (unlikely(mc->mc_xcursor == NULL)) if (unlikely(mc->mc_xcursor == NULL))
return MDB_INCOMPATIBLE; return MDB_INCOMPATIBLE;
@ -7932,12 +7980,12 @@ void
mdb_cursor_close(MDB_cursor *mc) mdb_cursor_close(MDB_cursor *mc)
{ {
if (mc) { if (mc) {
mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE); mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE
|| mc->mc_signature == MDBX_MC_READY4CLOSE);
if (!mc->mc_backup) { if (!mc->mc_backup) {
/* Remove from txn, if tracked. /* Remove from txn, if tracked.
* A read-only txn (!C_UNTRACK) may have been freed already, * A read-only txn (!C_UNTRACK) may have been freed already,
* so do not peek inside it. Only write txns track cursors. * so do not peek inside it. Only write txns track cursors. */
*/
if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) { if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) {
MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi];
while (*prev && *prev != mc) prev = &(*prev)->mc_next; while (*prev && *prev != mc) prev = &(*prev)->mc_next;
@ -7946,6 +7994,10 @@ mdb_cursor_close(MDB_cursor *mc)
} }
mc->mc_signature = 0; mc->mc_signature = 0;
free(mc); free(mc);
} else {
/* cursor closed before nested txn ends */
mdb_cassert(mc, mc->mc_signature == MDBX_MC_SIGNATURE);
mc->mc_signature = MDBX_MC_WAIT4EOT;
} }
} }
} }
@ -8743,10 +8795,12 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi,
if (unlikely(txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) if (unlikely(txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)))
return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
#if ! MDBX_MODE_ENABLED
if (!F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) { if (!F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
/* must ignore any data */ /* must ignore any data */
data = NULL; data = NULL;
} }
#endif
return mdb_del0(txn, dbi, key, data, 0); return mdb_del0(txn, dbi, key, data, 0);
} }
@ -8758,7 +8812,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi,
MDB_cursor mc; MDB_cursor mc;
MDB_xcursor mx; MDB_xcursor mx;
MDB_cursor_op op; MDB_cursor_op op;
MDB_val rdata, *xdata; MDB_val rdata;
int rc, exact = 0; int rc, exact = 0;
DKBUF; DKBUF;
@ -8769,13 +8823,12 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi,
if (data) { if (data) {
op = MDB_GET_BOTH; op = MDB_GET_BOTH;
rdata = *data; rdata = *data;
xdata = &rdata; data = &rdata;
} else { } else {
op = MDB_SET; op = MDB_SET;
xdata = NULL;
flags |= MDB_NODUPDATA; flags |= MDB_NODUPDATA;
} }
rc = mdb_cursor_set(&mc, key, xdata, op, &exact); rc = mdb_cursor_set(&mc, key, data, op, &exact);
if (likely(rc == 0)) { if (likely(rc == 0)) {
/* let mdb_page_split know about this cursor if needed: /* let mdb_page_split know about this cursor if needed:
* delete will trigger a rebalance; if it needs to move * delete will trigger a rebalance; if it needs to move
@ -9254,7 +9307,7 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi,
MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1); mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1);
rc = MDB_KEYEXIST; rc = MDBX_EMULTIVAL;
} }
} }
} }
@ -10002,6 +10055,21 @@ mdb_env_info(MDB_env *env, MDB_envinfo *arg)
return mdbx_env_info(env, (MDBX_envinfo*) arg, sizeof(MDB_envinfo)); return mdbx_env_info(env, (MDBX_envinfo*) arg, sizeof(MDB_envinfo));
} }
static MDB_cmp_func*
mdbx_default_keycmp(unsigned flags)
{
return (flags & MDB_REVERSEKEY) ? mdb_cmp_memnr :
(flags & MDB_INTEGERKEY) ? mdb_cmp_int_a2 : mdb_cmp_memn;
}
static MDB_cmp_func*
mdbx_default_datacmp(unsigned flags)
{
return !(flags & MDB_DUPSORT) ? 0 :
((flags & MDB_INTEGERDUP) ? mdb_cmp_int_ua :
((flags & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn));
}
/** Set the default comparison functions for a database. /** Set the default comparison functions for a database.
* Called immediately after a database is opened to set the defaults. * Called immediately after a database is opened to set the defaults.
* The user can then override them with #mdb_set_compare() or * The user can then override them with #mdb_set_compare() or
@ -10012,16 +10080,9 @@ mdb_env_info(MDB_env *env, MDB_envinfo *arg)
static void static void
mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi) mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi)
{ {
unsigned f = txn->mt_dbs[dbi].md_flags; unsigned flags = txn->mt_dbs[dbi].md_flags;
txn->mt_dbxs[dbi].md_cmp = mdbx_default_keycmp(flags);
txn->mt_dbxs[dbi].md_cmp = txn->mt_dbxs[dbi].md_dcmp = mdbx_default_datacmp(flags);
(f & MDB_REVERSEKEY) ? mdb_cmp_memnr :
(f & MDB_INTEGERKEY) ? mdb_cmp_int_a2 : mdb_cmp_memn;
txn->mt_dbxs[dbi].md_dcmp =
!(f & MDB_DUPSORT) ? 0 :
((f & MDB_INTEGERDUP) ? mdb_cmp_int_ua :
((f & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn));
} }
int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned flags, MDB_dbi *dbi) int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned flags, MDB_dbi *dbi)

View File

@ -264,7 +264,7 @@ static int pgvisitor(size_t pgno, unsigned pgnumber, void* ctx, const char* dbi,
problem_add("page", pgno, "illegal header-length", "%zu < %i < %zu", problem_add("page", pgno, "illegal header-length", "%zu < %i < %zu",
sizeof(long), header_bytes, stat.base.ms_psize - sizeof(long)); sizeof(long), header_bytes, stat.base.ms_psize - sizeof(long));
if (payload_bytes < 1) { if (payload_bytes < 1) {
if (nentries > 0) { if (nentries > 1) {
problem_add("page", pgno, "zero size-of-entry", "payload %i bytes, %i entries", problem_add("page", pgno, "zero size-of-entry", "payload %i bytes, %i entries",
payload_bytes, nentries); payload_bytes, nentries);
if ((size_t) header_bytes + unused_bytes < page_size) { if ((size_t) header_bytes + unused_bytes < page_size) {
@ -487,9 +487,7 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent)
goto bailout; goto bailout;
} }
if (key.mv_size == 0) { if (key.mv_size > maxkeysize) {
problem_add("entry", record_count, "key with zero length", NULL);
} else if (key.mv_size > maxkeysize) {
problem_add("entry", record_count, "key length exceeds max-key-size", problem_add("entry", record_count, "key length exceeds max-key-size",
"%zu > %zu", key.mv_size, maxkeysize); "%zu > %zu", key.mv_size, maxkeysize);
} else if ((flags & MDB_INTEGERKEY) } else if ((flags & MDB_INTEGERKEY)

214
mdbx.c
View File

@ -118,7 +118,7 @@ mdbx_env_set_syncbytes(MDB_env *env, size_t bytes)
return MDB_VERSION_MISMATCH; return MDB_VERSION_MISMATCH;
env->me_sync_threshold = bytes; env->me_sync_threshold = bytes;
return env->me_map ? mdb_env_sync(env, 0) : 0; return env->me_map ? mdb_env_sync(env, 0) : MDB_SUCCESS;
} }
void __cold void __cold
@ -245,8 +245,6 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
} }
assert(IS_LEAF(mp)); assert(IS_LEAF(mp));
if (node->mn_ksize < 1)
return MDB_CORRUPTED;
if (node->mn_flags & F_BIGDATA) { if (node->mn_flags & F_BIGDATA) {
MDB_page *omp; MDB_page *omp;
pgno_t *opg; pgno_t *opg;
@ -281,8 +279,6 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
MDB_db *db = NODEDATA(node); MDB_db *db = NODEDATA(node);
char* name = NULL; char* name = NULL;
if (NODEDSZ(node) < 1)
return MDB_CORRUPTED;
if (! (node->mn_flags & F_DUPDATA)) { if (! (node->mn_flags & F_DUPDATA)) {
name = NODEKEY(node); name = NODEKEY(node);
int namelen = (char*) db - name; int namelen = (char*) db - name;
@ -367,16 +363,16 @@ int mdbx_cursor_eof(MDB_cursor *mc)
return MDB_VERSION_MISMATCH; return MDB_VERSION_MISMATCH;
if ((mc->mc_flags & C_INITIALIZED) == 0) if ((mc->mc_flags & C_INITIALIZED) == 0)
return 1; return MDBX_RESULT_TRUE;
if (mc->mc_snum == 0) if (mc->mc_snum == 0)
return 1; return MDBX_RESULT_TRUE;
if ((mc->mc_flags & C_EOF) if ((mc->mc_flags & C_EOF)
&& mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top]))
return 1; return MDBX_RESULT_TRUE;
return 0; return MDBX_RESULT_FALSE;
} }
static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) {
@ -399,6 +395,8 @@ static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) {
* когда посредством old_data из записей с одинаковым ключом для * когда посредством old_data из записей с одинаковым ключом для
* удаления/обновления выбирается конкретная. Для выбора этого сценария * удаления/обновления выбирается конкретная. Для выбора этого сценария
* во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE. * во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE.
* Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет
* идентифицировать запрос такого сценария.
* *
* Функция может быть замещена соответствующими операциями с курсорами * Функция может быть замещена соответствующими операциями с курсорами
* после двух доработок (TODO): * после двух доработок (TODO):
@ -411,7 +409,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
MDB_cursor mc; MDB_cursor mc;
MDB_xcursor mx; MDB_xcursor mx;
if (unlikely(!key || !old_data || !txn)) if (unlikely(!key || !old_data || !txn || old_data == new_data))
return EINVAL; return EINVAL;
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
@ -438,17 +436,50 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
int rc; int rc;
MDB_val present_key = *key; MDB_val present_key = *key;
if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE) if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE)) {
&& (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) {
/* в old_data значение для выбора конкретного дубликата */ /* в old_data значение для выбора конкретного дубликата */
if (unlikely(!(txn->mt_dbs[dbi].md_flags & MDB_DUPSORT))) {
rc = EINVAL;
goto bailout;
}
/* убираем лишний бит, он был признаком запрошенного режима */
flags -= MDB_NOOVERWRITE;
rc = mdbx_cursor_get(&mc, &present_key, old_data, MDB_GET_BOTH); rc = mdbx_cursor_get(&mc, &present_key, old_data, MDB_GET_BOTH);
if (rc != MDB_SUCCESS) if (rc != MDB_SUCCESS)
goto bailout; goto bailout;
if (new_data) {
/* обновление конкретного дубликата */
if (mdbx_is_samedata(old_data, new_data))
/* если данные совпадают, то ничего делать не надо */ /* если данные совпадают, то ничего делать не надо */
if (new_data && mdbx_is_samedata(old_data, new_data))
goto bailout; goto bailout;
#if 0 /* LY: исправлено в mdbx_cursor_put(), здесь в качестве памятки */
MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
if (F_ISSET(leaf->mn_flags, F_DUPDATA)
&& mc.mc_xcursor->mx_db.md_entries > 1) {
/* Если у ключа больше одного значения, то
* сначала удаляем найденое "старое" значение.
*
* Этого можно не делать, так как MDBX уже
* обучен корректно обрабатывать такие ситуации.
*
* Однако, следует помнить, что в LMDB при
* совпадении размера данных, значение будет
* просто перезаписано с нарушением
* упорядоченности, что сломает поиск. */
rc = mdbx_cursor_del(&mc, 0);
if (rc != MDB_SUCCESS)
goto bailout;
flags -= MDB_CURRENT;
}
#endif
}
} else { } else {
/* в old_data буфер получения предыдущего значения */ /* в old_data буфер для сохранения предыдущего значения */
if (unlikely(new_data && old_data->iov_base == new_data->iov_base))
return EINVAL;
MDB_val present_data; MDB_val present_data;
rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY); rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY);
if (unlikely(rc != MDB_SUCCESS)) { if (unlikely(rc != MDB_SUCCESS)) {
@ -468,14 +499,20 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
MDB_node *leaf = NODEPTR(page, mc.mc_ki[mc.mc_top]); MDB_node *leaf = NODEPTR(page, mc.mc_ki[mc.mc_top]);
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1); mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1);
rc = MDB_KEYEXIST; if (mc.mc_xcursor->mx_db.md_entries > 1) {
rc = MDBX_EMULTIVAL;
goto bailout; goto bailout;
} }
}
/* если данные совпадают, то ничего делать не надо */ /* если данные совпадают, то ничего делать не надо */
if (new_data && mdbx_is_samedata(&present_data, new_data)) { if (new_data && mdbx_is_samedata(&present_data, new_data)) {
*old_data = *new_data; *old_data = *new_data;
goto bailout; goto bailout;
} }
/* В оригинальной LMDB фладок MDB_CURRENT здесь приведет
* к замене данных без учета MDB_DUPSORT сортировки,
* но здесь это в любом случае допустимо, так как мы
* проверили что для ключа есть только одно значение. */
} else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) { } else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) {
/* если данные совпадают и установлен MDB_NODUPDATA */ /* если данные совпадают и установлен MDB_NODUPDATA */
rc = MDB_KEYEXIST; rc = MDB_KEYEXIST;
@ -494,7 +531,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
if (unlikely(old_data->iov_len < present_data.iov_len)) { if (unlikely(old_data->iov_len < present_data.iov_len)) {
old_data->iov_base = NULL; old_data->iov_base = NULL;
old_data->iov_len = present_data.iov_len; old_data->iov_len = present_data.iov_len;
rc = -1; rc = MDBX_RESULT_TRUE;
goto bailout; goto bailout;
} }
memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len); memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len);
@ -514,3 +551,148 @@ bailout:
txn->mt_cursors[dbi] = mc.mc_next; txn->mt_cursors[dbi] = mc.mc_next;
return rc; return rc;
} }
int
mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi,
MDB_val *key, MDB_val *data, int* values_count)
{
DKBUF;
mdb_debug("===> get db %u key [%s]", dbi, DKEY(key));
if (unlikely(!key || !data || !txn))
return EINVAL;
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
return MDB_VERSION_MISMATCH;
if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID)))
return EINVAL;
if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED))
return MDB_BAD_TXN;
MDB_cursor mc;
MDB_xcursor mx;
mdb_cursor_init(&mc, txn, dbi, &mx);
int exact = 0;
int rc = mdb_cursor_set(&mc, key, data, MDB_SET_KEY, &exact);
if (unlikely(rc != MDB_SUCCESS)) {
if (rc == MDB_NOTFOUND && values_count)
*values_count = 0;
return rc;
}
if (values_count) {
*values_count = 1;
if (mc.mc_xcursor != NULL) {
MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdb_tassert(txn, mc.mc_xcursor == &mx
&& (mx.mx_cursor.mc_flags & C_INITIALIZED));
*values_count = mx.mx_db.md_entries;
}
}
}
return MDB_SUCCESS;
}
/* Функция сообщает находится ли указанный адрес в "грязной" странице у
* заданной пишущей транзакции. В конечном счете это позволяет избавиться от
* лишнего копирования данных из НЕ-грязных страниц.
*
* "Грязные" страницы - это те, которые уже были изменены в ходе пишущей
* транзакции. Соответственно, какие-либо дальнейшие изменения могут привести
* к перезаписи таких страниц. Поэтому все функции, выполняющие изменения, в
* качестве аргументов НЕ должны получать указатели на данные в таких
* страницах. В свою очередь "НЕ грязные" страницы перед модификацией будут
* скопированы.
*
* Другими словами, данные из "грязных" страниц должны быть либо скопированы
* перед передачей в качестве аргументов для дальнейших модификаций, либо
* отвергнуты на стадии проверки корректности аргументов.
*
* Таким образом, функция позволяет как избавится от лишнего копирования,
* так и выполнить более полную проверку аргументов.
*
* ВАЖНО: Передаваемый указатель должен указывать на начало данных. Только
* так гарантируется что актуальный заголовок страницы будет физически
* расположен в той-же странице памяти, в том числе для многостраничных
* P_OVERFLOW страниц с длинными данными. */
int mdbx_is_dirty(const MDB_txn *txn, const void* ptr)
{
if (unlikely(!txn))
return EINVAL;
if(unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
return MDB_VERSION_MISMATCH;
if (unlikely(txn->mt_flags & MDB_TXN_RDONLY))
return MDB_BAD_TXN;
const MDB_env *env = txn->mt_env;
const uintptr_t mask = ~(uintptr_t) (env->me_psize - 1);
const MDB_page *page = (const MDB_page *) ((uintptr_t) ptr & mask);
/* LY: Тут не всё хорошо с абсолютной достоверностью результата,
* так как флажок P_DIRTY в LMDB может означать не совсем то,
* что было исходно задумано, детали см в логике кода mdb_page_touch().
*
* Более того, в режиме БЕЗ WRITEMAP грязные страницы выделяются через
* malloc(), т.е. находятся вне mmap-диаппазона.
*
* Тем не менее, однозначно страница "не грязная" если:
* - адрес находится внутри mmap-диаппазона и в заголовке страницы
* нет флажка P_DIRTY, то однозначно страница "не грязная".
* - адрес вне mmap-диаппазона и его нет среди списка "грязных" страниц.
*/
if (env->me_map < (char*) page) {
const size_t used_size = env->me_psize * txn->mt_next_pgno;
if (env->me_map + used_size > (char*) page) {
/* страница внутри диапазона */
if (page->mp_flags & P_DIRTY)
return MDBX_RESULT_TRUE;
return MDBX_RESULT_FALSE;
}
/* Гипотетически здесь возможна ситуация, когда указатель адресует что-то
* в пределах mmap, но за границей распределенных страниц. Это тяжелая
* ошибка, которой не возможно добиться без каких-то мега-нарушений.
* Поэтому не проверяем этот случай кроме как assert-ом, ибо бестолку. */
mdb_tassert(txn, env->me_map + env->me_mapsize > (char*) page);
}
/* Страница вне mmap-диаппазона */
if (env->me_flags & MDB_WRITEMAP)
/* Если MDB_WRITEMAP, то результат уже ясен. */
return MDBX_RESULT_FALSE;
/* Смотрим список грязных страниц у заданной транзакции. */
MDB_ID2 *list = txn->mt_u.dirty_list;
if (list) {
unsigned i, n = list[0].mid;
for (i = 1; i <= n; i++) {
const MDB_page *dirty = list[i].mptr;
if (dirty == page)
return MDBX_RESULT_TRUE;
}
}
/* При вложенных транзакциях, страница может быть в dirty-списке
* родительской транзакции, но в этом случае она будет скопирована перед
* изменением в текущей транзакции, т.е. относительно заданной транзакции
* проверяемый адрес "не грязный". */
return MDBX_RESULT_FALSE;
}
int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags,
MDB_dbi *pdbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp)
{
int rc = mdbx_dbi_open(txn, name, flags, pdbi);
if (likely(rc == MDB_SUCCESS)) {
MDB_dbi dbi = *pdbi;
unsigned flags = txn->mt_dbs[dbi].md_flags;
txn->mt_dbxs[dbi].md_cmp = keycmp ? keycmp : mdbx_default_keycmp(flags);
txn->mt_dbxs[dbi].md_dcmp = datacmp ? datacmp : mdbx_default_datacmp(flags);
}
return rc;
}

16
mdbx.h
View File

@ -219,12 +219,26 @@ typedef struct mdbx_canary {
int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary); int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary);
size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary); size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary);
/** Returns 1 when no more data available or cursor not positioned, /* Returns 1 when no more data available or cursor not positioned,
* 0 otherwise or less that zero in error case. */ * 0 otherwise or less that zero in error case. */
int mdbx_cursor_eof(MDB_cursor *mc); int mdbx_cursor_eof(MDB_cursor *mc);
#define MDBX_EMULTIVAL (MDB_LAST_ERRCODE - 42)
#define MDBX_RESULT_FALSE MDB_SUCCESS
#define MDBX_RESULT_TRUE (-1)
int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags);
/* Same as mdbx_get(), but:
* 1) if values_count is not NULL, then returns the count
* of multi-values/duplicates for a given key.
* 2) updates the key for pointing to the actual key's data inside DB. */
int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* values_count);
int mdbx_is_dirty(const MDB_txn *txn, const void* ptr);
int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags,
MDB_dbi *dbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp);
/** @} */ /** @} */

View File

@ -57,7 +57,7 @@
#endif /* __must_check_result */ #endif /* __must_check_result */
#ifndef __hot #ifndef __hot
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__)) # if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
# define __hot __attribute__((hot, optimize("O3"))) # define __hot __attribute__((hot, optimize("O3")))
# elif defined(__GNUC__) # elif defined(__GNUC__)
/* cland case, just put frequently used functions in separate section */ /* cland case, just put frequently used functions in separate section */
@ -68,7 +68,7 @@
#endif /* __hot */ #endif /* __hot */
#ifndef __cold #ifndef __cold
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__)) # if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
# define __cold __attribute__((cold, optimize("Os"))) # define __cold __attribute__((cold, optimize("Os")))
# elif defined(__GNUC__) # elif defined(__GNUC__)
/* cland case, just put infrequently used functions in separate section */ /* cland case, just put infrequently used functions in separate section */
@ -79,7 +79,7 @@
#endif /* __cold */ #endif /* __cold */
#ifndef __flatten #ifndef __flatten
# if defined(NDEBUG) && (defined(__GNUC__) || defined(__clang__)) # if defined(__OPTIMIZE__) && (defined(__GNUC__) || defined(__clang__))
# define __flatten __attribute__((flatten)) # define __flatten __attribute__((flatten))
# else # else
# define __flatten # define __flatten