mdbx: Merge branch 'master' into stable/0.1

Change-Id: Idd1af2c07b48bfb1f335aa31a52bed4bd68514ac
This commit is contained in:
Leo Yuriev 2018-05-29 03:14:07 +03:00
commit 22d8b0b2e1
5 changed files with 231 additions and 142 deletions

3
.gitignore vendored
View File

@ -16,6 +16,7 @@ Win32/
build-* build-*
cmake-build-* cmake-build-*
core core
example
libmdbx.creator.user libmdbx.creator.user
mdbx-dll.VC.VC.opendb mdbx-dll.VC.VC.opendb
mdbx-dll.VC.db mdbx-dll.VC.db
@ -25,7 +26,7 @@ mdbx_copy
mdbx_dump mdbx_dump
mdbx_load mdbx_load
mdbx_stat mdbx_stat
ootest/test mdbx_test
test.log test.log
test/test.vcxproj.user test/test.vcxproj.user
test/tmp.db test/tmp.db

View File

@ -14,28 +14,28 @@ and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.c
**Сейчас MDBX _активно перерабатывается_** и к середине 2018 **Сейчас MDBX _активно перерабатывается_** и к середине 2018
ожидается большое измените как API, так и формата базы данных. ожидается большое изменение как API, так и формата базы данных.
К сожалению, обновление приведет к потере совместимости с К сожалению, обновление приведет к потере совместимости с
предыдущими версиями. предыдущими версиями.
Цель этой революции в обеспечении более четкого надежного Цель этой революции - обеспечение более четкого надежного
API и добавлении новых функции, а также в наделении базы данных API и добавление новых функции, а также наделение базы данных
новыми свойствами. новыми свойствами.
В настоящее время MDBX предназначена для Linux, а также В настоящее время MDBX предназначена для Linux, а также
поддерживает Windows (начиная с Windows Server 2008) в качестве поддерживает Windows (начиная с Windows Server 2008) в качестве
дополнительно платформы. Поддержка других ОС может быть дополнительной платформы. Поддержка других ОС может быть
обеспечена на коммерческой основе. Однако такие обеспечена на коммерческой основе. Однако такие
усовершенствования (т. е. pull-requests) могут быть приняты в усовершенствования (т. е. pull-requests) могут быть приняты в
мейнстрим только в том случае, если будет доступен мейнстрим только в том случае, если будет доступен
соответствующий публичный и бесплатный сервис непрерывной соответствующий публичный и бесплатный сервис непрерывной
интеграции (aka Countinious Integration). интеграции (aka Continuous Integration).
## Содержание ## Содержание
- [Обзор](#Обзор) - [Обзор](#Обзор)
- [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД) - [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД)
- [История & Acknowledgements](#История) - [История & Acknowledgments](#История)
- [Основные свойства](#Основные-свойства) - [Основные свойства](#Основные-свойства)
- [Сравнение производительности](#Сравнение-производительности) - [Сравнение производительности](#Сравнение-производительности)
- [Интегральная производительность](#Интегральная-производительность) - [Интегральная производительность](#Интегральная-производительность)
@ -62,8 +62,8 @@ _libmdbx_ позволяет множеству процессов совмес
_libmdbx_ обеспечивает _libmdbx_ обеспечивает
[serializability](https://en.wikipedia.org/wiki/Serializability) [serializability](https://en.wikipedia.org/wiki/Serializability)
изменений и согласованность данных после аварий. При этом транзакции изменений и согласованность данных после аварий. При этом транзакции,
изменяющие данные никак не мешают операциям чтения и выполняются строго изменяющие данные, никак не мешают операциям чтения и выполняются строго
последовательно с использованием единственного последовательно с использованием единственного
[мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion). [мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion).
@ -101,7 +101,7 @@ Tables](https://github.com/leo-yuriev/libfpta), aka ["Позитивные
Technologies](https://www.ptsecurity.ru). Technologies](https://www.ptsecurity.ru).
#### Acknowledgements #### Acknowledgments
Howard Chu (Symas Corporation) - the author of LMDB, Howard Chu (Symas Corporation) - the author of LMDB,
from which originated the MDBX in 2015. from which originated the MDBX in 2015.
@ -113,7 +113,7 @@ which was used for begin development of LMDB.
Основные свойства Основные свойства
================= =================
_libmdbx_ наследует все ключевые возможности и особенности от _libmdbx_ наследует все ключевые возможности и особенности
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
но с устранением ряда описываемых далее проблем и архитектурных недочетов. но с устранением ряда описываемых далее проблем и архитектурных недочетов.
@ -193,7 +193,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
1. Такое сравнение не совсем правомочно, его следует делать с движками 1. Такое сравнение не совсем правомочно, его следует делать с движками
ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)). ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)).
2. Превосходство libmdbx становится еще более подавляющем, что мешает 2. Превосходство libmdbx становится еще более подавляющим, что мешает
восприятию информации. восприятию информации.
![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) ![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png)
@ -217,7 +217,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
- Логарифмическая шкала справа и желтые интервальные отрезки - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченное на выполнение показывает минимальное и максимальное время, затраченное на выполнение
транзакций, а крестиком отмечено среднеквадратичное значение. транзакций, а крестиком отмечено среднеквадратичное значение.
Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на
@ -243,7 +243,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
- Логарифмическая шкала справа и желтые интервальные отрезки - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченное на выполнение показывает минимальное и максимальное время, затраченное на выполнение
транзакций, а крестиком отмечено среднеквадратичное значение. транзакций, а крестиком отмечено среднеквадратичное значение.
Выполняется **100.000 транзакций в режиме отложенной фиксации данных** Выполняется **100.000 транзакций в режиме отложенной фиксации данных**
@ -274,7 +274,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
- Логарифмическая шкала справа и желтые интервальные отрезки - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченное на выполнение показывает минимальное и максимальное время, затраченное на выполнение
транзакций, а крестиком отмечено среднеквадратичное значение. транзакций, а крестиком отмечено среднеквадратичное значение.
Выполняется **1.000.000 транзакций в режиме асинхронной фиксации Выполняется **1.000.000 транзакций в режиме асинхронной фиксации
@ -283,7 +283,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
консистентны на момент завершения одной из транзакций, но допускается консистентны на момент завершения одной из транзакций, но допускается
потеря изменений из значительного количества последних транзакций. Во потеря изменений из значительного количества последних транзакций. Во
всех движках при этом включался режим предполагающий минимальную всех движках при этом включался режим предполагающий минимальную
нагрузку на диск по-записи, и соответственно минимальную гарантию нагрузку на диск по записи, и соответственно минимальную гарантию
сохранности данных. В _libmdbx_ при этом используется режим асинхронной сохранности данных. В _libmdbx_ при этом используется режим асинхронной
записи измененных страниц на диск посредством ядра ОС и системного записи измененных страниц на диск посредством ядра ОС и системного
вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync). вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync).
@ -348,7 +348,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с > _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
> журналом кол-во IOPS будет меняться в зависимости от файловой системы, > журналом кол-во IOPS будет меняться в зависимости от файловой системы,
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во > но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
> будет расти логарифмически от кол-во записей/строк в БД (по высоте > будет расти логарифмически от кол-ва записей/строк в БД (по высоте
> b+tree). > b+tree).
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) 3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
@ -364,7 +364,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
> значительного копирования данных в памяти и массы других затратных операций. > значительного копирования данных в памяти и массы других затратных операций.
> Поэтому обусловленное этим недостатком падение производительности становится > Поэтому обусловленное этим недостатком падение производительности становится
> заметным только при отказе от фиксации изменений на диске. > заметным только при отказе от фиксации изменений на диске.
> Соответственно, корректнее сказать что _libmdbx_ позволяет > Соответственно, корректнее сказать, что _libmdbx_ позволяет
> получить персистентность ценой минимального падения производительности. > получить персистентность ценой минимального падения производительности.
> Если же нет необходимости оперативно сохранять данные, то логичнее > Если же нет необходимости оперативно сохранять данные, то логичнее
> использовать `std::map`. > использовать `std::map`.
@ -457,7 +457,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
может быть сохранена только часть измененных страниц БД. При этом с большой может быть сохранена только часть измененных страниц БД. При этом с большой
вероятностью может оказаться так, что будут сохранены мета-страницы со вероятностью может оказаться, что будут сохранены мета-страницы со
ссылками на страницы с новыми версиями данных, но не сами новые данные. ссылками на страницы с новыми версиями данных, но не сами новые данные.
В этом случае БД будет безвозвратна разрушена, даже если до аварии В этом случае БД будет безвозвратна разрушена, даже если до аварии
производилась полная синхронизация данных (посредством производилась полная синхронизация данных (посредством
@ -471,11 +471,11 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
с переносом изменений после фиксации данных. с переносом изменений после фиксации данных.
* При завершении транзакций, в зависимости от состояния * При завершении транзакций, в зависимости от состояния
синхронности данных между диском и оперативной память, синхронности данных между диском и оперативной памятью,
_libmdbx_ помечает точки фиксации либо как сильные (strong), _libmdbx_ помечает точки фиксации либо как сильные (strong),
либо как слабые (weak). Так например, в режиме либо как слабые (weak). Так например, в режиме
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
слабые, а при явной синхронизации данных как сильные. слабые, а при явной синхронизации данных - как сильные.
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы. * В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
Это позволяет выполнять фиксацию транзакций с формированием как Это позволяет выполнять фиксацию транзакций с формированием как
@ -489,20 +489,20 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
сильной фиксации. Этим обеспечивается гарантия сохранности БД. сильной фиксации. Этим обеспечивается гарантия сохранности БД.
Такая гарантия надежности не дается бесплатно. Для Такая гарантия надежности не дается бесплатно. Для
сохранности данных, страницы формирующие крайний снимок с сохранности данных, страницы, формирующие крайний снимок с
сильной фиксацией, не должны повторно использоваться сильной фиксацией, не должны повторно использоваться
(перезаписываться) до формирования следующей сильной точки (перезаписываться) до формирования следующей сильной точки
фиксации. Таким образом, крайняя точка фиксации создает фиксации. Таким образом, крайняя точка фиксации создает
описанный выше эффект "долгого чтения". Разница же здесь в том, описанный выше эффект "долгого чтения". Разница же здесь в том,
что при исчерпании свободных страниц ситуация будет что при исчерпании свободных страниц ситуация будет
автоматически исправлена, посредством записи изменений на диск автоматически исправлена, посредством записи изменений на диск
и формированием новой сильной точки фиксации. и формирования новой сильной точки фиксации.
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
всегда использовать новые страницы до исчерпания места в БД или до явного всегда использовать новые страницы до исчерпания места в БД или до явного
формирования сильной точки фиксации посредством `mdbx_env_sync()`. формирования сильной точки фиксации посредством `mdbx_env_sync()`.
При этом суммарный трафик записи на диск будет примерно такой-же, При этом суммарный трафик записи на диск будет примерно такой же,
как если бы отдельно фиксировалась каждая транзакций. как если бы отдельно фиксировалась каждая транзакция.
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда
@ -535,7 +535,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
Посредством `mdbx_env_set_oomfunc()` может быть установлен Посредством `mdbx_env_set_oomfunc()` может быть установлен
внешний обработчик (callback), который будет вызван при внешний обработчик (callback), который будет вызван при
исчерпания свободных страниц из-за долгой операцией чтения. исчерпании свободных страниц из-за долгой операцией чтения.
Обработчику будет передан PID и pthread_id виновника. Обработчику будет передан PID и pthread_id виновника.
В свою очередь обработчик может предпринять одно из действий: В свою очередь обработчик может предпринять одно из действий:
@ -545,7 +545,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
* отменить или перезапустить проблемную операцию чтения, если * отменить или перезапустить проблемную операцию чтения, если
операция выполняется одним из потоков текущего процесса; операция выполняется одним из потоков текущего процесса;
* подождать некоторое время, в расчете что проблемная операция * подождать некоторое время, в расчете на то, что проблемная операция
чтения будет штатно завершена; чтения будет штатно завершена;
* прервать текущую операцию изменения данных с возвратом кода * прервать текущую операцию изменения данных с возвратом кода
@ -612,7 +612,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли 19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом, некоторый ключ или данные в "грязной" странице БД. Таким образом,
избегая лишнего копирования данных перед выполнением модифицирующих избегая лишнего копирования данных перед выполнением модифицирующих
операций (значения в размещенные "грязных" страницах могут быть операций (значения, размещенные в "грязных" страницах, могут быть
перезаписаны при изменениях, иначе они будут неизменны). перезаписаны при изменениях, иначе они будут неизменны).
20. Корректное обновление текущей записи, в том числе сортированного 20. Корректное обновление текущей записи, в том числе сортированного
@ -632,7 +632,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
> >
> - обращение к уже освобожденной памяти; > - обращение к уже освобожденной памяти;
> - попытки повторного освобождения памяти; > - попытки повторного освобождения памяти;
> - memory corruption and segfaults. > - повреждение памяти и ошибки сегментации.
22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из 22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное `mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
@ -666,7 +666,7 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет
постоянную компактификацию данных, но не затрачивая на это постоянную компактификацию данных, но не затрачивая на это
дополнительных ресурсов, а только освобождая их. При освобождении места дополнительных ресурсов, а только освобождая их. При освобождении места
в БД и установки соответствующих параметров геометрии базы данных, также будет в БД и установке соответствующих параметров геометрии базы данных, также будет
уменьшаться размер файла на диске, в том числе в **Windows**. уменьшаться размер файла на диске, в том числе в **Windows**.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -9,16 +9,15 @@ libmdbx
## Project Status for now ## Project Status for now
- The stable versions (the _stable/*_ branches) of are frozen, i.e. no new features or API changes, but only bug fixes. - The stable versions ([_stable/0.0_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.0) and [_stable/0.1_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.1) branches) of _MDBX_ are frozen, i.e. no new features or API changes, but only bug fixes.
- The next (the _devel_ branch) version **is under active development**, i.e. current API and set of features are extreme volatile. - The next version ([_devel_](https://github.com/leo-yuriev/libmdbx/tree/devel) branch) **is under active non-public development**, i.e. current API and set of features are extreme volatile.
- The immediate goal of development is formation of the stable API and the stable internal database format, which allows realise all planned features. - The immediate goal of development is formation of the stable API and the stable internal database format, which allows realise all PLANNED FEATURES:
- Planned features: 1. Integrity check by [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree);
1. Integrity check by Merkle tree; 2. Support for [raw block devices](https://en.wikipedia.org/wiki/Raw_device);
2. Support for raw block devices;
3. Separate place (HDD) for large data items; 3. Separate place (HDD) for large data items;
4. Using "roaring bitmaps" inside garbage collector; 4. Using "[Roaring bitmaps](http://roaringbitmap.org/about/)" inside garbage collector;
5. Non-linear page reclaiming (like PostgreSQL's Vacuum); 5. Non-sequential reclaiming, like PostgreSQL's [Vacuum](https://www.postgresql.org/docs/9.1/static/sql-vacuum.html);
6. Asynchronous lazy data flushing to disk(s); 6. [Asynchronous lazy data flushing](https://sites.fas.harvard.edu/~cs265/papers/kathuria-2008.pdf) to disk(s);
7. etc... 7. etc...
----- -----
@ -34,7 +33,7 @@ Integration service will be available.
- [Overview](#overview) - [Overview](#overview)
- [Comparison with other DBs](#comparison-with-other-dbs) - [Comparison with other DBs](#comparison-with-other-dbs)
- [History & Acknowledgements](#history) - [History & Acknowledgments](#history)
- [Main features](#main-features) - [Main features](#main-features)
- [Performance comparison](#performance-comparison) - [Performance comparison](#performance-comparison)
- [Integral performance](#integral-performance) - [Integral performance](#integral-performance)
@ -74,7 +73,7 @@ Because _libmdbx_ is currently overhauled, I think it's better to just link
### History ### History
_libmdbx_ design is based on [Lightning Memory-Mapped Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). The _libmdbx_ design is based on [Lightning Memory-Mapped Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
Initial development was going in [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project, about a year later it Initial development was going in [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project, about a year later it
received separate development effort and in autumn 2015 was isolated to separate project, which was received separate development effort and in autumn 2015 was isolated to separate project, which was
[presented at Highload++ 2015 conference](http://www.highload.ru/2015/abstracts/1831.html). [presented at Highload++ 2015 conference](http://www.highload.ru/2015/abstracts/1831.html).
@ -82,7 +81,7 @@ received separate development effort and in autumn 2015 was isolated to separate
Since early 2017 _libmdbx_ is used in [Fast Positive Tables](https://github.com/leo-yuriev/libfpta), Since early 2017 _libmdbx_ is used in [Fast Positive Tables](https://github.com/leo-yuriev/libfpta),
by [Positive Technologies](https://www.ptsecurity.com). by [Positive Technologies](https://www.ptsecurity.com).
#### Acknowledgements #### Acknowledgments
Howard Chu (Symas Corporation) - the author of LMDB, Howard Chu (Symas Corporation) - the author of LMDB,
from which originated the MDBX in 2015. from which originated the MDBX in 2015.
@ -278,7 +277,7 @@ scanning data directory.
#### Long-time read transactions problem #### Long-time read transactions problem
Garbage collection problem exists in all databases one way or another (e.g. VACUUM in PostgreSQL). Garbage collection problem exists in all databases one way or another (e.g. VACUUM in PostgreSQL).
But in _libmbdx_ and LMDB it's even more important because of high performance and deliberate But in _libmdbx_ and LMDB it's even more important because of high performance and deliberate
simplification of internals with emphasis on performance. simplification of internals with emphasis on performance.
* Altering data during long read operation may exhaust available space on persistent storage. * Altering data during long read operation may exhaust available space on persistent storage.
@ -314,7 +313,7 @@ _libmdbx_ addresses the problem, details below. Illustrations to this problem ca
In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage by kernel. This means that in case of application In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage by kernel. This means that in case of application
crash OS kernel will write all dirty data to disk and nothing will be lost. But in case of hardware malfunction or OS kernel crash OS kernel will write all dirty data to disk and nothing will be lost. But in case of hardware malfunction or OS kernel
fatal error only some dirty data might be synced to disk, and there is high probability that pages with metadata saved, fatal error only some dirty data might be synced to disk, and there is high probability that pages with metadata saved,
will point to non-saved, hence non-existent, data pages. In such situation DB is completely corrupted and can't be will point to non-saved, hence non-existent, data pages. In such situation, DB is completely corrupted and can't be
repaired even if there was full sync before the crash via `mdbx_env_sync(). repaired even if there was full sync before the crash via `mdbx_env_sync().
_libmdbx_ addresses this by fully reimplementing write path of data: _libmdbx_ addresses this by fully reimplementing write path of data:
@ -353,7 +352,7 @@ Improvements over LMDB
1. `LIFO RECLAIM` mode: 1. `LIFO RECLAIM` mode:
The newest pages are picked for reuse instead of the oldest. The newest pages are picked for reuse instead of the oldest.
This allows to minimize reclaim loop and make it execution time independent from total page count. This allows to minimize reclaim loop and make it execution time independent of total page count.
This results in OS kernel cache mechanisms working with maximum efficiency. This results in OS kernel cache mechanisms working with maximum efficiency.
In case of using disk controllers or storages with In case of using disk controllers or storages with
@ -365,7 +364,7 @@ Improvements over LMDB
`mdbx_env_set_oomfunc()` allows to set a callback, which will be called `mdbx_env_set_oomfunc()` allows to set a callback, which will be called
in the event of memory exhausting during long-time read transaction. in the event of memory exhausting during long-time read transaction.
Callback will be invoked with PID and pthread_id of offending thread as parameters. Callback will be invoked with PID and pthread_id of offending thread as parameters.
Callback can do any of this things to remedy the problem: Callback can do any of these things to remedy the problem:
* wait for read transaction to finish normally; * wait for read transaction to finish normally;
@ -409,7 +408,7 @@ Improvements over LMDB
15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point) 15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point)
via `mdbx_env_close_ex()`. via `mdbx_env_close_ex()`.
16. Ability to get addition info, including number of the oldest snapshot of DB, which is used by one of the readers. 16. Ability to get additional info, including number of the oldest snapshot of DB, which is used by one of the readers.
Implemented via `mdbx_env_info()`. Implemented via `mdbx_env_info()`.
17. `mdbx_del()` doesn't ignore additional argument (specifier) `data` 17. `mdbx_del()` doesn't ignore additional argument (specifier) `data`
@ -418,7 +417,7 @@ Improvements over LMDB
18. Ability to open dbi-table with simultaneous setup of comparators for keys and values, via `mdbx_dbi_open_ex()`. 18. Ability to open dbi-table with simultaneous setup of comparators for keys and values, via `mdbx_dbi_open_ex()`.
19. Ability to find out if key or value are in dirty page. This may be useful to make a decision to avoid 19. Ability to find out if key or value is in dirty page. This may be useful to make a decision to avoid
excessive CoW before updates. Implemented via `mdbx_is_dirty()`. excessive CoW before updates. Implemented via `mdbx_is_dirty()`.
20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated. 20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated.
@ -449,12 +448,12 @@ Improvements over LMDB
27. Advanced dynamic control over DB size, including ability to choose page size via `mdbx_env_set_geometry()`, 27. Advanced dynamic control over DB size, including ability to choose page size via `mdbx_env_set_geometry()`,
including on Windows. including on Windows.
28. Three meta-pages instead two, this allows to guarantee consistently update weak sync-points without risking to 28. Three meta-pages instead of two, this allows to guarantee consistently update weak sync-points without risking to
corrupt last steady sync-point. corrupt last steady sync-point.
29. Automatic reclaim of freed pages to specific reserved space in the end of database file. This lowers amount of pages, 29. Automatic reclaim of freed pages to specific reserved space at the end of database file. This lowers amount of pages,
loaded to memory, used in update/flush loop. In fact _libmdbx_ constantly performs compactification of data, loaded to memory, used in update/flush loop. In fact _libmdbx_ constantly performs compactification of data,
but doesn't use addition resources for that. Space reclaim of DB and setup of database geometry parameters also decreases but doesn't use additional resources for that. Space reclaim of DB and setup of database geometry parameters also decreases
size of the database on disk, including on Windows. size of the database on disk, including on Windows.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -70,7 +70,7 @@
- [x] Привести в порядок volatile. - [x] Привести в порядок volatile.
- [x] контроль meta.mapsize. - [x] контроль meta.mapsize.
- [x] переработка формата: заголовки страниц, meta, clk... - [x] переработка формата: заголовки страниц, meta, clk...
- [x] зачистка Doxygen и бесполезных коментариев. - [x] зачистка Doxygen и бесполезных комментариев.
- [x] Добавить поле типа контрольной суммы. - [x] Добавить поле типа контрольной суммы.
- [x] Добавить поле/флаг размера pgno_t. - [x] Добавить поле/флаг размера pgno_t.
- [x] Поменять сигнатуры. - [x] Поменять сигнатуры.

View File

@ -962,63 +962,80 @@ enum {
#define MDBX_END_SLOT 0x80 /* release any reader slot if MDBX_NOTLS */ #define MDBX_END_SLOT 0x80 /* release any reader slot if MDBX_NOTLS */
static int mdbx_txn_end(MDBX_txn *txn, unsigned mode); static int mdbx_txn_end(MDBX_txn *txn, unsigned mode);
static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **mp, static int __must_check_result mdbx_page_get(MDBX_cursor *mc, pgno_t pgno,
int *lvl); MDBX_page **mp, int *lvl);
static int mdbx_page_search_root(MDBX_cursor *mc, MDBX_val *key, int modify); static int __must_check_result mdbx_page_search_root(MDBX_cursor *mc,
MDBX_val *key, int modify);
#define MDBX_PS_MODIFY 1 #define MDBX_PS_MODIFY 1
#define MDBX_PS_ROOTONLY 2 #define MDBX_PS_ROOTONLY 2
#define MDBX_PS_FIRST 4 #define MDBX_PS_FIRST 4
#define MDBX_PS_LAST 8 #define MDBX_PS_LAST 8
static int mdbx_page_search(MDBX_cursor *mc, MDBX_val *key, int flags); static int __must_check_result mdbx_page_search(MDBX_cursor *mc, MDBX_val *key,
static int mdbx_page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst); int flags);
static int __must_check_result mdbx_page_merge(MDBX_cursor *csrc,
MDBX_cursor *cdst);
#define MDBX_SPLIT_REPLACE MDBX_APPENDDUP /* newkey is not new */ #define MDBX_SPLIT_REPLACE MDBX_APPENDDUP /* newkey is not new */
static int mdbx_page_split(MDBX_cursor *mc, MDBX_val *newkey, MDBX_val *newdata, static int __must_check_result mdbx_page_split(MDBX_cursor *mc,
MDBX_val *newkey,
MDBX_val *newdata,
pgno_t newpgno, unsigned nflags); pgno_t newpgno, unsigned nflags);
static int mdbx_read_header(MDBX_env *env, MDBX_meta *meta); static int __must_check_result mdbx_read_header(MDBX_env *env, MDBX_meta *meta);
static int mdbx_sync_locked(MDBX_env *env, unsigned flags, static int __must_check_result mdbx_sync_locked(MDBX_env *env, unsigned flags,
MDBX_meta *const pending); MDBX_meta *const pending);
static void mdbx_env_close0(MDBX_env *env); static void mdbx_env_close0(MDBX_env *env);
static MDBX_node *mdbx_node_search(MDBX_cursor *mc, MDBX_val *key, int *exactp); static MDBX_node *mdbx_node_search(MDBX_cursor *mc, MDBX_val *key, int *exactp);
static int mdbx_node_add(MDBX_cursor *mc, unsigned indx, MDBX_val *key, static int __must_check_result mdbx_node_add(MDBX_cursor *mc, unsigned indx,
MDBX_val *data, pgno_t pgno, unsigned flags); MDBX_val *key, MDBX_val *data,
pgno_t pgno, unsigned flags);
static void mdbx_node_del(MDBX_cursor *mc, size_t ksize); static void mdbx_node_del(MDBX_cursor *mc, size_t ksize);
static void mdbx_node_shrink(MDBX_page *mp, unsigned indx); static void mdbx_node_shrink(MDBX_page *mp, unsigned indx);
static int mdbx_node_move(MDBX_cursor *csrc, MDBX_cursor *cdst, int fromleft); static int __must_check_result mdbx_node_move(MDBX_cursor *csrc,
static int mdbx_node_read(MDBX_cursor *mc, MDBX_node *leaf, MDBX_val *data); MDBX_cursor *cdst, int fromleft);
static int __must_check_result mdbx_node_read(MDBX_cursor *mc, MDBX_node *leaf,
MDBX_val *data);
static size_t mdbx_leaf_size(MDBX_env *env, MDBX_val *key, MDBX_val *data); static size_t mdbx_leaf_size(MDBX_env *env, MDBX_val *key, MDBX_val *data);
static size_t mdbx_branch_size(MDBX_env *env, MDBX_val *key); static size_t mdbx_branch_size(MDBX_env *env, MDBX_val *key);
static int mdbx_rebalance(MDBX_cursor *mc); static int __must_check_result mdbx_rebalance(MDBX_cursor *mc);
static int mdbx_update_key(MDBX_cursor *mc, MDBX_val *key); static int __must_check_result mdbx_update_key(MDBX_cursor *mc, MDBX_val *key);
static void mdbx_cursor_pop(MDBX_cursor *mc); static void mdbx_cursor_pop(MDBX_cursor *mc);
static int mdbx_cursor_push(MDBX_cursor *mc, MDBX_page *mp); static int __must_check_result mdbx_cursor_push(MDBX_cursor *mc, MDBX_page *mp);
static int mdbx_cursor_del0(MDBX_cursor *mc); static int __must_check_result mdbx_cursor_del0(MDBX_cursor *mc);
static int mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, static int __must_check_result mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi,
MDBX_val *key, MDBX_val *data,
unsigned flags); unsigned flags);
static int mdbx_cursor_sibling(MDBX_cursor *mc, int move_right); static int __must_check_result mdbx_cursor_sibling(MDBX_cursor *mc,
static int mdbx_cursor_next(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, int move_right);
static int __must_check_result mdbx_cursor_next(MDBX_cursor *mc, MDBX_val *key,
MDBX_val *data,
MDBX_cursor_op op); MDBX_cursor_op op);
static int mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, static int __must_check_result mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key,
MDBX_val *data,
MDBX_cursor_op op); MDBX_cursor_op op);
static int mdbx_cursor_set(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, static int __must_check_result mdbx_cursor_set(MDBX_cursor *mc, MDBX_val *key,
MDBX_val *data,
MDBX_cursor_op op, int *exactp); MDBX_cursor_op op, int *exactp);
static int mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data); static int __must_check_result mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key,
static int mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data); MDBX_val *data);
static int __must_check_result mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key,
MDBX_val *data);
static void mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi, static int __must_check_result mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn,
MDBX_xcursor *mx); MDBX_dbi dbi, MDBX_xcursor *mx);
static void mdbx_xcursor_init0(MDBX_cursor *mc); static int __must_check_result mdbx_xcursor_init0(MDBX_cursor *mc);
static void mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node); static int __must_check_result mdbx_xcursor_init1(MDBX_cursor *mc,
static void mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, MDBX_node *node);
static int __must_check_result mdbx_xcursor_init2(MDBX_cursor *mc,
MDBX_xcursor *src_mx,
int force); int force);
static int mdbx_drop0(MDBX_cursor *mc, int subs); static int __must_check_result mdbx_drop0(MDBX_cursor *mc, int subs);
static MDBX_cmp_func mdbx_cmp_memn, mdbx_cmp_memnr, mdbx_cmp_int_ai, static MDBX_cmp_func mdbx_cmp_memn, mdbx_cmp_memnr, mdbx_cmp_int_ai,
mdbx_cmp_int_a2, mdbx_cmp_int_ua; mdbx_cmp_int_a2, mdbx_cmp_int_ua;
@ -1305,13 +1322,15 @@ static void mdbx_cursor_chk(MDBX_cursor *mc) {
/* Count all the pages in each DB and in the freelist and make sure /* Count all the pages in each DB and in the freelist and make sure
* it matches the actual number of pages being used. * it matches the actual number of pages being used.
* All named DBs must be open for a correct count. */ * All named DBs must be open for a correct count. */
static void mdbx_audit(MDBX_txn *txn) { static int mdbx_audit(MDBX_txn *txn) {
MDBX_cursor mc; MDBX_cursor mc;
MDBX_val key, data; MDBX_val key, data;
int rc; int rc;
pgno_t freecount = 0; pgno_t freecount = 0;
mdbx_cursor_init(&mc, txn, FREE_DBI, NULL); rc = mdbx_cursor_init(&mc, txn, FREE_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
while ((rc = mdbx_cursor_get(&mc, &key, &data, MDBX_NEXT)) == 0) while ((rc = mdbx_cursor_get(&mc, &key, &data, MDBX_NEXT)) == 0)
freecount += *(pgno_t *)data.iov_base; freecount += *(pgno_t *)data.iov_base;
mdbx_tassert(txn, rc == MDBX_NOTFOUND); mdbx_tassert(txn, rc == MDBX_NOTFOUND);
@ -1321,7 +1340,9 @@ static void mdbx_audit(MDBX_txn *txn) {
MDBX_xcursor mx; MDBX_xcursor mx;
if (!(txn->mt_dbflags[i] & DB_VALID)) if (!(txn->mt_dbflags[i] & DB_VALID))
continue; continue;
mdbx_cursor_init(&mc, txn, i, &mx); rc = mdbx_cursor_init(&mc, txn, i, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
if (txn->mt_dbs[i].md_root == P_INVALID) if (txn->mt_dbs[i].md_root == P_INVALID)
continue; continue;
count += txn->mt_dbs[i].md_branch_pages + txn->mt_dbs[i].md_leaf_pages + count += txn->mt_dbs[i].md_branch_pages + txn->mt_dbs[i].md_leaf_pages +
@ -1348,7 +1369,9 @@ static void mdbx_audit(MDBX_txn *txn) {
" total: %" PRIaPGNO " next_pgno: %" PRIaPGNO "\n", " total: %" PRIaPGNO " next_pgno: %" PRIaPGNO "\n",
txn->mt_txnid, freecount, count + NUM_METAS, txn->mt_txnid, freecount, count + NUM_METAS,
freecount + count + NUM_METAS, txn->mt_next_pgno); freecount + count + NUM_METAS, txn->mt_next_pgno);
return MDBX_CORRUPTED;
} }
return MDBX_SUCCESS;
} }
int mdbx_cmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, int mdbx_cmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a,
@ -2179,7 +2202,9 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp,
/* Prepare to fetch more and coalesce */ /* Prepare to fetch more and coalesce */
oldest = (flags & MDBX_LIFORECLAIM) ? mdbx_find_oldest(txn) oldest = (flags & MDBX_LIFORECLAIM) ? mdbx_find_oldest(txn)
: env->me_oldest[0]; : env->me_oldest[0];
mdbx_cursor_init(&recur, txn, FREE_DBI, NULL); rc = mdbx_cursor_init(&recur, txn, FREE_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
goto fail;
if (flags & MDBX_LIFORECLAIM) { if (flags & MDBX_LIFORECLAIM) {
/* Begin from oldest reader if any */ /* Begin from oldest reader if any */
if (oldest > 2) { if (oldest > 2) {
@ -3458,7 +3483,9 @@ static int mdbx_freelist_save(MDBX_txn *txn) {
unsigned cleanup_reclaimed_pos = 0, refill_reclaimed_pos = 0; unsigned cleanup_reclaimed_pos = 0, refill_reclaimed_pos = 0;
const bool lifo = (env->me_flags & MDBX_LIFORECLAIM) != 0; const bool lifo = (env->me_flags & MDBX_LIFORECLAIM) != 0;
mdbx_cursor_init(&mc, txn, FREE_DBI, NULL); rc = mdbx_cursor_init(&mc, txn, FREE_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
/* MDBX_RESERVE cancels meminit in ovpage malloc (when no WRITEMAP) */ /* MDBX_RESERVE cancels meminit in ovpage malloc (when no WRITEMAP) */
const intptr_t clean_limit = const intptr_t clean_limit =
@ -4180,7 +4207,9 @@ int mdbx_txn_commit(MDBX_txn *txn) {
MDBX_val data; MDBX_val data;
data.iov_len = sizeof(MDBX_db); data.iov_len = sizeof(MDBX_db);
mdbx_cursor_init(&mc, txn, MAIN_DBI, NULL); rc = mdbx_cursor_init(&mc, txn, MAIN_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
goto fail;
for (i = CORE_DBS; i < txn->mt_numdbs; i++) { for (i = CORE_DBS; i < txn->mt_numdbs; i++) {
if (txn->mt_dbflags[i] & DB_DIRTY) { if (txn->mt_dbflags[i] & DB_DIRTY) {
if (unlikely(TXN_DBI_CHANGED(txn, i))) { if (unlikely(TXN_DBI_CHANGED(txn, i))) {
@ -6390,9 +6419,11 @@ static int mdbx_page_search(MDBX_cursor *mc, MDBX_val *key, int flags) {
MDBX_cursor mc2; MDBX_cursor mc2;
if (unlikely(TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))) if (unlikely(TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi)))
return MDBX_BAD_DBI; return MDBX_BAD_DBI;
mdbx_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL); rc = mdbx_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_page_search(&mc2, &mc->mc_dbx->md_name, 0); rc = mdbx_page_search(&mc2, &mc->mc_dbx->md_name, 0);
if (rc) if (unlikely(rc != MDBX_SUCCESS))
return rc; return rc;
{ {
MDBX_val data; MDBX_val data;
@ -6576,7 +6607,9 @@ int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) {
if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED))
return MDBX_BAD_TXN; return MDBX_BAD_TXN;
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
return mdbx_cursor_set(&mc, key, data, MDBX_SET, &exact); return mdbx_cursor_set(&mc, key, data, MDBX_SET, &exact);
} }
@ -6631,7 +6664,9 @@ static int mdbx_cursor_sibling(MDBX_cursor *mc, int move_right) {
return rc; return rc;
} }
mdbx_cursor_push(mc, mp); rc = mdbx_cursor_push(mc, mp);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
if (!move_right) if (!move_right)
mc->mc_ki[mc->mc_top] = NUMKEYS(mp) - 1; mc->mc_ki[mc->mc_top] = NUMKEYS(mp) - 1;
@ -6711,7 +6746,9 @@ skip:
leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); 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)) {
mdbx_xcursor_init1(mc, leaf); rc = mdbx_xcursor_init1(mc, leaf);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} }
if (data) { if (data) {
if (unlikely((rc = mdbx_node_read(mc, leaf, data)) != MDBX_SUCCESS)) if (unlikely((rc = mdbx_node_read(mc, leaf, data)) != MDBX_SUCCESS))
@ -6799,7 +6836,9 @@ static int mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); 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)) {
mdbx_xcursor_init1(mc, leaf); rc = mdbx_xcursor_init1(mc, leaf);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} }
if (data) { if (data) {
if (unlikely((rc = mdbx_node_read(mc, leaf, data)) != MDBX_SUCCESS)) if (unlikely((rc = mdbx_node_read(mc, leaf, data)) != MDBX_SUCCESS))
@ -6966,7 +7005,9 @@ set1:
} }
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdbx_xcursor_init1(mc, leaf); rc = mdbx_xcursor_init1(mc, leaf);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} }
if (likely(data)) { if (likely(data)) {
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
@ -7041,8 +7082,9 @@ static int mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) {
if (likely(data)) { if (likely(data)) {
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdbx_cassert(mc, mc->mc_xcursor != nullptr); rc = mdbx_xcursor_init1(mc, leaf);
mdbx_xcursor_init1(mc, leaf); if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
if (unlikely(rc)) if (unlikely(rc))
return rc; return rc;
@ -7085,8 +7127,9 @@ static int mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) {
if (likely(data)) { if (likely(data)) {
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
mdbx_cassert(mc, mc->mc_xcursor != nullptr); rc = mdbx_xcursor_init1(mc, leaf);
mdbx_xcursor_init1(mc, leaf); if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); rc = mdbx_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL);
if (unlikely(rc)) if (unlikely(rc))
return rc; return rc;
@ -7139,7 +7182,9 @@ int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
if (data) { if (data) {
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) { if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) {
mdbx_xcursor_init1(mc, leaf); rc = mdbx_xcursor_init1(mc, leaf);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
if (unlikely(rc)) if (unlikely(rc))
return rc; return rc;
@ -7281,7 +7326,9 @@ static int mdbx_cursor_touch(MDBX_cursor *mc) {
MDBX_xcursor mcx; MDBX_xcursor mcx;
if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi)) if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))
return MDBX_BAD_DBI; return MDBX_BAD_DBI;
mdbx_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, &mcx); rc = mdbx_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, &mcx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_page_search(&mc2, &mc->mc_dbx->md_name, MDBX_PS_MODIFY); rc = mdbx_page_search(&mc2, &mc->mc_dbx->md_name, MDBX_PS_MODIFY);
if (unlikely(rc)) if (unlikely(rc))
return rc; return rc;
@ -7462,7 +7509,9 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
return rc2; return rc2;
} }
assert(np->mp_flags & P_LEAF); assert(np->mp_flags & P_LEAF);
mdbx_cursor_push(mc, np); rc2 = mdbx_cursor_push(mc, np);
if (unlikely(rc2 != MDBX_SUCCESS))
return rc2;
mc->mc_db->md_root = np->mp_pgno; mc->mc_db->md_root = np->mp_pgno;
mc->mc_db->md_depth++; mc->mc_db->md_depth++;
*mc->mc_dbflag |= DB_DIRTY; *mc->mc_dbflag |= DB_DIRTY;
@ -7810,7 +7859,9 @@ new_sub:
? MDBX_CURRENT | MDBX_NOOVERWRITE | MDBX_NOSPILL ? MDBX_CURRENT | MDBX_NOOVERWRITE | MDBX_NOSPILL
: MDBX_CURRENT | MDBX_NOSPILL; : MDBX_CURRENT | MDBX_NOSPILL;
} else { } else {
mdbx_xcursor_init1(mc, leaf); rc2 = mdbx_xcursor_init1(mc, leaf);
if (unlikely(rc2 != MDBX_SUCCESS))
return rc2;
xflags = (flags & MDBX_NODUPDATA) ? MDBX_NOOVERWRITE | MDBX_NOSPILL xflags = (flags & MDBX_NODUPDATA) ? MDBX_NOOVERWRITE | MDBX_NOSPILL
: MDBX_NOSPILL; : MDBX_NOSPILL;
} }
@ -7839,7 +7890,9 @@ new_sub:
continue; 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]) {
mdbx_xcursor_init2(m2, mx, dupdata_flag); rc2 = mdbx_xcursor_init2(m2, mx, dupdata_flag);
if (unlikely(rc2 != MDBX_SUCCESS))
return rc2;
} 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]);
} }
@ -8359,8 +8412,10 @@ static void mdbx_node_shrink(MDBX_page *mp, unsigned indx) {
* depend only on the parent DB. * depend only on the parent DB.
* *
* [in] mc The main cursor whose sorted-dups cursor is to be initialized. */ * [in] mc The main cursor whose sorted-dups cursor is to be initialized. */
static void mdbx_xcursor_init0(MDBX_cursor *mc) { static int mdbx_xcursor_init0(MDBX_cursor *mc) {
MDBX_xcursor *mx = mc->mc_xcursor; MDBX_xcursor *mx = mc->mc_xcursor;
if (unlikely(mx == nullptr))
return MDBX_CORRUPTED;
mx->mx_cursor.mc_xcursor = NULL; mx->mx_cursor.mc_xcursor = NULL;
mx->mx_cursor.mc_txn = mc->mc_txn; mx->mx_cursor.mc_txn = mc->mc_txn;
@ -8375,6 +8430,7 @@ static void mdbx_xcursor_init0(MDBX_cursor *mc) {
mx->mx_dbx.md_name.iov_base = NULL; mx->mx_dbx.md_name.iov_base = NULL;
mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp; mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp;
mx->mx_dbx.md_dcmp = NULL; mx->mx_dbx.md_dcmp = NULL;
return MDBX_SUCCESS;
} }
/* Final setup of a sorted-dups cursor. /* Final setup of a sorted-dups cursor.
@ -8382,8 +8438,10 @@ static void mdbx_xcursor_init0(MDBX_cursor *mc) {
* [in] mc The main cursor whose sorted-dups cursor is to be initialized. * [in] mc The main cursor whose sorted-dups cursor is to be initialized.
* [in] node The data containing the MDBX_db record for the sorted-dup database. * [in] node The data containing the MDBX_db record for the sorted-dup database.
*/ */
static void mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) { static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) {
MDBX_xcursor *mx = mc->mc_xcursor; MDBX_xcursor *mx = mc->mc_xcursor;
if (unlikely(mx == nullptr))
return MDBX_CORRUPTED;
mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]); mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]);
if (node->mn_flags & F_SUBDATA) { if (node->mn_flags & F_SUBDATA) {
@ -8422,6 +8480,8 @@ static void mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) {
sizeof(size_t)) sizeof(size_t))
mx->mx_dbx.md_cmp = mdbx_cmp_clong; mx->mx_dbx.md_cmp = mdbx_cmp_clong;
#endif */ #endif */
return MDBX_SUCCESS;
} }
/* Fixup a sorted-dups cursor due to underlying update. /* Fixup a sorted-dups cursor due to underlying update.
@ -8431,9 +8491,11 @@ static void mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) {
* [in] mc The main cursor whose sorted-dups cursor is to be fixed up. * [in] mc The main cursor whose sorted-dups cursor is to be fixed up.
* [in] src_mx The xcursor of an up-to-date cursor. * [in] src_mx The xcursor of an up-to-date cursor.
* [in] new_dupdata True if converting from a non-F_DUPDATA item. */ * [in] new_dupdata True if converting from a non-F_DUPDATA item. */
static void mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, static int mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx,
int new_dupdata) { int new_dupdata) {
MDBX_xcursor *mx = mc->mc_xcursor; MDBX_xcursor *mx = mc->mc_xcursor;
if (unlikely(mx == nullptr))
return MDBX_CORRUPTED;
mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]); mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]);
if (new_dupdata) { if (new_dupdata) {
@ -8444,16 +8506,17 @@ static void mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx,
mx->mx_dbflag = DB_VALID | DB_USRVALID | DB_DUPDATA; mx->mx_dbflag = DB_VALID | DB_USRVALID | DB_DUPDATA;
mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp; mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
} else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) { } else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) {
return; return MDBX_SUCCESS;
} }
mx->mx_db = src_mx->mx_db; mx->mx_db = src_mx->mx_db;
mx->mx_cursor.mc_pg[0] = src_mx->mx_cursor.mc_pg[0]; mx->mx_cursor.mc_pg[0] = src_mx->mx_cursor.mc_pg[0];
mdbx_debug("Sub-db -%u root page %" PRIaPGNO "", mx->mx_cursor.mc_dbi, mdbx_debug("Sub-db -%u root page %" PRIaPGNO "", mx->mx_cursor.mc_dbi,
mx->mx_db.md_root); mx->mx_db.md_root);
return MDBX_SUCCESS;
} }
/* Initialize a cursor for a given transaction and database. */ /* Initialize a cursor for a given transaction and database. */
static void mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi, static int mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi,
MDBX_xcursor *mx) { MDBX_xcursor *mx) {
mc->mc_signature = MDBX_MC_SIGNATURE; mc->mc_signature = MDBX_MC_SIGNATURE;
mc->mc_next = NULL; mc->mc_next = NULL;
@ -8469,16 +8532,23 @@ static void mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi,
mc->mc_flags = 0; mc->mc_flags = 0;
mc->mc_ki[0] = 0; mc->mc_ki[0] = 0;
mc->mc_xcursor = NULL; mc->mc_xcursor = NULL;
if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) { if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) {
mdbx_tassert(txn, mx != NULL); mdbx_tassert(txn, mx != NULL);
mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE; mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE;
mc->mc_xcursor = mx; mc->mc_xcursor = mx;
mdbx_xcursor_init0(mc); int rc = mdbx_xcursor_init0(mc);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} }
mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]); mdbx_cassert(mc, mc->mc_txn->mt_txnid >= mc->mc_txn->mt_env->me_oldest[0]);
int rc = MDBX_SUCCESS;
if (unlikely(*mc->mc_dbflag & DB_STALE)) { if (unlikely(*mc->mc_dbflag & DB_STALE)) {
mdbx_page_search(mc, NULL, MDBX_PS_ROOTONLY); rc = mdbx_page_search(mc, NULL, MDBX_PS_ROOTONLY);
rc = (rc != MDBX_NOTFOUND) ? rc : MDBX_SUCCESS;
} }
return rc;
} }
int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) { int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
@ -8507,7 +8577,11 @@ int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
size += sizeof(MDBX_xcursor); size += sizeof(MDBX_xcursor);
if (likely((mc = malloc(size)) != NULL)) { if (likely((mc = malloc(size)) != NULL)) {
mdbx_cursor_init(mc, txn, dbi, (MDBX_xcursor *)(mc + 1)); int rc = mdbx_cursor_init(mc, txn, dbi, (MDBX_xcursor *)(mc + 1));
if (unlikely(rc != MDBX_SUCCESS)) {
free(mc);
return rc;
}
if (txn->mt_cursors) { if (txn->mt_cursors) {
mc->mc_next = txn->mt_cursors[dbi]; mc->mc_next = txn->mt_cursors[dbi];
txn->mt_cursors[dbi] = mc; txn->mt_cursors[dbi] = mc;
@ -8518,7 +8592,6 @@ int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
} }
*ret = mc; *ret = mc;
return MDBX_SUCCESS; return MDBX_SUCCESS;
} }
@ -8557,8 +8630,7 @@ int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *mc) {
if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED))
return MDBX_BAD_TXN; return MDBX_BAD_TXN;
mdbx_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor); return mdbx_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor);
return MDBX_SUCCESS;
} }
/* Return the count of duplicate data items for the current key */ /* Return the count of duplicate data items for the current key */
@ -9399,7 +9471,8 @@ static int mdbx_cursor_del0(MDBX_cursor *mc) {
if (!(node->mn_flags & F_SUBDATA)) if (!(node->mn_flags & F_SUBDATA))
m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
} else { } else {
mdbx_xcursor_init1(m3, node); rc = mdbx_xcursor_init1(m3, node);
if (likely(rc == MDBX_SUCCESS))
m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL; m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL;
} }
} }
@ -9446,7 +9519,9 @@ static int mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
mdbx_debug("====> delete db %u key [%s], data [%s]", dbi, DKEY(key), mdbx_debug("====> delete db %u key [%s], data [%s]", dbi, DKEY(key),
DVAL(data)); DVAL(data));
mdbx_cursor_init(&mc, txn, dbi, &mx); rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
if (data) { if (data) {
op = MDBX_GET_BOTH; op = MDBX_GET_BOTH;
@ -9935,11 +10010,12 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED))) if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
return (txn->mt_flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; return (txn->mt_flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN;
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
mc.mc_next = txn->mt_cursors[dbi]; mc.mc_next = txn->mt_cursors[dbi];
txn->mt_cursors[dbi] = &mc; txn->mt_cursors[dbi] = &mc;
int rc = MDBX_SUCCESS;
/* LY: support for update (explicit overwrite) */ /* LY: support for update (explicit overwrite) */
if (flags & MDBX_CURRENT) { if (flags & MDBX_CURRENT) {
rc = mdbx_cursor_get(&mc, key, NULL, MDBX_SET); rc = mdbx_cursor_get(&mc, key, NULL, MDBX_SET);
@ -10264,7 +10340,9 @@ static int __cold mdbx_env_compact(MDBX_env *env, mdbx_filehandle_t fd) {
MDBX_cursor mc; MDBX_cursor mc;
MDBX_val key, data; MDBX_val key, data;
mdbx_cursor_init(&mc, txn, FREE_DBI, NULL); rc = mdbx_cursor_init(&mc, txn, FREE_DBI, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
while ((rc = mdbx_cursor_get(&mc, &key, &data, MDBX_NEXT)) == 0) while ((rc = mdbx_cursor_get(&mc, &key, &data, MDBX_NEXT)) == 0)
freecount += *(pgno_t *)data.iov_base; freecount += *(pgno_t *)data.iov_base;
if (unlikely(rc != MDBX_NOTFOUND)) if (unlikely(rc != MDBX_NOTFOUND))
@ -10685,8 +10763,10 @@ int mdbx_dbi_open_ex(MDBX_txn *txn, const char *table_name, unsigned user_flags,
key.iov_len = len; key.iov_len = len;
key.iov_base = (void *)table_name; key.iov_base = (void *)table_name;
MDBX_cursor mc; MDBX_cursor mc;
mdbx_cursor_init(&mc, txn, MAIN_DBI, NULL); int rc = mdbx_cursor_init(&mc, txn, MAIN_DBI, NULL);
int rc = mdbx_cursor_set(&mc, &key, &data, MDBX_SET, &exact); if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_cursor_set(&mc, &key, &data, MDBX_SET, &exact);
if (unlikely(rc != MDBX_SUCCESS)) { if (unlikely(rc != MDBX_SUCCESS)) {
if (rc != MDBX_NOTFOUND || !(user_flags & MDBX_CREATE)) if (rc != MDBX_NOTFOUND || !(user_flags & MDBX_CREATE))
return rc; return rc;
@ -10822,7 +10902,9 @@ int __cold mdbx_dbi_stat(MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *arg,
MDBX_cursor mc; MDBX_cursor mc;
MDBX_xcursor mx; MDBX_xcursor mx;
/* Stale, must read the DB's root. cursor_init does it for us. */ /* Stale, must read the DB's root. cursor_init does it for us. */
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} }
return mdbx_stat0(txn->mt_env, &txn->mt_dbs[dbi], arg); return mdbx_stat0(txn->mt_env, &txn->mt_dbs[dbi], arg);
} }
@ -10926,7 +11008,9 @@ static int mdbx_drop0(MDBX_cursor *mc, int subs) {
if (!mc->mc_db->md_overflow_pages && !subs) if (!mc->mc_db->md_overflow_pages && !subs)
break; break;
} else if (subs && (ni->mn_flags & F_SUBDATA)) { } else if (subs && (ni->mn_flags & F_SUBDATA)) {
mdbx_xcursor_init1(mc, ni); rc = mdbx_xcursor_init1(mc, ni);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
rc = mdbx_drop0(&mc->mc_xcursor->mx_cursor, 0); rc = mdbx_drop0(&mc->mc_xcursor->mx_cursor, 0);
if (unlikely(rc)) if (unlikely(rc))
goto done; goto done;
@ -11762,11 +11846,12 @@ int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *new_data,
MDBX_cursor mc; MDBX_cursor mc;
MDBX_xcursor mx; MDBX_xcursor mx;
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
mc.mc_next = txn->mt_cursors[dbi]; mc.mc_next = txn->mt_cursors[dbi];
txn->mt_cursors[dbi] = &mc; txn->mt_cursors[dbi] = &mc;
int rc;
MDBX_val present_key = *key; MDBX_val present_key = *key;
if (F_ISSET(flags, MDBX_CURRENT | MDBX_NOOVERWRITE)) { if (F_ISSET(flags, MDBX_CURRENT | MDBX_NOOVERWRITE)) {
/* в old_data значение для выбора конкретного дубликата */ /* в old_data значение для выбора конкретного дубликата */
@ -11889,10 +11974,12 @@ int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
MDBX_cursor mc; MDBX_cursor mc;
MDBX_xcursor mx; MDBX_xcursor mx;
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
int exact = 0; int exact = 0;
int rc = mdbx_cursor_set(&mc, key, data, MDBX_SET_KEY, &exact); rc = mdbx_cursor_set(&mc, key, data, MDBX_SET_KEY, &exact);
if (unlikely(rc != MDBX_SUCCESS)) { if (unlikely(rc != MDBX_SUCCESS)) {
if (rc == MDBX_NOTFOUND && values_count) if (rc == MDBX_NOTFOUND && values_count)
*values_count = 0; *values_count = 0;
@ -12136,8 +12223,10 @@ int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data,
MDBX_cursor mc; MDBX_cursor mc;
MDBX_xcursor mx; MDBX_xcursor mx;
MDBX_val old_data; MDBX_val old_data;
mdbx_cursor_init(&mc, txn, dbi, &mx); int rc = mdbx_cursor_init(&mc, txn, dbi, &mx);
int rc = mdbx_cursor_set(&mc, key, &old_data, MDBX_SET, NULL); if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_cursor_set(&mc, key, &old_data, MDBX_SET, NULL);
if (unlikely(rc != MDBX_SUCCESS)) { if (unlikely(rc != MDBX_SUCCESS)) {
if (rc == MDBX_NOTFOUND && data) { if (rc == MDBX_NOTFOUND && data) {
mc.mc_next = txn->mt_cursors[dbi]; mc.mc_next = txn->mt_cursors[dbi];