mirror of
https://github.com/isar/libmdbx.git
synced 2025-01-01 23:54:12 +08:00
mdbx: Merge branch 'devel' branch.
Change-Id: I7430c4078a4cba86b885db25643ddb57bf5fc6a8
This commit is contained in:
commit
284712a9d4
132
README.md
132
README.md
@ -3,37 +3,36 @@ libmdbx
|
||||
Extended LMDB, aka "Расширенная LMDB".
|
||||
|
||||
*The Future will Positive. Всё будет хорошо.*
|
||||
|
||||
[![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_ позволяя строить системы с линейным масштабированием
|
||||
производительности чтения/поиска по ядрам CPU и амортизационной
|
||||
стоимостью любых операций Olog(N).
|
||||
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
||||
набором возможностей, которые при правильном применении позволяют
|
||||
создавать уникальные решения с чемпионской производительностью, идеально
|
||||
сочетаясь с технологией [MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
|
||||
|
||||
_libmdbx_ обновляет совместно используемый набор данных, никак не мешая
|
||||
при этом параллельным операциям чтения, не применяя атомарных операций к
|
||||
самим данным, и обеспечивая согласованность при аварийной остановке в
|
||||
любой момент. Поэтому _libmdbx_ позволяя строить системы с линейным
|
||||
масштабированием производительности чтения/поиска по ядрам CPU и
|
||||
амортизационной стоимостью любых операций Olog(N).
|
||||
|
||||
### История
|
||||
|
||||
_libmdbx_ является потомком "Lightning Memory-Mapped Database",
|
||||
известной под аббревиатурой
|
||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||
Изначально доработка производилась в составе проекта
|
||||
[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за
|
||||
год работы внесенные изменения приобрели самостоятельную
|
||||
ценность. Осенью 2015 доработанный движок был выделен в
|
||||
отдельный проект, который был [представлен на конференции
|
||||
Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html).
|
||||
[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год
|
||||
работы внесенные изменения приобрели самостоятельную ценность. Осенью
|
||||
2015 доработанный движок был выделен в отдельный проект, который был
|
||||
[представлен на конференции Highload++
|
||||
2015](http://www.highload.ru/2015/abstracts/1831.html).
|
||||
|
||||
|
||||
Характеристики и ключевые особенности
|
||||
@ -283,48 +282,85 @@ RECLAIM` в _libmdbx_.
|
||||
формированием сильных точек фиксации.
|
||||
|
||||
4. Возможность автоматического формирования контрольных точек
|
||||
(сброса данных на диск) при накоплении заданного объёма
|
||||
изменений, устанавливаемого функцией
|
||||
`mdbx_env_set_syncbytes()`.
|
||||
(сброса данных на диск) при накоплении заданного объёма изменений,
|
||||
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
|
||||
|
||||
5. Возможность получить отставание текущей транзакции чтения от
|
||||
последней версии данных в БД посредством
|
||||
`mdbx_txn_straggler()`.
|
||||
последней версии данных в БД посредством `mdbx_txn_straggler()`.
|
||||
|
||||
6. Утилита mdbx_chk для проверки БД и функция
|
||||
`mdbx_env_pgwalk()` для обхода всех страниц БД.
|
||||
6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для
|
||||
обхода всех страниц БД.
|
||||
|
||||
7. Управление отладкой и получение отладочных сообщений
|
||||
посредством `mdbx_setup_debug()`.
|
||||
7. Управление отладкой и получение отладочных сообщений посредством
|
||||
`mdbx_setup_debug()`.
|
||||
|
||||
8. Возможность связать с каждой завершаемой транзакцией до 3
|
||||
дополнительных маркеров посредством `mdbx_canary_put()`, и
|
||||
прочитать их в транзакции чтения посредством
|
||||
`mdbx_canary_get()`.
|
||||
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
|
||||
в транзакции чтения посредством `mdbx_canary_get()`.
|
||||
|
||||
9. Возможность узнать есть ли за текущей позицией курсора
|
||||
строка данных посредством `mdbx_cursor_eof()`.
|
||||
9. Возможность узнать есть ли за текущей позицией курсора строка данных
|
||||
посредством `mdbx_cursor_eof()`.
|
||||
|
||||
10. Возможность явно запросить обновление существующей записи,
|
||||
без создания новой посредством флажка `MDB_CURRENT` для
|
||||
`mdb_put()`.
|
||||
10. Возможность явно запросить обновление существующей записи, без
|
||||
создания новой посредством флажка `MDB_CURRENT` для `mdbx_put()`.
|
||||
|
||||
11. Возможность обновить или удалить запись с получением
|
||||
предыдущего значения данных посредством `mdbx_replace()`.
|
||||
11. Возможность обновить или удалить запись с получением предыдущего
|
||||
значения данных посредством `mdbx_replace()`.
|
||||
|
||||
12. Поддержка ключей нулевого размера.
|
||||
12. Поддержка ключей и значений нулевой длины. Включая сортированные
|
||||
дубликаты, в том числе вне зависимости от порядка их добавления или
|
||||
обновления.
|
||||
|
||||
13. Исправленный вариант `mdb_cursor_count()`, возвращающий
|
||||
корректное количество дубликатов для всех типов таблиц и любого
|
||||
положения курсора.
|
||||
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
||||
количество дубликатов для всех типов таблиц и любого положения курсора.
|
||||
|
||||
14. Возможность открыть БД в эксклюзивном режиме посредством
|
||||
`mdbx_env_open_ex()`, например в целях её проверки.
|
||||
|
||||
15. Возможность закрыть БД в "грязном" состоянии (без сброса
|
||||
данных и формирования сильной точки фиксации) посредством
|
||||
`mdbx_env_close_ex()`.
|
||||
15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
|
||||
формирования сильной точки фиксации) посредством `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
18
lmdb.h
@ -1,7 +1,7 @@
|
||||
/** @file lmdb.h
|
||||
* @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
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* (#MDB_DUPSORT) the data parameter is ignored.
|
||||
* If the database supports sorted duplicates and the data parameter
|
||||
* is NULL, all of the duplicate data items for the key will be
|
||||
* deleted. Otherwise, if the data parameter is non-NULL
|
||||
* only the matching data item will be deleted.
|
||||
*
|
||||
* This function will return #MDB_NOTFOUND if the specified key/data
|
||||
* pair is not in the database.
|
||||
* @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
|
||||
* when its transaction has ended, except with #mdb_cursor_renew().
|
||||
* 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
|
||||
* ends, and will otherwise be closed when its transaction ends.
|
||||
* 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.
|
||||
* @note Earlier documentation said that cursors in every transaction
|
||||
* were closed when the transaction committed or aborted.
|
||||
*
|
||||
* @param[in] txn A transaction handle returned by #mdb_txn_begin()
|
||||
* @param[in] dbi A database handle returned by #mdb_dbi_open()
|
||||
* @param[out] cursor Address where the new #MDB_cursor handle will be stored
|
||||
|
149
mdb.c
149
mdb.c
@ -70,6 +70,10 @@
|
||||
# warning "ReOpenMDBX required at least GLIBC 2.12."
|
||||
#endif
|
||||
|
||||
#if MDB_DEBUG
|
||||
# undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include "./reopen.h"
|
||||
#include "./barriers.h"
|
||||
|
||||
@ -934,6 +938,8 @@ struct MDB_xcursor;
|
||||
*/
|
||||
struct MDB_cursor {
|
||||
#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;
|
||||
/** Next cursor on this DB in this txn */
|
||||
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 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);
|
||||
|
||||
/** @cond */
|
||||
@ -2687,7 +2692,7 @@ mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst)
|
||||
* @return 0 on success, non-zero on failure.
|
||||
*/
|
||||
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_xcursor *mx;
|
||||
@ -2695,6 +2700,8 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge)
|
||||
|
||||
for (i = txn->mt_numdbs; --i >= 0; ) {
|
||||
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;
|
||||
if ((bk = mc->mc_backup) != NULL) {
|
||||
if (merge) {
|
||||
@ -2707,16 +2714,30 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge)
|
||||
if ((mx = mc->mc_xcursor) != NULL)
|
||||
mx->mx_cursor.mc_txn = bk->mc_txn;
|
||||
} else {
|
||||
/* Abort nested txn */
|
||||
/* Abort nested txn, but save current cursor's stage */
|
||||
unsigned stage = mc->mc_signature;
|
||||
*mc = *bk;
|
||||
mc->mc_signature = stage;
|
||||
if ((mx = mc->mc_xcursor) != NULL)
|
||||
*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;
|
||||
}
|
||||
/* Only malloced cursors are permanently tracked. */
|
||||
mc->mc_signature = 0;
|
||||
free(mc);
|
||||
#endif
|
||||
}
|
||||
cursors[i] = NULL;
|
||||
}
|
||||
@ -3163,7 +3184,7 @@ mdb_txn_end(MDB_txn *txn, unsigned mode)
|
||||
pgno_t *pghead = env->me_pghead;
|
||||
|
||||
if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */
|
||||
mdb_cursors_close(txn, 0);
|
||||
mdb_cursors_eot(txn, 0);
|
||||
if (!(env->me_flags & MDB_WRITEMAP)) {
|
||||
mdb_dlist_free(txn);
|
||||
}
|
||||
@ -3761,7 +3782,7 @@ mdb_txn_commit(MDB_txn *txn)
|
||||
parent->mt_flags = txn->mt_flags;
|
||||
|
||||
/* Merge our cursors into parent's and close them */
|
||||
mdb_cursors_close(txn, 1);
|
||||
mdb_cursors_eot(txn, 1);
|
||||
|
||||
/* Update parent's DB table. */
|
||||
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;
|
||||
}
|
||||
|
||||
mdb_cursors_close(txn, 0);
|
||||
mdb_cursors_eot(txn, 0);
|
||||
|
||||
if (!txn->mt_u.dirty_list[0].mid &&
|
||||
!(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",
|
||||
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 (unlikely(!(mc->mc_flags & C_INITIALIZED)))
|
||||
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;
|
||||
} else if (mc->mc_db->md_root == P_INVALID) {
|
||||
/* new database, cursor has nothing to point to */
|
||||
@ -6897,8 +6932,11 @@ more:
|
||||
/* Was a single item before, must convert now */
|
||||
if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* does data match? */
|
||||
if (!mc->mc_dbx->md_dcmp(data, &olddata)) {
|
||||
@ -6909,6 +6947,7 @@ more:
|
||||
}
|
||||
|
||||
/* Back up original data item */
|
||||
dupdata_flag = 1;
|
||||
dkey.mv_size = 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
|
||||
* DB are all zero size. */
|
||||
if (do_sub) {
|
||||
int xflags, new_dupdata;
|
||||
int xflags;
|
||||
size_t ecount;
|
||||
put_sub:
|
||||
xdata.mv_size = 0;
|
||||
@ -7153,9 +7192,8 @@ put_sub:
|
||||
}
|
||||
if (sub_root)
|
||||
mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root;
|
||||
new_dupdata = (int)dkey.mv_size;
|
||||
/* 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);
|
||||
if (unlikely(rc))
|
||||
goto bad_sub;
|
||||
@ -7175,7 +7213,7 @@ put_sub:
|
||||
if (!(m2->mc_flags & C_INITIALIZED)) continue;
|
||||
if (m2->mc_pg[i] == mp) {
|
||||
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) {
|
||||
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_ki[0] = 0;
|
||||
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;
|
||||
#endif
|
||||
} else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) {
|
||||
return;
|
||||
}
|
||||
@ -7849,16 +7885,31 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc)
|
||||
if (unlikely(!mc || !txn))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE
|
||||
|| mc->mc_signature != MDBX_MC_SIGNATURE))
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
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)))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors))
|
||||
if (unlikely(mc->mc_backup))
|
||||
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))
|
||||
return MDB_BAD_TXN;
|
||||
|
||||
@ -7894,16 +7945,13 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp)
|
||||
return MDB_NOTFOUND;
|
||||
}
|
||||
|
||||
if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) {
|
||||
*countp = 1;
|
||||
} else {
|
||||
*countp = 1;
|
||||
if (mc->mc_xcursor != NULL) {
|
||||
MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
|
||||
if (!F_ISSET(leaf->mn_flags, F_DUPDATA))
|
||||
*countp = 1;
|
||||
else if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)))
|
||||
return EINVAL;
|
||||
else
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
mdb_cassert(mc, mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED));
|
||||
*countp = mc->mc_xcursor->mx_db.md_entries;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (unlikely(mc->mc_xcursor == NULL))
|
||||
@ -7932,12 +7980,12 @@ void
|
||||
mdb_cursor_close(MDB_cursor *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) {
|
||||
/* Remove from txn, if tracked.
|
||||
* 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) {
|
||||
MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi];
|
||||
while (*prev && *prev != mc) prev = &(*prev)->mc_next;
|
||||
@ -7946,6 +7994,10 @@ mdb_cursor_close(MDB_cursor *mc)
|
||||
}
|
||||
mc->mc_signature = 0;
|
||||
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)))
|
||||
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)) {
|
||||
/* must ignore any data */
|
||||
data = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return mdb_del0(txn, dbi, key, data, 0);
|
||||
}
|
||||
@ -8758,7 +8812,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi,
|
||||
MDB_cursor mc;
|
||||
MDB_xcursor mx;
|
||||
MDB_cursor_op op;
|
||||
MDB_val rdata, *xdata;
|
||||
MDB_val rdata;
|
||||
int rc, exact = 0;
|
||||
DKBUF;
|
||||
|
||||
@ -8769,13 +8823,12 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi,
|
||||
if (data) {
|
||||
op = MDB_GET_BOTH;
|
||||
rdata = *data;
|
||||
xdata = &rdata;
|
||||
data = &rdata;
|
||||
} else {
|
||||
op = MDB_SET;
|
||||
xdata = NULL;
|
||||
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)) {
|
||||
/* let mdb_page_split know about this cursor if needed:
|
||||
* 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]);
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
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));
|
||||
}
|
||||
|
||||
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.
|
||||
* Called immediately after a database is opened to set the defaults.
|
||||
* 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
|
||||
mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi)
|
||||
{
|
||||
unsigned f = txn->mt_dbs[dbi].md_flags;
|
||||
|
||||
txn->mt_dbxs[dbi].md_cmp =
|
||||
(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));
|
||||
unsigned flags = txn->mt_dbs[dbi].md_flags;
|
||||
txn->mt_dbxs[dbi].md_cmp = mdbx_default_keycmp(flags);
|
||||
txn->mt_dbxs[dbi].md_dcmp = mdbx_default_datacmp(flags);
|
||||
}
|
||||
|
||||
int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned flags, MDB_dbi *dbi)
|
||||
|
@ -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",
|
||||
sizeof(long), header_bytes, stat.base.ms_psize - sizeof(long));
|
||||
if (payload_bytes < 1) {
|
||||
if (nentries > 0) {
|
||||
if (nentries > 1) {
|
||||
problem_add("page", pgno, "zero size-of-entry", "payload %i bytes, %i entries",
|
||||
payload_bytes, nentries);
|
||||
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;
|
||||
}
|
||||
|
||||
if (key.mv_size == 0) {
|
||||
problem_add("entry", record_count, "key with zero length", NULL);
|
||||
} else if (key.mv_size > maxkeysize) {
|
||||
if (key.mv_size > maxkeysize) {
|
||||
problem_add("entry", record_count, "key length exceeds max-key-size",
|
||||
"%zu > %zu", key.mv_size, maxkeysize);
|
||||
} else if ((flags & MDB_INTEGERKEY)
|
||||
|
220
mdbx.c
220
mdbx.c
@ -118,7 +118,7 @@ mdbx_env_set_syncbytes(MDB_env *env, size_t bytes)
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
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
|
||||
@ -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));
|
||||
if (node->mn_ksize < 1)
|
||||
return MDB_CORRUPTED;
|
||||
if (node->mn_flags & F_BIGDATA) {
|
||||
MDB_page *omp;
|
||||
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);
|
||||
char* name = NULL;
|
||||
|
||||
if (NODEDSZ(node) < 1)
|
||||
return MDB_CORRUPTED;
|
||||
if (! (node->mn_flags & F_DUPDATA)) {
|
||||
name = NODEKEY(node);
|
||||
int namelen = (char*) db - name;
|
||||
@ -367,16 +363,16 @@ int mdbx_cursor_eof(MDB_cursor *mc)
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if ((mc->mc_flags & C_INITIALIZED) == 0)
|
||||
return 1;
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
if (mc->mc_snum == 0)
|
||||
return 1;
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
if ((mc->mc_flags & C_EOF)
|
||||
&& 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) {
|
||||
@ -399,6 +395,8 @@ static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) {
|
||||
* когда посредством old_data из записей с одинаковым ключом для
|
||||
* удаления/обновления выбирается конкретная. Для выбора этого сценария
|
||||
* во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE.
|
||||
* Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет
|
||||
* идентифицировать запрос такого сценария.
|
||||
*
|
||||
* Функция может быть замещена соответствующими операциями с курсорами
|
||||
* после двух доработок (TODO):
|
||||
@ -411,7 +409,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
|
||||
MDB_cursor mc;
|
||||
MDB_xcursor mx;
|
||||
|
||||
if (unlikely(!key || !old_data || !txn))
|
||||
if (unlikely(!key || !old_data || !txn || old_data == new_data))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
@ -438,17 +436,50 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
|
||||
|
||||
int rc;
|
||||
MDB_val present_key = *key;
|
||||
if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE)
|
||||
&& (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) {
|
||||
if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE)) {
|
||||
/* в 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);
|
||||
if (rc != MDB_SUCCESS)
|
||||
goto bailout;
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
if (new_data && mdbx_is_samedata(old_data, new_data))
|
||||
goto bailout;
|
||||
|
||||
if (new_data) {
|
||||
/* обновление конкретного дубликата */
|
||||
if (mdbx_is_samedata(old_data, new_data))
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
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 {
|
||||
/* в old_data буфер получения предыдущего значения */
|
||||
/* в old_data буфер для сохранения предыдущего значения */
|
||||
if (unlikely(new_data && old_data->iov_base == new_data->iov_base))
|
||||
return EINVAL;
|
||||
MDB_val present_data;
|
||||
rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY);
|
||||
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]);
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1);
|
||||
rc = MDB_KEYEXIST;
|
||||
goto bailout;
|
||||
if (mc.mc_xcursor->mx_db.md_entries > 1) {
|
||||
rc = MDBX_EMULTIVAL;
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
if (new_data && mdbx_is_samedata(&present_data, new_data)) {
|
||||
*old_data = *new_data;
|
||||
goto bailout;
|
||||
}
|
||||
/* В оригинальной LMDB фладок MDB_CURRENT здесь приведет
|
||||
* к замене данных без учета MDB_DUPSORT сортировки,
|
||||
* но здесь это в любом случае допустимо, так как мы
|
||||
* проверили что для ключа есть только одно значение. */
|
||||
} else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) {
|
||||
/* если данные совпадают и установлен MDB_NODUPDATA */
|
||||
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)) {
|
||||
old_data->iov_base = NULL;
|
||||
old_data->iov_len = present_data.iov_len;
|
||||
rc = -1;
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
goto bailout;
|
||||
}
|
||||
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;
|
||||
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
16
mdbx.h
@ -219,12 +219,26 @@ typedef struct mdbx_canary {
|
||||
int mdbx_canary_put(MDB_txn *txn, const 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. */
|
||||
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,
|
||||
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);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
6
reopen.h
6
reopen.h
@ -57,7 +57,7 @@
|
||||
#endif /* __must_check_result */
|
||||
|
||||
#ifndef __hot
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# define __hot __attribute__((hot, optimize("O3")))
|
||||
# elif defined(__GNUC__)
|
||||
/* cland case, just put frequently used functions in separate section */
|
||||
@ -68,7 +68,7 @@
|
||||
#endif /* __hot */
|
||||
|
||||
#ifndef __cold
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# define __cold __attribute__((cold, optimize("Os")))
|
||||
# elif defined(__GNUC__)
|
||||
/* cland case, just put infrequently used functions in separate section */
|
||||
@ -79,7 +79,7 @@
|
||||
#endif /* __cold */
|
||||
|
||||
#ifndef __flatten
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) || defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) || defined(__clang__))
|
||||
# define __flatten __attribute__((flatten))
|
||||
# else
|
||||
# define __flatten
|
||||
|
Loading…
x
Reference in New Issue
Block a user