├── +lmdb ├── Cursor.m ├── DB.m ├── Transaction.m ├── make.m └── private │ └── .gitignore ├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── include ├── mexplus.h └── mexplus │ ├── arguments.h │ ├── dispatch.h │ ├── mxarray.h │ └── mxtypes.h ├── src ├── LMDB_.cc └── liblmdb │ ├── .gitignore │ ├── CHANGES │ ├── COPYRIGHT │ ├── Doxyfile │ ├── LICENSE │ ├── Makefile │ ├── lmdb.h │ ├── mdb.c │ ├── mdb_copy.1 │ ├── mdb_copy.c │ ├── mdb_dump.1 │ ├── mdb_dump.c │ ├── mdb_load.1 │ ├── mdb_load.c │ ├── mdb_stat.1 │ ├── mdb_stat.c │ ├── midl.c │ ├── midl.h │ ├── mtest.c │ ├── mtest2.c │ ├── mtest3.c │ ├── mtest4.c │ ├── mtest5.c │ ├── mtest6.c │ ├── sample-bdb.txt │ ├── sample-mdb.txt │ └── tooltag └── test └── testLMDB.m /+lmdb/Cursor.m: -------------------------------------------------------------------------------- 1 | classdef Cursor < handle 2 | %CURSOR LMDB cursor wrapper. 3 | % 4 | % cursor = database.cursor('RDONLY', true); 5 | % while cursor.next() 6 | % key = cursor.key; 7 | % value = cursor.value; 8 | % end 9 | % clear cursor; 10 | % 11 | % See also lmdb.DB.cursor 12 | 13 | properties (Access = private) 14 | id_ % ID of the session. 15 | transaction_id_ % ID of the transaction session. 16 | database_id_ % ID of the database session. 17 | end 18 | 19 | properties (Dependent) 20 | key 21 | value 22 | end 23 | 24 | methods (Hidden) 25 | function this = Cursor(database_id, varargin) 26 | %CURSOR Create a new cursor. 27 | assert(isscalar(this)); 28 | assert(isscalar(database_id)); 29 | this.database_id_ = database_id; 30 | this.transaction_id_ = LMDB_('txn_new', database_id, varargin{:}); 31 | this.id_ = LMDB_('cursor_new', this.transaction_id_, database_id); 32 | end 33 | end 34 | 35 | methods 36 | function delete(this) 37 | %DELETE Destructor. 38 | assert(isscalar(this)); 39 | LMDB_('cursor_delete', this.id_); 40 | LMDB_('txn_commit', this.transaction_id_); 41 | LMDB_('txn_delete', this.transaction_id_); 42 | end 43 | 44 | function flag = next(this) 45 | %NEXT Proceed to next and return true if the value exists. 46 | assert(isscalar(this)); 47 | flag = LMDB_('cursor_next', this.id_); 48 | end 49 | 50 | function flag = previous(this) 51 | %PREVIOUS Proceed to previous and return true if the value exists. 52 | assert(isscalar(this)); 53 | flag = LMDB_('cursor_previous', this.id_); 54 | end 55 | 56 | function flag = first(this) 57 | %FIRST Proceed to the first and return true if the value exists. 58 | assert(isscalar(this)); 59 | flag = LMDB_('cursor_first', this.id_); 60 | end 61 | 62 | function flag = last(this) 63 | %LAST Proceed to the last and return true if the value exists. 64 | assert(isscalar(this)); 65 | flag = LMDB_('cursor_last', this.id_); 66 | end 67 | 68 | function flag = find(this, key) 69 | %FIND Proceed to the specified key and return true if the value exists. 70 | assert(isscalar(this)); 71 | flag = LMDB_('cursor_find', this.id_, key); 72 | end 73 | 74 | function key_value = get.key(this) 75 | %GETKEY Return the current key. 76 | key_value = LMDB_('cursor_getkey', this.id_); 77 | end 78 | 79 | function set.key(this, key_value) 80 | %SETKEY Set the current key. 81 | LMDB_('cursor_setkey', this.id_, key_value); 82 | end 83 | 84 | function setKey(this, key_value, varargin) 85 | %SETKEY Set the current key. 86 | % 87 | % Options 88 | % 'CURRENT' default true 89 | % 'NODUPDATA' default false 90 | % 'NOOVERWRITE' default false 91 | % 'RESERVE' default false 92 | % 'APPEND' default false 93 | % 'APPENDDUP' default false 94 | % 'MULTIPLE' default false 95 | LMDB_('cursor_setkey', this.id_, key_value, varargin{:}); 96 | end 97 | 98 | function value_value = get.value(this) 99 | %GETVALUE Return the current value. 100 | value_value = LMDB_('cursor_getvalue', this.id_); 101 | end 102 | 103 | function set.value(this, value_value) 104 | %SETVALUE Set the current value. 105 | LMDB_('cursor_setvalue', this.id_, value_value); 106 | end 107 | 108 | function setValue(this, value_value, varargin) 109 | %SETVALUE Set the current value. 110 | % 111 | % Options 112 | % 'CURRENT' default true 113 | % 'NODUPDATA' default false 114 | % 'NOOVERWRITE' default false 115 | % 'RESERVE' default false 116 | % 'APPEND' default false 117 | % 'APPENDDUP' default false 118 | % 'MULTIPLE' default false 119 | LMDB_('cursor_setvalue', this.id_, value_value, varargin{:}); 120 | end 121 | 122 | function remove(this, varargin) 123 | %REMOVE Delete the current key and value. 124 | % 125 | % Options 126 | % 'NODUPDATA' default false 127 | LMDB_('cursor_remove', varargin{:}); 128 | end 129 | end 130 | 131 | end 132 | -------------------------------------------------------------------------------- /+lmdb/DB.m: -------------------------------------------------------------------------------- 1 | classdef DB < handle 2 | %DB LMDB wrapper. 3 | % 4 | % % Open and close. 5 | % database = lmdb.DB('./db'); 6 | % clear database; 7 | % 8 | % % Read and write. 9 | % database.put('key1', 'value1'); 10 | % database.put('key2', 'value2'); 11 | % value1 = database.get('key1'); 12 | % database.remove('key1'); 13 | % 14 | % % Iterator. 15 | % database.each(@(key, value) disp([key, ':', 'value'])); 16 | % count = database.reduce(@(key, value, count) count + 1, 0); 17 | % 18 | % % Cursor. 19 | % cursor = database.cursor('RDONLY', true); 20 | % while cursor.next() 21 | % key = cursor.key; 22 | % value = cursor.value; 23 | % end 24 | % clear cursor; 25 | % 26 | % See also lmdb.DB.DB 27 | 28 | properties (Access = private) 29 | id_ % ID of the session. 30 | end 31 | 32 | methods 33 | function this = DB(filename, varargin) 34 | %DB Create a new database. 35 | % 36 | % database = lmdb.DB('./db') 37 | % database = lmdb.DB('./db', 'RDONLY', true, ...) 38 | % 39 | % Options 40 | % 'MODE' default 0664 41 | % 'FIXEDMAP' default false 42 | % 'NOSUBDIR' default false 43 | % 'NOSYNC' default false 44 | % 'RDONLY', default false 45 | % 'NOMETASYNC' default false 46 | % 'WRITEMAP' default false 47 | % 'MAPASYNC' default false 48 | % 'NOTLS' default false 49 | % 'NOLOCK' default false 50 | % 'NORDAHEAD' default false 51 | % 'NOMEMINIT' default false 52 | % 'REVERSEKEY' default false 53 | % 'DUPSORT' default false 54 | % 'INTEGERKEY' default false 55 | % 'DUPFIXED' default false 56 | % 'INTEGERDUP' default false 57 | % 'REVERSEDUP' default false 58 | % 'CREATE' default true unless 'RDONLY' specified 59 | % 'MAPSIZE' default 10485760 60 | % 'MAXREADERS' default 126 61 | % 'MAXDBS' default 0 62 | % 'NAME' default '' 63 | assert(isscalar(this)); 64 | assert(ischar(filename)); 65 | this.id_ = LMDB_('new', filename, varargin{:}); 66 | end 67 | 68 | function delete(this) 69 | %DELETE Destructor. 70 | assert(isscalar(this)); 71 | LMDB_('delete', this.id_); 72 | end 73 | 74 | function result = get(this, key) 75 | %GET Query a record. 76 | assert(isscalar(this)); 77 | result = LMDB_('get', this.id_, key); 78 | end 79 | 80 | function put(this, key, value, varargin) 81 | %PUT Save a record in the database. 82 | % 83 | % Options 84 | % 'NODUPDATA' default false 85 | % 'NOOVERWRITE' default false 86 | % 'RESERVE' default false 87 | % 'APPEND' default false 88 | assert(isscalar(this)); 89 | LMDB_('put', this.id_, key, value, varargin{:}); 90 | end 91 | 92 | function remove(this, key, varargin) 93 | %REMOVE Remove a record. 94 | assert(isscalar(this)); 95 | LMDB_('remove', this.id_, key, varargin{:}); 96 | end 97 | 98 | function each(this, func) 99 | %EACH Apply a function to each record. 100 | % 101 | % Example: show each record. 102 | % 103 | % database.each(@(key, value) disp([key, ': ', value])) 104 | assert(isscalar(this)); 105 | assert(ischar(func) || isa(func, 'function_handle')); 106 | assert(abs(nargin(func)) > 1); 107 | LMDB_('each', this.id_, func); 108 | end 109 | 110 | function result = reduce(this, func, initial_value) 111 | %REDUCE Apply an accumulation function to each record. 112 | % 113 | % Example: counting the number of 'foo' in the records. 114 | % 115 | % database.reduce(@(key, val, accum) accum + strcmp(val, 'foo'), 0) 116 | assert(isscalar(this)); 117 | assert(ischar(func) || isa(func, 'function_handle')); 118 | assert(abs(nargin(func)) > 2 && abs(nargout(func)) > 0); 119 | result = LMDB_('reduce', this.id_, func, initial_value); 120 | end 121 | 122 | function transaction = begin(this, varargin) 123 | %TRANSACTION Create a new transaction. 124 | % 125 | % transaction = database.begin() 126 | % try 127 | % transaction.put(key, value) 128 | % transaction.commit() 129 | % catch exception 130 | % transaction.abort() 131 | % end 132 | % 133 | % Options 134 | % 'RDONLY' default false 135 | % 136 | % Note: Calling a database method when there is an active transaction results 137 | % in deadlock. Also, destroying a database while there is an active 138 | % transaction will crash the process. 139 | assert(isscalar(this)); 140 | transaction = lmdb.Transaction(this.id_, varargin{:}); 141 | end 142 | 143 | function cursor_value = cursor(this, varargin) 144 | %CURSOR Create a new cursor. 145 | % 146 | % Options 147 | % 'RDONLY' default false 148 | % 149 | % Example 150 | % 151 | % cursor = database.cursor('RDONLY', true); 152 | % while cursor.next() 153 | % key = cursor.key; 154 | % value = cursor.value; 155 | % end 156 | % clear cursor; 157 | % 158 | % Clear a cursor before the database. 159 | % 160 | % See also lmdb.Cursor 161 | assert(isscalar(this)); 162 | cursor_value = lmdb.Cursor(this.id_, varargin{:}); 163 | end 164 | 165 | function [key, value] = first(this) 166 | %FIRST Get the first key-value pair. 167 | % 168 | % [key, value] = database.first() 169 | assert(isscalar(this)); 170 | cursor = this.cursor('RDONLY', true); 171 | if cursor.next() 172 | key = cursor.key; 173 | if nargout > 1 174 | value = cursor.value; 175 | end 176 | else 177 | key = []; 178 | value = []; 179 | end 180 | clear cursor; 181 | end 182 | 183 | function result = keys(this) 184 | %KEYS Get a cell array of all keys. 185 | assert(isscalar(this)); 186 | result = LMDB_('keys', this.id_); 187 | end 188 | 189 | function result = values(this) 190 | %VALUES Get a cell array of all values. 191 | assert(isscalar(this)); 192 | result = LMDB_('values', this.id_); 193 | end 194 | 195 | function result = stat(this) 196 | %STAT Get the environment statistics. 197 | assert(isscalar(this)); 198 | result = LMDB_('stat', this.id_); 199 | end 200 | end 201 | 202 | end 203 | -------------------------------------------------------------------------------- /+lmdb/Transaction.m: -------------------------------------------------------------------------------- 1 | classdef Transaction < handle 2 | %TRANSACTION LMDB transaction wrapper. 3 | % 4 | % transaction = database.begin() 5 | % try 6 | % transaction.put(key, value) 7 | % transaction.commit() 8 | % catch exception 9 | % transaction.abort() 10 | % end 11 | % 12 | % See also lmdb.DB.begin 13 | 14 | properties (Access = private) 15 | id_ % ID of the session. 16 | end 17 | 18 | methods (Hidden) 19 | function this = Transaction(database_id, varargin) 20 | %TRANSACTION Create a new transaction. 21 | % 22 | % See also lmdb.DB.begin 23 | assert(isscalar(this)); 24 | assert(isscalar(database_id)); 25 | this.id_ = LMDB_('txn_new', database_id, varargin{:}); 26 | end 27 | end 28 | 29 | methods 30 | function delete(this) 31 | %DELETE Destructor. 32 | assert(isscalar(this)); 33 | LMDB_('txn_delete', this.id_); 34 | end 35 | 36 | function commit(this) 37 | %ABORT Commit a transaction. 38 | assert(isscalar(this)); 39 | LMDB_('txn_commit', this.id_); 40 | end 41 | 42 | function abort(this) 43 | %ABORT Abort a transaction. 44 | assert(isscalar(this)); 45 | LMDB_('txn_abort', this.id_); 46 | end 47 | 48 | function result = get(this, key) 49 | %GET Query a record. 50 | assert(isscalar(this)); 51 | result = LMDB_('txn_get', this.id_, key); 52 | end 53 | 54 | function put(this, key, value, varargin) 55 | %PUT Save a record in the database. 56 | % 57 | % Options 58 | % 'NODUPDATA' default false 59 | % 'NOOVERWRITE' default false 60 | % 'RESERVE' default false 61 | % 'APPEND' default false 62 | assert(isscalar(this)); 63 | LMDB_('txn_put', this.id_, key, value, varargin{:}); 64 | end 65 | 66 | function remove(this, key, varargin) 67 | %REMOVE Remove a record. 68 | assert(isscalar(this)); 69 | LMDB_('txn_remove', this.id_, key, varargin{:}); 70 | end 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /+lmdb/make.m: -------------------------------------------------------------------------------- 1 | function make(varargin) 2 | %MAKE Build MEX files. 3 | % 4 | % Build MEX files in the project. 5 | % 6 | % Example 7 | % 8 | % lmdb.make 9 | % lmdb.make('test') 10 | % lmdb.make('clean') 11 | % 12 | root = fileparts(fileparts(mfilename('fullpath'))); 13 | command = sprintf('make -C %s MATLABDIR=%s%s', ... 14 | root, ... 15 | matlabroot, ... 16 | sprintf(' %s', varargin{:})); 17 | disp(command); 18 | system(command); 19 | end 20 | -------------------------------------------------------------------------------- /+lmdb/private/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyamagu/matlab-lmdb/66729a0e7f623d2c38ff0d3609a7df8a7517314a/+lmdb/private/.gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | *.mex* 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Kota Yamaguchi 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for matlab-lmdb 2 | LMDBDIR := src/liblmdb 3 | ECHO := echo 4 | MATLABDIR ?= /usr/local/matlab 5 | MATLAB := $(MATLABDIR)/bin/matlab 6 | MEX := $(MATLABDIR)/bin/mex 7 | MEXEXT := $(shell $(MATLABDIR)/bin/mexext) 8 | MEXFLAGS := -Iinclude -I$(LMDBDIR) CXXFLAGS="\$$CXXFLAGS -std=c++11" 9 | TARGET := +lmdb/private/LMDB_.$(MEXEXT) 10 | 11 | .PHONY: all test clean 12 | 13 | all: $(TARGET) 14 | 15 | $(TARGET): src/LMDB_.cc $(LMDBDIR)/liblmdb.a 16 | $(MEX) -output $@ $< $(MEXFLAGS) $(LMDBDIR)/liblmdb.a 17 | 18 | $(LMDBDIR)/liblmdb.a: $(LMDBDIR) 19 | $(MAKE) -C $(LMDBDIR) 20 | 21 | test: $(TARGET) 22 | $(ECHO) "run test/testLMDB" | $(MATLAB) -nodisplay 23 | 24 | clean: 25 | $(MAKE) -C $(LMDBDIR) clean 26 | $(RM) $(TARGET) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Matlab LMDB 2 | =========== 3 | 4 | Matlab LMDB wrapper for UNIX environment. 5 | 6 | * See [LMDB website](http://symas.com/mdb/). 7 | * The implementation is based on [mexplus](http://github.com/kyamagu/mexplus). 8 | 9 | See also [matlab-leveldb](http://github.com/kyamagu/matlab-leveldb). 10 | 11 | The package does not contain any data serialization. Use `char` for storing keys and values. You will need to serialize any Matlab variables to directly save in the database. For example, using `num2str` and `str2double`. There are different serialization options, such as [this serialization package](https://github.com/kyamagu/matlab-serialization) or [JSON format](https://github.com/kyamagu/matlab-json). Those using [Caffe](https://github.com/BVLC/caffe) might want to use a Datum converter in [the caffe-extension branch](https://github.com/kyamagu/matlab-lmdb/tree/caffe-extension). 12 | 13 | Build 14 | ----- 15 | 16 | Specify the MATLABDIR path. 17 | 18 | make MATLABDIR=/usr/local/MATLAB/R2015a 19 | make test 20 | 21 | Edit `Makefile` to customize the build process. 22 | 23 | In the caffe-extension branch, also specify the path to caffe. 24 | 25 | make MATLABDIR=/usr/local/MATLAB/R2015a CAFFEDIR=../caffe 26 | 27 | Example 28 | ------- 29 | 30 | % Open and close. 31 | database = lmdb.DB('./db', 'MAPSIZE', 1024^3); 32 | clear database; 33 | readonly_database = lmdb.DB('./db', 'RDONLY', true, 'NOLOCK', true); 34 | clear readonly_database; 35 | 36 | % Read and write. 37 | database.put('key1', 'value1'); 38 | database.put('key2', 'value2'); 39 | value1 = database.get('key1'); 40 | database.remove('key1'); 41 | 42 | % Iterator. 43 | database.each(@(key, value) disp([key, ': ', value])); 44 | count = database.reduce(@(key, value, count) count + 1, 0); 45 | 46 | % Cursor. 47 | cursor = database.cursor('RDONLY', true); 48 | while cursor.next() 49 | key = cursor.key; 50 | value = cursor.value; 51 | end 52 | clear cursor; 53 | 54 | % Transaction. 55 | transaction = database.begin(); 56 | try 57 | transaction.put('key1', 'value1'); 58 | transaction.put('key2', 'value2'); 59 | transaction.commit(); 60 | catch exception 61 | transaction.abort(); 62 | end 63 | clear transaction; 64 | 65 | % Dump. 66 | keys = database.keys(); 67 | values = database.values(); 68 | 69 | See `help` documentation of each function, or visit [LMDB documentation](http://symas.com/mdb/doc/index.html) to understand the flags. 70 | 71 | Caffe extension 72 | --------------- 73 | 74 | The `caffe-extension` branch includes utilities to convert [`Datum` protocol buffer](https://github.com/BVLC/caffe/blob/master/src/caffe/proto/caffe.proto). 75 | 76 | % Make plain datum. 77 | image = imread('peppers.png'); 78 | label = 1; 79 | datum = caffe_pb.toDatum(image, label); 80 | 81 | % Make encoded datum. 82 | fid = fopen('peppers.png', 'r'); 83 | png_image = fread(fid, inf, 'uint8=>uint8'); 84 | fclose(fid); 85 | label = 1; 86 | datum = caffe_pb.toEncodedDatum(png_image, label); 87 | 88 | % Read datum. 89 | [png_image, label] = caffe_pb.fromDatum(datum); 90 | 91 | TODO 92 | ---- 93 | 94 | * Finer transaction API. 95 | -------------------------------------------------------------------------------- /include/mexplus.h: -------------------------------------------------------------------------------- 1 | /** MEXPLUS: C++ Matlab MEX development kit. 2 | * 3 | * Kota Yamaguchi 2014 4 | */ 5 | 6 | #ifndef __MEXPLUS_H__ 7 | #define __MEXPLUS_H__ 8 | 9 | #include 10 | #include 11 | 12 | #endif // __MEXPLUS_H__ 13 | -------------------------------------------------------------------------------- /include/mexplus/arguments.h: -------------------------------------------------------------------------------- 1 | /** MEX function arguments helper library. 2 | * 3 | * Example: writing a MEX function that takes 2 input arguments and 1 optional 4 | * flag, and returns one output. 5 | * 6 | * % myFunction.m 7 | * function result = myFunction(arg1, arg2, varargin) 8 | * 9 | * // myFunction.cc 10 | * #include "mexplus/arguments.h" 11 | * using namespace std; 12 | * using namespace mexplus; 13 | * void mexFunction(int nlhs, mxArray* plhs[], 14 | * int nrhs, const mxArray* prhs) { 15 | * InputArguments input(nrhs, prhs, 2, 1, "Flag"); 16 | * OutputArguments output(nlhs, plhs, 1); 17 | * vector result = myFunction(input.get >(0), 18 | * input.get(1), 19 | * input.get("Flag", 0)); 20 | * output.set(0, result); 21 | * } 22 | * 23 | * % Build 24 | * >> mex myFunction.cc src/mexplus/arguments.cc 25 | * 26 | * Kota Yamaguchi 2014 27 | */ 28 | 29 | #ifndef __MEXPLUS_ARGUMENTS_H__ 30 | #define __MEXPLUS_ARGUMENTS_H__ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace mexplus { 38 | 39 | /** Utility to parse input arguments. 40 | * 41 | * Example: parse 2 mandatory and 2 optional arguments. 42 | * 43 | * InputArguments input(nrhs, prhs, 2, 2, "option1", "option2"); 44 | * myFunction2(input.get(0), 45 | * input.get(1), 46 | * input.get("option1", "foo"), 47 | * input.get("option2", 10)); 48 | * 49 | * Example: parse 1 + 2 argument format or 2 + 2 argument format. 50 | * 51 | * InputArguments input; 52 | * input.define("format1", 1, 2, "option1", "option2"); 53 | * input.define("format2", 2, 2, "option1", "option2"); 54 | * input.parse(nrhs, prhs); 55 | * if (input.is("format1")) 56 | * myFunction(input.get(0), 57 | * input.get("option1", "foo"), 58 | * input.get("option2", 10)); 59 | * else if (input.is("format2")) 60 | * myFunction2(input.get(0), 61 | * input.get(1), 62 | * input.get("option1", "foo"), 63 | * input.get("option2", 10)); 64 | * 65 | */ 66 | class InputArguments { 67 | public: 68 | /** Case-insensitive comparator for std::string. 69 | */ 70 | struct CaseInsensitiveComparator { 71 | struct CaseInsensitiveElementComparator { 72 | bool operator() (const char& c1, const char& c2) const { 73 | return tolower(c1) < tolower(c2); 74 | } 75 | }; 76 | bool operator() (const std::string & s1, const std::string & s2) const { 77 | return std::lexicographical_compare(s1.begin(), 78 | s1.end(), 79 | s2.begin(), 80 | s2.end(), 81 | CaseInsensitiveElementComparator()); 82 | } 83 | }; 84 | 85 | typedef std::map 86 | OptionMap; 87 | /** Definition of arguments. 88 | */ 89 | typedef struct Definition_tag { 90 | std::vector mandatories; 91 | OptionMap optionals; 92 | } Definition; 93 | 94 | /** Empty constructor. 95 | */ 96 | InputArguments() {} 97 | /** Shorthand constructor for a single argument definition. 98 | */ 99 | InputArguments(int nrhs, 100 | const mxArray* prhs[], 101 | int mandatory_size = 1, 102 | int option_size = 0, 103 | ...) { 104 | Definition* definition = &definitions_["default"]; 105 | definition->mandatories.resize(mandatory_size, NULL); 106 | va_list variable_list; 107 | va_start(variable_list, option_size); 108 | fillOptionalDefinition(option_size, &definition->optionals, variable_list); 109 | va_end(variable_list); 110 | parse(nrhs, prhs); 111 | } 112 | virtual ~InputArguments() {} 113 | /** Define a new argument format. 114 | */ 115 | void define(const std::string name, 116 | int mandatory_size, 117 | int option_size = 0, 118 | ...) { 119 | Definition* definition = &definitions_[name]; 120 | definition->mandatories.resize(mandatory_size); 121 | va_list variable_list; 122 | va_start(variable_list, option_size); 123 | fillOptionalDefinition(option_size, &definition->optionals, variable_list); 124 | va_end(variable_list); 125 | } 126 | /** Parse arguments from mexFunction input. 127 | */ 128 | void parse(int nrhs, const mxArray* prhs[]) { 129 | if (definitions_.empty()) 130 | mexErrMsgIdAndTxt("mexplus:arguments:error", "No format defined."); 131 | std::map::iterator entry; 132 | std::vector::iterator> delete_positions; 133 | for (entry = definitions_.begin(); entry != definitions_.end(); ++entry) 134 | if (!parseDefinition(nrhs, prhs, &entry->second)) 135 | delete_positions.push_back(entry); 136 | for (int i = 0; i < delete_positions.size(); ++i) 137 | definitions_.erase(delete_positions[i]); 138 | if (definitions_.empty()) 139 | mexErrMsgIdAndTxt("mexplus:arguments:error", 140 | (error_message_.empty()) ? 141 | "Invalid arguments." : error_message_.c_str()); 142 | if (definitions_.size() > 1) 143 | mexWarnMsgIdAndTxt("mexplus:arguments:warning", 144 | "Input arguments match more than one signature."); 145 | } 146 | /** Return which format is chosen. 147 | */ 148 | bool is(const std::string& name) const { 149 | std::map::const_iterator entry = 150 | definitions_.find(name); 151 | return (entry != definitions_.end()); 152 | } 153 | /** Get a parsed mandatory argument. 154 | */ 155 | const mxArray* get(size_t index) const { 156 | if (definitions_.empty()) 157 | mexErrMsgIdAndTxt("mexplus:arguments:error", "No format defined."); 158 | const Definition& definition = definitions_.begin()->second; 159 | if (index >= definition.mandatories.size()) 160 | mexErrMsgIdAndTxt("mexplus:arguments:error", "Index out of range."); 161 | return definition.mandatories[index]; 162 | } 163 | /** Get a parsed mandatory argument. 164 | */ 165 | template 166 | T get(size_t index) const; 167 | template 168 | void get(size_t index, T* value) const; 169 | /** Get a parsed optional argument. 170 | */ 171 | const mxArray* get(const std::string& option_name) const { 172 | if (definitions_.empty()) 173 | mexErrMsgIdAndTxt("mexplus:arguments:error", "No format defined."); 174 | const Definition& definition = definitions_.begin()->second; 175 | std::map::const_iterator entry = 176 | definition.optionals.find(option_name); 177 | if (entry == definition.optionals.end()) 178 | mexErrMsgIdAndTxt("mexplus:arguments:error", 179 | "Unknown option %s.", 180 | option_name.c_str()); 181 | return entry->second; 182 | } 183 | /** Get a parsed optional argument. 184 | */ 185 | template 186 | T get(const std::string& option_name, const T& default_value) const; 187 | template 188 | void get(const std::string& option_name, 189 | const T& default_value, 190 | T* value) const; 191 | /** Access raw mxArray* pointer. 192 | */ 193 | const mxArray* operator[] (size_t index) const { return get(index); } 194 | /** Access raw mxArray* pointer. 195 | */ 196 | const mxArray* operator[] (const std::string& option_name) const { 197 | return get(option_name); 198 | } 199 | 200 | private: 201 | /** Fill in optional arguments definition. 202 | */ 203 | void fillOptionalDefinition(int option_size, 204 | OptionMap* optionals, 205 | va_list variable_list) { 206 | for (int i = 0; i < option_size; ++i) { 207 | const char* option_name = va_arg(variable_list, const char*); 208 | (*optionals)[std::string(option_name)] = NULL; 209 | } 210 | } 211 | /** Try to parse one definition or return false on failure. 212 | */ 213 | bool parseDefinition(int nrhs, 214 | const mxArray* prhs[], 215 | Definition* definition) { 216 | std::stringstream message; 217 | if (nrhs < definition->mandatories.size()) { 218 | message << "Too few arguments: " << nrhs << " for at least " 219 | << definition->mandatories.size() << "."; 220 | error_message_.assign(message.str()); 221 | return false; 222 | } 223 | int index = 0; 224 | for (; index < definition->mandatories.size(); ++index) 225 | definition->mandatories[index] = prhs[index]; 226 | for (; index < nrhs; ++index) { 227 | // Check if option name is valid. 228 | const mxArray* option_name_array = prhs[index]; 229 | if (!mxIsChar(option_name_array)) { 230 | message << "Option name must be char but is given " 231 | << mxGetClassName(option_name_array) << "."; 232 | error_message_.assign(message.str()); 233 | return false; 234 | } 235 | char option_name[64]; 236 | if (mxGetString(option_name_array, option_name, sizeof(option_name))) { 237 | message << "Option name too long."; 238 | error_message_.assign(message.str()); 239 | return false; 240 | } 241 | std::map::iterator entry = 242 | definition->optionals.find(option_name); 243 | if (entry == definition->optionals.end()) { 244 | message << "Invalid option name: '" << option_name << "'."; 245 | error_message_.assign(message.str()); 246 | return false; 247 | } 248 | // Check if options are even. 249 | if (++index >= nrhs) { 250 | message << "Missing option value for option '" << option_name << "'."; 251 | error_message_.assign(message.str()); 252 | return false; 253 | } 254 | if (entry->second) 255 | mexErrMsgIdAndTxt("mexplus:arguments:warning", 256 | "Option '%s' appeared more than once.", 257 | option_name); 258 | entry->second = prhs[index]; 259 | } 260 | return true; 261 | } 262 | /** Format definitions. 263 | */ 264 | std::map definitions_; 265 | /** Last error message. 266 | */ 267 | std::string error_message_; 268 | }; 269 | 270 | template 271 | T InputArguments::get(size_t index) const { 272 | T value; 273 | get(index, &value); 274 | return value; 275 | } 276 | 277 | template 278 | void InputArguments::get(size_t index, T* value) const { 279 | MxArray::to(get(index), value); 280 | } 281 | 282 | template 283 | T InputArguments::get(const std::string& option_name, 284 | const T& default_value) const { 285 | T value; 286 | get(option_name, default_value, &value); 287 | return value; 288 | } 289 | 290 | template 291 | void InputArguments::get(const std::string& option_name, 292 | const T& default_value, 293 | T* value) const { 294 | MxArray option(get(option_name)); 295 | if (option) 296 | option.to(value); 297 | else 298 | *value = default_value; 299 | } 300 | 301 | /** Output arguments wrapper. 302 | * 303 | * Example: 304 | * 305 | * OutputArguments output(nlhs, plhs, 3); 306 | * MxArray cell = MxArray::Cell(1, 3); 307 | * cell.set(0, 0); 308 | * cell.set(1, 1); 309 | * cell.set(2, "value"); 310 | * output.set(0, 1); 311 | * output.set(1, "foo"); 312 | * output.set(2, cell.release()); 313 | */ 314 | class OutputArguments { 315 | public: 316 | /** Construct output argument wrapper. 317 | */ 318 | OutputArguments(int nlhs, 319 | mxArray** plhs, 320 | int maximum_size = 1, 321 | int mandatory_size = 0) : nlhs_(nlhs), plhs_(plhs) { 322 | if (mandatory_size > nlhs) 323 | mexErrMsgIdAndTxt("mexplus:arguments:error", 324 | "Too few output: %d for %d.", 325 | nlhs, 326 | mandatory_size); 327 | if (maximum_size < nlhs) 328 | mexErrMsgIdAndTxt("mexplus:arguments:error", 329 | "Too many output: %d for %d.", 330 | nlhs, 331 | maximum_size); 332 | } 333 | /** Safely assign mxArray to the output. 334 | */ 335 | void set(size_t index, mxArray* value) { 336 | if (index < nlhs_) 337 | plhs_[index] = value; 338 | } 339 | /** Safely assign T to the output. 340 | */ 341 | template 342 | void set(size_t index, const T& value) { 343 | set(index, MxArray::from(value)); 344 | } 345 | /** Size of the output. 346 | */ 347 | size_t size() const { return nlhs_; } 348 | /** Const square bracket operator. 349 | */ 350 | mxArray* const& operator[] (size_t index) const { 351 | if (index >= nlhs_) 352 | mexErrMsgIdAndTxt("mexplus:arguments:error", 353 | "Output index out of range: %d.", 354 | index); 355 | return plhs_[index]; 356 | } 357 | /** Mutable square bracket operator. 358 | */ 359 | mxArray*& operator[] (size_t index) { 360 | if (index >= nlhs_) 361 | mexErrMsgIdAndTxt("mexplus:arguments:error", 362 | "Output index out of range: %d.", 363 | index); 364 | return plhs_[index]; 365 | } 366 | 367 | private: 368 | /** Number of output arguments. 369 | */ 370 | int nlhs_; 371 | /** Output argument array. 372 | */ 373 | mxArray** plhs_; 374 | }; 375 | 376 | } // namespace mexplus 377 | 378 | #endif // __MEXPLUS_ARGUMENTS_H__ 379 | -------------------------------------------------------------------------------- /include/mexplus/dispatch.h: -------------------------------------------------------------------------------- 1 | /** MEX dispatch library. 2 | * 3 | * This helper contains MEX_DEFINE() macro to help create a dispatchable MEX 4 | * file. Two files are required to create a new mex function. Suppose you are 5 | * creating two MEX functions `myfunc` and `myfunc2`. Then, make the following 6 | * files. 7 | * 8 | * myfunc.m 9 | * 10 | * function output_args = myfunc( varargin ) 11 | * %MYFUNC Description of the function. 12 | * % 13 | * % Details go here. 14 | * % 15 | * output_args = mylibrary(mfilename, varargin{:}) 16 | * end 17 | * 18 | * myfunc2.m 19 | * 20 | * function output_args = myfunc( varargin ) 21 | * %MYFUNC Description of the function. 22 | * % 23 | * % Details go here. 24 | * % 25 | * output_args = mylibrary(mfilename, varargin{:}) 26 | * end 27 | * 28 | * These files contains help documentation and a line to invoke the mex 29 | * function. 30 | * 31 | * mylibrary.cc 32 | * 33 | * #include 34 | * 35 | * MEX_DEFINE(myfunc) (int nlhs, mxArray* plhs[], 36 | * int nrhs, const mxArray* prhs[]) { 37 | * ... 38 | * } 39 | * 40 | * MEX_DEFINE(myfunc2) (int nlhs, mxArray* plhs[], 41 | * int nrhs, const mxArray* prhs[]) { 42 | * ... 43 | * } 44 | * 45 | * MEX_DISPATCH 46 | * 47 | * This file is the implementation of the mex function. The MEX_DEFINE macro 48 | * defines an entry point of the function. MEX_DISPATCH macro at the end 49 | * inserts necessary codes to dispatch function calls to an appropriate 50 | * function. 51 | * 52 | * Similarly, you can write another pair of .m (and C++) file to add to your 53 | * library. You may split MEX_DEFINE macros in multiple C++ files. In that 54 | * case, have MEX_DISPATCH macro in one of the files. 55 | * 56 | * Kota Yamaguchi 2014 57 | */ 58 | 59 | #ifndef __MEXPLUS_DISPATCH_H__ 60 | #define __MEXPLUS_DISPATCH_H__ 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | namespace mexplus { 68 | 69 | class OperationCreator; 70 | inline void CreateOperation(const std::string& name, 71 | OperationCreator* creator); 72 | 73 | /** Abstract operation class. Child class must implement operator(). 74 | */ 75 | class Operation { 76 | public: 77 | /** Destructor. 78 | */ 79 | virtual ~Operation() {} 80 | /** Execute the operation. 81 | */ 82 | virtual void operator()(int nlhs, 83 | mxArray *plhs[], 84 | int nrhs, 85 | const mxArray *prhs[]) = 0; 86 | }; 87 | 88 | /** Base class for operation creators. 89 | */ 90 | class OperationCreator { 91 | public: 92 | /** Register an operation in the constructor. 93 | */ 94 | OperationCreator(const std::string& name) { 95 | CreateOperation(name, this); 96 | } 97 | /** Destructor. 98 | */ 99 | virtual ~OperationCreator() {} 100 | /** Implementation must return a new instance of the operation. 101 | */ 102 | virtual Operation* create() = 0; 103 | }; 104 | 105 | /** Implementation of the operation creator to be used as composition in an 106 | * Operator class. 107 | */ 108 | template 109 | class OperationCreatorImpl : public OperationCreator { 110 | public: 111 | OperationCreatorImpl(const std::string& name) : OperationCreator(name) {} 112 | virtual Operation* create() { return new OperationClass; } 113 | }; 114 | 115 | /** Factory class for operations. 116 | */ 117 | class OperationFactory { 118 | public: 119 | /** Register a new creator. 120 | */ 121 | friend void CreateOperation(const std::string& name, 122 | OperationCreator* creator); 123 | /** Create a new instance of the registered operation. 124 | */ 125 | static Operation* create(const std::string& name) { 126 | std::map::const_iterator it = 127 | registry()->find(name); 128 | if (it == registry()->end()) 129 | return static_cast(NULL); 130 | else 131 | return it->second->create(); 132 | } 133 | 134 | private: 135 | /** Obtain a pointer to the registration table. 136 | */ 137 | static std::map* registry() { 138 | static std::map registry_table; 139 | return ®istry_table; 140 | } 141 | }; 142 | 143 | /** Register a new creator in OperationFactory. 144 | */ 145 | inline void CreateOperation(const std::string& name, 146 | OperationCreator* creator) { 147 | OperationFactory::registry()->insert(make_pair(name, creator)); 148 | } 149 | 150 | /** Key-value storage to make a stateful MEX function. 151 | * 152 | * #include 153 | * 154 | * using namespace std; 155 | * using namespace mexplus; 156 | * 157 | * class Database; 158 | * 159 | * template class Session; 160 | * 161 | * MEX_DEFINE(open) (int nlhs, mxArray* plhs[], 162 | * int nrhs, const mxArray* prhs[]) { 163 | * unique_ptr database(new Database(...)); 164 | * database->open(...); 165 | * intptr_t session_id = Session::create(database.release()); 166 | * plhs[0] = mxCreateDoubleScalar(session_id); 167 | * } 168 | * 169 | * MEX_DEFINE(query) (int nlhs, mxArray* plhs[], 170 | * int nrhs, const mxArray* prhs[]) { 171 | * intptr_t session_id = mxGetScalar(prhs[0]); 172 | * Database* database = Session::get(session_id); 173 | * database->query(...); 174 | * } 175 | * 176 | * MEX_DEFINE(close) (int nlhs, mxArray* plhs[], 177 | * int nrhs, const mxArray* prhs[]) { 178 | * intptr_t session_id = mxGetScalar(prhs[0]); 179 | * Session::destroy(session_id); 180 | * } 181 | */ 182 | template 183 | class Session { 184 | public: 185 | typedef std::map > InstanceMap; 186 | 187 | /** Create an instance. 188 | */ 189 | static intptr_t create(T* instance) { 190 | InstanceMap* instances = getInstances(); 191 | intptr_t id = reinterpret_cast(instance); 192 | instances->insert(std::make_pair(id, std::shared_ptr(instance))); 193 | mexLock(); 194 | return id; 195 | } 196 | /** Destroy an instance. 197 | */ 198 | static void destroy(intptr_t id) { 199 | getInstances()->erase(id); 200 | mexUnlock(); 201 | } 202 | static void destroy(const mxArray* pointer) { 203 | destroy(getIntPointer(pointer)); 204 | } 205 | /** Retrieve an instance or throw if no instance is found. 206 | */ 207 | static T* get(intptr_t id) { 208 | InstanceMap* instances = getInstances(); 209 | typename InstanceMap::iterator instance = instances->find(id); 210 | if (instance == instances->end()) 211 | mexErrMsgIdAndTxt("mexplus:session:notFound", 212 | "Invalid id %d. Did you create?", 213 | id); 214 | return instance->second.get(); 215 | } 216 | static T* get(const mxArray* pointer) { 217 | return get(getIntPointer(pointer)); 218 | } 219 | /** Retrieve a const instance or throw if no instance is found. 220 | */ 221 | static const T& getConst(intptr_t id) { 222 | return *get(id); 223 | } 224 | static const T& getConst(const mxArray* pointer) { 225 | return getConst(getIntPointer(pointer)); 226 | } 227 | /** Check if the given id exists. 228 | */ 229 | static bool exist(intptr_t id) { 230 | InstanceMap* instances = getInstances(); 231 | typename InstanceMap::iterator instance = instances->find(id); 232 | return instance != instances->end(); 233 | } 234 | static bool exist(const mxArray* pointer) { 235 | return exist(getIntPointer(pointer)); 236 | } 237 | /** Clear all session instances. 238 | */ 239 | static void clear() { 240 | for (int i = 0; i < getInstances()->size(); ++i) 241 | mexUnlock(); 242 | getInstances()->clear(); 243 | } 244 | 245 | private: 246 | /** Constructor prohibited. 247 | */ 248 | Session() {} 249 | ~Session() {} 250 | /** Convert mxArray to intptr_t. 251 | */ 252 | static intptr_t getIntPointer(const mxArray* pointer) { 253 | if (mxIsEmpty(pointer)) 254 | mexErrMsgIdAndTxt("mexplus:session:invalidType", "Id is empty."); 255 | if (sizeof(intptr_t) == 8 && !mxIsInt64(pointer) && !mxIsUint64(pointer)) 256 | mexErrMsgIdAndTxt("mexplus:session:invalidType", 257 | "Invalid id type %s.", 258 | mxGetClassName(pointer)); 259 | if (sizeof(intptr_t) == 4 && !mxIsInt32(pointer) && !mxIsUint32(pointer)) 260 | mexErrMsgIdAndTxt("mexplus:session:invalidType", 261 | "Invalid id type %s.", 262 | mxGetClassName(pointer)); 263 | return *reinterpret_cast(mxGetData(pointer)); 264 | } 265 | /** Get static instance storage. 266 | */ 267 | static InstanceMap* getInstances() { 268 | static InstanceMap instances; 269 | return &instances; 270 | } 271 | }; 272 | 273 | } // namespace mexplus 274 | 275 | /** Define a MEX API function. Example: 276 | * 277 | * MEX_DEFINE(myfunc) (int nlhs, mxArray *plhs[], 278 | * int nrhs, const mxArray *prhs[]) { 279 | * if (nrhs != 1 || nlhs > 1) 280 | * mexErrMsgTxt("Wrong number of arguments."); 281 | * ... 282 | * } 283 | */ 284 | #define MEX_DEFINE(name) \ 285 | class Operation_##name : public mexplus::Operation { \ 286 | public: \ 287 | virtual void operator()(int nlhs, \ 288 | mxArray *plhs[], \ 289 | int nrhs, \ 290 | const mxArray *prhs[]); \ 291 | private: \ 292 | static const mexplus::OperationCreatorImpl creator_; \ 293 | }; \ 294 | const mexplus::OperationCreatorImpl \ 295 | Operation_##name::creator_(#name); \ 296 | void Operation_##name::operator() 297 | 298 | /** Insert a function dispatching code. Use once per MEX binary. 299 | */ 300 | #define MEX_DISPATCH \ 301 | void mexFunction(int nlhs, mxArray *plhs[], \ 302 | int nrhs, const mxArray *prhs[]) { \ 303 | if (nrhs < 1 || !mxIsChar(prhs[0])) \ 304 | mexErrMsgIdAndTxt("mexplus:dispatch:argumentError", \ 305 | "Invalid argument: missing operation."); \ 306 | std::string operation_name( \ 307 | mxGetChars(prhs[0]), \ 308 | mxGetChars(prhs[0]) + mxGetNumberOfElements(prhs[0])); \ 309 | std::auto_ptr operation( \ 310 | mexplus::OperationFactory::create(operation_name)); \ 311 | if (operation.get() == NULL) \ 312 | mexErrMsgIdAndTxt("mexplus:dispatch:argumentError", \ 313 | "Invalid operation: %s", operation_name.c_str()); \ 314 | (*operation)(nlhs, plhs, nrhs - 1, prhs + 1); \ 315 | } 316 | 317 | #endif // __MEXPLUS_DISPATCH_H__ 318 | -------------------------------------------------------------------------------- /include/mexplus/mxarray.h: -------------------------------------------------------------------------------- 1 | /** MxArray data conversion library. 2 | * 3 | * The library provides mexplus::MxArray class for data conversion between 4 | * mxArray* and C++ types. The static API's are the core of the high-level 5 | * conversions. 6 | * 7 | * int value = MxArray::to(prhs[0]); 8 | * string value = MxArray::to(prhs[0]); 9 | * vector value = MxArray::to >(prhs[0]); 10 | * 11 | * plhs[0] = MxArray::from(20); 12 | * plhs[0] = MxArray::from("text value."); 13 | * plhs[0] = MxArray::from(vector(20, 0)); 14 | * 15 | * Additionally, object API's are there to wrap around a complicated data 16 | * access. 17 | * 18 | * ### Read access 19 | * 20 | * MxArray cell(prhs[0]); // Assumes cell array in prhs[0]. 21 | * int x = cell.at(0); 22 | * vector y = cell.at >(1); 23 | * 24 | * MxArray numeric(prhs[0]); // Assumes numeric array in prhs[0]. 25 | * double x = numeric.at(0); 26 | * int y = numeric.at(1); 27 | * 28 | * ### Write access 29 | * 30 | * MxArray cell(MxArray::Cell(1, 3)); 31 | * cell.set(0, 12); 32 | * cell.set(1, "text value."); 33 | * cell.set(2, vector(4, 0)); 34 | * plhs[0] = cell.release(); 35 | * 36 | * MxArray numeric(MxArray::Numeric(2, 2)); 37 | * numeric.set(0, 0, 1); 38 | * numeric.set(0, 1, 2); 39 | * numeric.set(1, 0, 3); 40 | * numeric.set(1, 1, 4); 41 | * plhs[0] = numeric.release(); 42 | * 43 | * To add your own data conversion, define in namespace mexplus a template 44 | * specialization of MxArray::from() and MxArray::to(). 45 | * 46 | * Kota Yamaguchi 2014 47 | */ 48 | 49 | #ifndef __MEXPLUS_MXARRAY_H__ 50 | #define __MEXPLUS_MXARRAY_H__ 51 | 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | /** Macro definitions. 60 | */ 61 | #define MEXPLUS_CHECK_NOTNULL(pointer) \ 62 | if (!(pointer)) \ 63 | mexErrMsgIdAndTxt("mexplus:error", \ 64 | "Null pointer exception: %s:%d:%s `" #pointer "`.", \ 65 | __FILE__, \ 66 | __LINE__, \ 67 | __FUNCTION__) 68 | 69 | #define MEXPLUS_ERROR(...) mexErrMsgIdAndTxt("mexplus:error", __VA_ARGS__) 70 | #define MEXPLUS_WARNING(...) mexWarnMsgIdAndTxt("mexplus:warning", __VA_ARGS__) 71 | #define MEXPLUS_ASSERT(condition, ...) \ 72 | if (!(condition)) mexErrMsgIdAndTxt("mexplus:error", __VA_ARGS__) 73 | 74 | namespace mexplus { 75 | 76 | /** mxArray object wrapper for data conversion and manipulation. 77 | * 78 | * The class is similar to a combination of unique_ptr and wrapper around 79 | * Matlab's matrix API. An MxArray object created from a mutable mxArray* 80 | * pointer automatically frees its internal memory unless explicitly 81 | * released. When MxArray is created from a const mxArray*, the object does not 82 | * manage memory but still provides the same matrix API. 83 | */ 84 | class MxArray { 85 | public: 86 | /** Empty MxArray constructor. Use reset() to set a pointer. 87 | */ 88 | MxArray() : array_(NULL), owner_(false) {} 89 | /** NULL assignment. 90 | */ 91 | MxArray& operator= (std::nullptr_t) { 92 | reset(); 93 | return *this; 94 | } 95 | /** Move constructor. 96 | */ 97 | MxArray(MxArray&& array) : array_(NULL), owner_(false) { 98 | *this = std::move(array); 99 | } 100 | /** Move assignment. 101 | */ 102 | MxArray& operator= (MxArray&& rhs) { 103 | if (this != &rhs) { 104 | array_ = rhs.array_; 105 | owner_ = rhs.owner_; 106 | rhs.array_ = NULL; 107 | rhs.owner_ = false; 108 | } 109 | return *this; 110 | } 111 | /** MxArray constructor from mutable mxArray*. MxArray will manage memory. 112 | * @param array mxArray pointer. 113 | */ 114 | explicit MxArray(const mxArray* array) : 115 | array_(const_cast(array)), 116 | owner_(false) {} 117 | /** MxArray constructor from const mxArray*. MxArray will not manage memory. 118 | * @param array mxArray pointer given by mexFunction. 119 | */ 120 | explicit MxArray(mxArray* array) : array_(array), owner_(array) {} 121 | /** MxArray constructor from scalar. 122 | */ 123 | template 124 | explicit MxArray(const T& value) : array_(from(value)), owner_(true) {} 125 | /** Destructor. Unreleased pointers will be destroyed. 126 | */ 127 | virtual ~MxArray() { 128 | if (array_ && owner_) 129 | mxDestroyArray(array_); 130 | } 131 | /** Swap operation. 132 | */ 133 | void swap(MxArray& rhs) { 134 | if (this != &rhs) { 135 | mxArray* array = rhs.array_; 136 | bool owner = rhs.owner_; 137 | rhs.array_ = array_; 138 | rhs.owner_ = owner_; 139 | array_ = array; 140 | owner_ = owner; 141 | } 142 | } 143 | /** Reset an mxArray to a const mxArray*. 144 | * 145 | * Caller must be VERY careful with this, as the behavior is undefined when 146 | * the original mxArray* is destroyed. For example, the following will crash. 147 | * @code 148 | * MxArray foo; 149 | * { 150 | * MxArray bar(1); 151 | * foo.reset(bar.get()); 152 | * } 153 | * foo.toInt(); // Error! 154 | * @endcode 155 | */ 156 | void reset(const mxArray* array = NULL) { 157 | if (array_ && owner_) 158 | mxDestroyArray(array_); 159 | array_ = const_cast(array); 160 | owner_ = false; 161 | } 162 | /** Reset an mxArray. 163 | */ 164 | void reset(mxArray* array) { 165 | if (array_ && owner_) 166 | mxDestroyArray(array_); 167 | array_ = array; 168 | owner_ = array; 169 | } 170 | /** Release managed mxArray* pointer, or clone if not owner. 171 | * @return Unmanaged mxArray*. Always caller must destroy. 172 | */ 173 | mxArray* release() { 174 | MEXPLUS_CHECK_NOTNULL(array_); 175 | mxArray* array = (owner_) ? array_ : clone(); 176 | array_ = NULL; 177 | owner_ = false; 178 | return array; 179 | } 180 | /** Clone mxArray. This always allocates new mxArray*. 181 | * @return Unmanaged mxArray*. Always caller must destroy. 182 | */ 183 | mxArray* clone() const { 184 | MEXPLUS_CHECK_NOTNULL(array_); 185 | mxArray* array = mxDuplicateArray(array_); 186 | MEXPLUS_CHECK_NOTNULL(array); 187 | return array; 188 | } 189 | /** Conversion to const mxArray*. 190 | * @return const mxArray* pointer. 191 | */ 192 | inline const mxArray* get() const { return array_; } 193 | /** Return true if the array is not NULL. 194 | */ 195 | operator bool() const { return array_ != NULL; } 196 | /** Return true if owner. 197 | */ 198 | inline bool isOwner() const { return owner_; } 199 | /** Create a new numeric matrix. 200 | * @param rows Number of rows. 201 | * @param columns Number of cols. 202 | */ 203 | template 204 | static mxArray* Numeric(int rows = 1, int columns = 1); 205 | /** Create a new logical matrix. 206 | * @param rows Number of rows. 207 | * @param columns Number of cols. 208 | */ 209 | static mxArray* Logical(int rows = 1, int columns = 1) { 210 | mxArray* logical_array = mxCreateLogicalMatrix(rows, columns); 211 | MEXPLUS_CHECK_NOTNULL(logical_array); 212 | return logical_array; 213 | } 214 | /** Create a new cell matrix. 215 | * @param rows Number of rows. 216 | * @param columns Number of cols. 217 | * 218 | * Example: 219 | * @code 220 | * MxArray cell_array = MxArray::Cell(1, 2); 221 | * cell_array.set(0, 1); 222 | * cell_array.set(1, "another value"); 223 | * plhs[0] = cell_array.release(); 224 | * @endcode 225 | */ 226 | static mxArray* Cell(int rows = 1, int columns = 1) { 227 | mxArray* cell_array = mxCreateCellMatrix(rows, columns); 228 | MEXPLUS_CHECK_NOTNULL(cell_array); 229 | return cell_array; 230 | } 231 | /** Generic constructor for a struct matrix. 232 | * @param fields field names. 233 | * @param nfields number of field names. 234 | * @param rows size of the first dimension. 235 | * @param columns size of the second dimension. 236 | * 237 | * Example: 238 | * @code 239 | * const char* fields[] = {"field1", "field2"}; 240 | * MxArray struct_array(MxArray::Struct(2, fields)); 241 | * struct_array.set("field1", 1); 242 | * struct_array.set("field2", "field2 value"); 243 | * plhs[0] = struct_array.release(); 244 | * @endcode 245 | */ 246 | static mxArray* Struct(int nfields = 0, 247 | const char** fields = NULL, 248 | int rows = 1, 249 | int columns = 1) { 250 | mxArray* struct_array = mxCreateStructMatrix(rows, 251 | columns, 252 | nfields, 253 | fields); 254 | MEXPLUS_CHECK_NOTNULL(struct_array); 255 | return struct_array; 256 | } 257 | /** mxArray* importer methods. 258 | */ 259 | template 260 | static mxArray* from(const T& value) { return fromInternal(value); } 261 | static mxArray* from(const char* value) { 262 | mxArray* array = mxCreateString(value); 263 | MEXPLUS_CHECK_NOTNULL(array); 264 | return array; 265 | } 266 | static mxArray* from(int32_t value) { 267 | mxArray* array = mxCreateNumericMatrix(1, 1, mxINT32_CLASS, mxREAL); 268 | MEXPLUS_CHECK_NOTNULL(array); 269 | *reinterpret_cast(mxGetData(array)) = value; 270 | return array; 271 | } 272 | /** mxArray* exporter methods. 273 | */ 274 | template 275 | static void to(const mxArray* array, T* value) { 276 | toInternal(array, value); 277 | } 278 | template 279 | static T to(const mxArray* array) { 280 | T value; 281 | toInternal(array, &value); 282 | return value; 283 | } 284 | /** mxArray* element reader methods. 285 | */ 286 | template 287 | static T at(const mxArray* array, mwIndex index) { 288 | T value; 289 | atInternal(array, index, &value); 290 | return value; 291 | } 292 | template 293 | static void at(const mxArray* array, mwIndex index, T* value) { 294 | atInternal(array, index, value); 295 | } 296 | static const mxArray* at(const mxArray* array, mwIndex index) { 297 | MEXPLUS_CHECK_NOTNULL(array); 298 | MEXPLUS_ASSERT(mxIsCell(array), "Expected a cell array."); 299 | return mxGetCell(array, index); 300 | } 301 | template 302 | static void at(const mxArray* array, 303 | const std::string& field, 304 | T* value, 305 | mwIndex index = 0) { 306 | atInternal(array, field, index, value); 307 | } 308 | static const mxArray* at(const mxArray* array, 309 | const std::string& field, 310 | mwIndex index = 0) { 311 | MEXPLUS_CHECK_NOTNULL(array); 312 | MEXPLUS_ASSERT(mxIsStruct(array), "Expected a struct array."); 313 | return mxGetField(array, index, field.c_str()); 314 | } 315 | /** mxArray* element writer methods. 316 | */ 317 | template 318 | static void set(mxArray* array, mwIndex index, const T& value) { 319 | setInternal(array, index, value); 320 | } 321 | static void set(mxArray* array, mwIndex index, mxArray* value) { 322 | MEXPLUS_CHECK_NOTNULL(array); 323 | MEXPLUS_CHECK_NOTNULL(value); 324 | MEXPLUS_ASSERT(mxIsCell(array), "Expected a cell array."); 325 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 326 | "Index out of range: %u.", 327 | index); 328 | mxSetCell(array, index, value); 329 | } 330 | template 331 | static void set(mxArray* array, 332 | const std::string& field, 333 | const T& value, 334 | mwIndex index = 0) { 335 | setInternal(array, field, index, value); 336 | } 337 | static void set(mxArray* array, 338 | const std::string& field, 339 | mxArray* value, 340 | mwIndex index = 0) { 341 | MEXPLUS_CHECK_NOTNULL(array); 342 | MEXPLUS_CHECK_NOTNULL(value); 343 | MEXPLUS_ASSERT(mxIsStruct(array), "Expected a struct array."); 344 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 345 | "Index out of range: %u.", 346 | index); 347 | if (!mxGetField(array, index, field.c_str())) 348 | MEXPLUS_ASSERT(mxAddField(array, field.c_str()) >= 0, 349 | "Failed to create a field '%s'", 350 | field.c_str()); 351 | mxSetField(array, index, field.c_str(), value); 352 | } 353 | 354 | /** Convert MxArray to a specified type. 355 | */ 356 | template 357 | T to() const { 358 | T value; 359 | toInternal(array_, &value); 360 | return value; 361 | } 362 | template 363 | void to(T* value) const { toInternal(array_, value); } 364 | /** Template for element accessor. 365 | * @param index index of the array element. 366 | * @return value of the element at index. 367 | * 368 | * 369 | * Example: 370 | * @code 371 | * MxArray array(prhs[0]); 372 | * double value = array.at(0); 373 | * @endcode 374 | */ 375 | template 376 | T at(mwIndex index) const { 377 | T value; 378 | atInternal(array_, index, &value); 379 | return value; 380 | } 381 | template 382 | void at(mwIndex index, T* value) const { 383 | atInternal(array_, index, value); 384 | } 385 | const mxArray* at(mwIndex index) const { 386 | return at(array_, index); 387 | } 388 | /** Template for element accessor. 389 | * @param row index of the first dimension. 390 | * @param column index of the second dimension. 391 | * @return value of the element at (row, column). 392 | */ 393 | template 394 | T at(mwIndex row, mwIndex column) const; 395 | /** Template for element accessor. 396 | * @param si subscript index of the element. 397 | * @return value of the element at subscript index. 398 | */ 399 | template 400 | T at(const std::vector& subscripts) const; 401 | /** Struct element accessor. 402 | * @param field field name of the struct array. 403 | * @param index index of the struct array. 404 | * @return value of the element at the specified field. 405 | */ 406 | template 407 | T at(const std::string& field, mwIndex index = 0) const { 408 | T value; 409 | atInternal(array_, field, index, &value); 410 | return value; 411 | } 412 | template 413 | void at(const std::string& field, T* value, mwIndex index = 0) const { 414 | atInternal(array_, field, index, value); 415 | } 416 | const mxArray* at(const std::string& field, mwIndex index = 0) const { 417 | return at(array_, field, index); 418 | } 419 | 420 | /** Template for element write accessor. 421 | * @param index offset of the array element. 422 | * @param value value of the field. 423 | */ 424 | template 425 | void set(mwIndex index, const T& value) { 426 | setInternal(array_, index, value); 427 | } 428 | /** Template for element write accessor. 429 | * @param row index of the first dimension of the array element. 430 | * @param column index of the first dimension of the array element. 431 | * @param value value of the field. 432 | */ 433 | template 434 | void set(mwIndex row, mwIndex column, const T& value); 435 | /** Template for element write accessor. 436 | * @param subscripts subscript index of the element. 437 | * @param value value of the field. 438 | */ 439 | template 440 | void set(const std::vector& subscripts, const T& value); 441 | /** Cell element write accessor. 442 | * @param index index of the element. 443 | * @param value cell element to be inserted. 444 | */ 445 | void set(mwIndex index, mxArray* value) { 446 | MEXPLUS_ASSERT(isOwner(), "Must be an owner to set."); 447 | set(array_, index, value); 448 | } 449 | /** Struct element write accessor. 450 | * @param field field name of the struct array. 451 | * @param value value of the field. 452 | * @param index linear index of the struct array element. 453 | */ 454 | template 455 | void set(const std::string& field, const T& value, mwIndex index = 0) { 456 | MEXPLUS_ASSERT(isOwner(), "Must be an owner to set."); 457 | setInternal(array_, field, index, value); 458 | } 459 | /** Struct element write accessor. 460 | * @param field field name of the struct array. 461 | * @param value value of the field to be inserted. 462 | * @param index linear index of the struct array element. 463 | */ 464 | void set(const std::string& field, mxArray* value, mwIndex index = 0) { 465 | MEXPLUS_ASSERT(isOwner(), "Must be an owner to set."); 466 | set(array_, field, value, index); 467 | } 468 | /** Get raw data pointer. 469 | * @return pointer T*. If MxArray is not compatible, return NULL. 470 | */ 471 | template 472 | T* getData() const; 473 | mxLogical* getLogicals() const { 474 | MEXPLUS_CHECK_NOTNULL(array_); 475 | MEXPLUS_ASSERT(isLogical(), 476 | "Expected a logical array but %s.", 477 | className().c_str()); 478 | return mxGetLogicals(array_); 479 | } 480 | mxChar* getChars() const { 481 | MEXPLUS_CHECK_NOTNULL(array_); 482 | MEXPLUS_ASSERT(isChar(), 483 | "Expected a char array but %s.", 484 | className().c_str()); 485 | return mxGetChars(array_); 486 | } 487 | /** Class ID of mxArray. 488 | */ 489 | inline mxClassID classID() const { return mxGetClassID(array_); } 490 | /** Class name of mxArray. 491 | */ 492 | inline const std::string className() const { 493 | return std::string(mxGetClassName(array_)); 494 | } 495 | /** Number of elements in an array. 496 | */ 497 | inline mwSize size() const { return mxGetNumberOfElements(array_); } 498 | /** Number of dimensions. 499 | */ 500 | inline mwSize dimensionSize() const { 501 | return mxGetNumberOfDimensions(array_); 502 | } 503 | /** Array of each dimension. 504 | */ 505 | inline std::vector dimensions() const { 506 | const mwSize* dimensions = mxGetDimensions(array_); 507 | return std::vector(dimensions, dimensions + dimensionSize()); 508 | } 509 | /** Number of rows in an array. 510 | */ 511 | inline mwSize rows() const { return mxGetM(array_); } 512 | /** Number of columns in an array. 513 | */ 514 | inline mwSize cols() const { return mxGetN(array_); } 515 | /** Number of fields in a struct array. 516 | */ 517 | inline int fieldSize() const { return mxGetNumberOfFields(array_); } 518 | /** Get field name of a struct array. 519 | * @param index index of the struct array. 520 | * @return std::string. 521 | */ 522 | std::string fieldName(int index) const { 523 | const char* field = mxGetFieldNameByNumber(array_, index); 524 | MEXPLUS_ASSERT(field, "Failed to get field name at %d.", index); 525 | return std::string(field); 526 | } 527 | /** Get field names of a struct array. 528 | * @params field_nams std::vector of struct field names. 529 | */ 530 | std::vector fieldNames() const { 531 | MEXPLUS_ASSERT(isStruct(), "Expected a struct array."); 532 | std::vector fields(fieldSize()); 533 | for (int i = 0; i < fields.size(); ++i) 534 | fields[i] = fieldName(i); 535 | return fields; 536 | } 537 | /** Number of elements in IR, PR, and PI arrays. 538 | */ 539 | inline mwSize nonZeroMax() const { return mxGetNzmax(array_); } 540 | /** Offset from first element to desired element. 541 | * @param row index of the first dimension of the array. 542 | * @param column index of the second dimension of the array. 543 | * @return linear offset of the specified subscript index. 544 | */ 545 | mwIndex subscriptIndex(mwIndex row, mwIndex column) const { 546 | MEXPLUS_ASSERT(row < rows() && column < cols(), 547 | "Subscript is out of range."); 548 | mwIndex subscripts[] = {row, column}; 549 | return mxCalcSingleSubscript(array_, 2, subscripts); 550 | } 551 | /** Offset from first element to desired element. 552 | * @param si subscript index of the array. 553 | * @return linear offset of the specified subscript index. 554 | */ 555 | mwIndex subscriptIndex(const std::vector& subscripts) const { 556 | return mxCalcSingleSubscript(array_, subscripts.size(), &subscripts[0]); 557 | } 558 | /** Determine whether input is cell array. 559 | */ 560 | inline bool isCell() const { return mxIsCell(array_); } 561 | /** Determine whether input is string array. 562 | */ 563 | inline bool isChar() const { return mxIsChar(array_); } 564 | /** Determine whether array is member of specified class. 565 | */ 566 | inline bool isClass(const char* name) const { 567 | return mxIsClass(array_, name); 568 | } 569 | /** Determine whether data is complex. 570 | */ 571 | inline bool isComplex() const { return mxIsComplex(array_); } 572 | /** Determine whether mxArray represents data as double-precision, 573 | * floating-point numbers. 574 | */ 575 | inline bool isDouble() const { return mxIsDouble(array_); } 576 | /** Determine whether array is empty. 577 | */ 578 | inline bool isEmpty() const { return mxIsEmpty(array_); } 579 | /** Determine whether input is finite. 580 | */ 581 | static inline bool IsFinite(double value) { return mxIsFinite(value); } 582 | /** Determine whether array was copied from MATLAB global workspace. 583 | */ 584 | inline bool isFromGlobalWS() const { return mxIsFromGlobalWS(array_); }; 585 | /** Determine whether input is infinite. 586 | */ 587 | static inline bool IsInf(double value) { return mxIsInf(value); } 588 | /** Determine whether array represents data as signed 8-bit integers. 589 | */ 590 | inline bool isInt8() const { return mxIsInt8(array_); } 591 | /** Determine whether array represents data as signed 16-bit integers. 592 | */ 593 | inline bool isInt16() const { return mxIsInt16(array_); } 594 | /** Determine whether array represents data as signed 32-bit integers. 595 | */ 596 | inline bool isInt32() const { return mxIsInt32(array_); } 597 | /** Determine whether array represents data as signed 64-bit integers. 598 | */ 599 | inline bool isInt64() const { return mxIsInt64(array_); } 600 | /** Determine whether array is of type mxLogical. 601 | */ 602 | inline bool isLogical() const { return mxIsLogical(array_); } 603 | /** Determine whether scalar array is of type mxLogical. 604 | */ 605 | inline bool isLogicalScalar() const { return mxIsLogicalScalar(array_); } 606 | /** Determine whether scalar array of type mxLogical is true. 607 | */ 608 | inline bool isLogicalScalarTrue() const { 609 | return mxIsLogicalScalarTrue(array_); 610 | } 611 | /** Determine whether array is numeric. 612 | */ 613 | inline bool isNumeric() const { return mxIsNumeric(array_); } 614 | /** Determine whether array represents data as single-precision, 615 | * floating-point numbers. 616 | */ 617 | inline bool isSingle() const { return mxIsSingle(array_); } 618 | /** Determine whether input is sparse array. 619 | */ 620 | inline bool isSparse() const { return mxIsSparse(array_); } 621 | /** Determine whether input is structure array. 622 | */ 623 | inline bool isStruct() const { return mxIsStruct(array_); } 624 | /** Determine whether array represents data as unsigned 8-bit integers. 625 | */ 626 | inline bool isUint8() const { return mxIsUint8(array_); } 627 | /** Determine whether array represents data as unsigned 16-bit integers. 628 | */ 629 | inline bool isUint16() const { return mxIsUint16(array_); } 630 | /** Determine whether array represents data as unsigned 32-bit integers. 631 | */ 632 | inline bool isUint32() const { return mxIsUint32(array_); } 633 | /** Determine whether array represents data as unsigned 64-bit integers. 634 | */ 635 | inline bool isUint64() const { return mxIsUint64(array_); } 636 | /** Determine whether a struct array has a specified field. 637 | */ 638 | bool hasField(const std::string& field_name, mwIndex index = 0) const { 639 | return isStruct() && 640 | mxGetField(array_, index, field_name.c_str()) != NULL; 641 | } 642 | /** Determine whether input is NaN (Not-a-Number). 643 | */ 644 | static inline bool IsNaN(double value) { return mxIsNaN(value); } 645 | /** Value of infinity. 646 | */ 647 | static inline double Inf() { return mxGetInf(); } 648 | /** Value of NaN (Not-a-Number). 649 | */ 650 | static inline double NaN() { return mxGetNaN(); } 651 | /** Value of EPS. 652 | */ 653 | static inline double Eps() { return mxGetEps(); } 654 | 655 | private: 656 | /** Copy constructor is prohibited except internally. 657 | */ 658 | MxArray(const MxArray& array); 659 | //MxArray(const MxArray& array) = delete; 660 | /** Copy assignment operator is prohibited. 661 | */ 662 | MxArray& operator=(const MxArray& rhs); 663 | //MxArray& operator=(const MxArray& rhs) = delete; 664 | /** Templated mxArray importers. 665 | */ 666 | template 667 | static mxArray* fromInternal(const typename std::enable_if< 668 | std::is_same::array_type, mxNumeric>::value, 669 | T>::type& value); 670 | template 671 | static mxArray* fromInternal(const typename std::enable_if< 672 | std::is_same::array_type, mxNumeric>::value && 674 | std::is_compound::value, 675 | Container>::type& value); 676 | template 677 | static mxArray* fromInternal(const typename std::enable_if< 678 | std::is_same::array_type, mxChar>::value, 679 | T>::type& value); 680 | template 681 | static mxArray* fromInternal(const typename std::enable_if< 682 | std::is_same::array_type, mxChar>::value && 684 | std::is_compound::value && 685 | std::is_signed::value, 686 | Container>::type& value); 687 | template 688 | static mxArray* fromInternal(const typename std::enable_if< 689 | std::is_same::array_type, mxChar>::value && 691 | std::is_compound::value && 692 | !std::is_signed::value, 693 | Container>::type& value); 694 | template 695 | static mxArray* fromInternal(const typename std::enable_if< 696 | std::is_same::array_type, mxLogical>::value, 697 | T>::type& value); 698 | template 699 | static mxArray* fromInternal(const typename std::enable_if< 700 | std::is_same::array_type, mxLogical>::value && 702 | std::is_compound::value, 703 | Container>::type& value); 704 | template 705 | static mxArray* fromInternal(const typename std::enable_if< 706 | std::is_same::array_type, mxCell>::value && 708 | std::is_compound::value, 709 | Container>::type& value); 710 | /** Templated mxArray exporters. 711 | */ 712 | template 713 | static void toInternal(const mxArray* array, typename std::enable_if< 714 | std::is_arithmetic::value, T>::type* value) { 715 | atInternal(array, 0, value); 716 | } 717 | template 718 | static void toInternal(const mxArray* array, typename std::enable_if< 719 | std::is_compound::value && 720 | std::is_arithmetic::value, 721 | T>::type* value); 722 | template 723 | static void toInternal(const mxArray* array, typename std::enable_if< 724 | std::is_compound::value && 725 | !std::is_arithmetic::value, 726 | T>::type* value); 727 | template 728 | static void atInternal(const mxArray* array, 729 | mwIndex index, 730 | typename std::enable_if< 731 | std::is_arithmetic::value, T>::type* value); 732 | template 733 | static void atInternal(const mxArray* array, 734 | mwIndex index, 735 | typename std::enable_if< 736 | std::is_compound::value, 737 | T>::type* value); 738 | template 739 | static void atInternal(const mxArray* array, 740 | const std::string& field, 741 | mwIndex index, 742 | T* value); 743 | template 744 | static void setInternal(mxArray* array, 745 | mwIndex index, 746 | const typename std::enable_if< 747 | std::is_arithmetic::value, 748 | T>::type& value); 749 | template 750 | static void setInternal(mxArray* array, 751 | mwIndex index, 752 | const typename std::enable_if< 753 | std::is_compound::value, 754 | T>::type& value); 755 | template 756 | static void setInternal(mxArray* array, 757 | const std::string& field, 758 | mwIndex index, 759 | const T& value); 760 | template 761 | static void assignTo(const mxArray* array, mwIndex index, R* value) { 762 | *value = *(reinterpret_cast(mxGetData(array)) + index); 763 | } 764 | template 765 | static void assignCharTo(const mxArray* array, 766 | mwIndex index, 767 | typename std::enable_if< 768 | std::is_signed::value, 769 | R>::type* value) { 770 | typedef typename std::make_signed::type SignedMxChar; 771 | *value = *(reinterpret_cast(mxGetChars(array)) + index); 772 | } 773 | template 774 | static void assignCharTo(const mxArray* array, 775 | mwIndex index, 776 | typename std::enable_if< 777 | !std::is_signed::value, 778 | R>::type* value) { 779 | *value = *(mxGetChars(array) + index); 780 | } 781 | template 782 | static void assignTo(const mxArray* array, R* value) { 783 | T* data_pointer = reinterpret_cast(mxGetData(array)); 784 | value->assign(data_pointer, data_pointer + mxGetNumberOfElements(array)); 785 | } 786 | template 787 | static void assignStringTo(const mxArray* array, typename std::enable_if< 788 | std::is_signed::value, 789 | R>::type* value) { 790 | typedef typename std::make_signed::type SignedMxChar; 791 | SignedMxChar* data_pointer = reinterpret_cast( 792 | mxGetChars(array)); 793 | value->assign(data_pointer, data_pointer + mxGetNumberOfElements(array)); 794 | } 795 | template 796 | static void assignStringTo(const mxArray* array, typename std::enable_if< 797 | !std::is_signed::value, 798 | R>::type* value) { 799 | mxChar* data_pointer = mxGetChars(array); 800 | value->assign(data_pointer, data_pointer + mxGetNumberOfElements(array)); 801 | } 802 | template 803 | static void assignCellTo(const mxArray* array, mwIndex index, T* value) { 804 | const mxArray* element = mxGetCell(array, index); 805 | MEXPLUS_CHECK_NOTNULL(element); 806 | toInternal(element, value); 807 | } 808 | template 809 | static void assignCellTo(const mxArray* array, T* value) { 810 | for (int i = 0; i < mxGetNumberOfElements(array); ++i) { 811 | const mxArray* element = mxGetCell(array, i); 812 | MEXPLUS_CHECK_NOTNULL(element); 813 | value->push_back(to(element)); 814 | } 815 | } 816 | template 817 | static void assignFrom(mxArray* array, mwIndex index, const T& value) { 818 | *(reinterpret_cast(mxGetData(array)) + index) = value; 819 | } 820 | template 821 | static void assignCharFrom(mxArray* array, 822 | mwIndex index, 823 | const typename std::enable_if< 824 | std::is_integral::value && std::is_signed::value, 825 | T>::type& value) { 826 | *(mxGetChars(array) + index) = reinterpret_cast::type&>(value); 828 | } 829 | template 830 | static void assignCharFrom(mxArray* array, 831 | mwIndex index, 832 | const typename std::enable_if< 833 | !std::is_integral::value || !std::is_signed::value, 834 | T>::type& value) { 835 | *(mxGetChars(array) + index) = value; 836 | } 837 | 838 | /** Pointer to the mxArray C object. 839 | */ 840 | mxArray* array_; 841 | /** Flag to enable resource management. 842 | */ 843 | bool owner_; 844 | }; 845 | 846 | template 847 | mxArray* MxArray::fromInternal(const typename std::enable_if< 848 | std::is_same::array_type, mxNumeric>::value, 849 | T>::type& value) { 850 | mxArray* array = mxCreateNumericMatrix(1, 851 | 1, 852 | MxTypes::class_id, 853 | MxTypes::complexity); 854 | MEXPLUS_CHECK_NOTNULL(array); 855 | *reinterpret_cast(mxGetData(array)) = value; 856 | return array; 857 | } 858 | 859 | template 860 | mxArray* MxArray::fromInternal(const typename std::enable_if< 861 | std::is_same::array_type, 862 | mxNumeric>::value && 863 | std::is_compound::value, 864 | Container>::type& value) { 865 | typedef typename Container::value_type ValueType; 866 | mxArray* array = mxCreateNumericMatrix(1, 867 | value.size(), 868 | MxTypes::class_id, 869 | MxTypes::complexity); 870 | MEXPLUS_CHECK_NOTNULL(array); 871 | std::copy(value.begin(), 872 | value.end(), 873 | reinterpret_cast(mxGetData(array))); 874 | return array; 875 | } 876 | 877 | template 878 | mxArray* MxArray::fromInternal(const typename std::enable_if< 879 | std::is_same::array_type, mxChar>::value, 880 | T>::type& value) { 881 | const char char_array[] = {static_cast(value), 0}; 882 | mxArray* array = mxCreateString(char_array); 883 | MEXPLUS_CHECK_NOTNULL(array); 884 | return array; 885 | } 886 | 887 | template 888 | mxArray* MxArray::fromInternal(const typename std::enable_if< 889 | std::is_same::array_type, 890 | mxChar>::value && 891 | std::is_compound::value && 892 | std::is_signed::value, 893 | Container>::type& value) { 894 | typedef typename std::make_unsigned::type 895 | UnsignedValue; 896 | const mwSize dimensions[] = {1, static_cast(value.size())}; 897 | mxArray* array = mxCreateCharArray(2, dimensions); 898 | MEXPLUS_CHECK_NOTNULL(array); 899 | mxChar* array_data = mxGetChars(array); 900 | for (typename Container::const_iterator it = value.begin(); 901 | it != value.end(); 902 | ++it) { 903 | *(array_data++) = reinterpret_cast(*it); 904 | } 905 | return array; 906 | } 907 | 908 | template 909 | mxArray* MxArray::fromInternal(const typename std::enable_if< 910 | std::is_same::array_type, 911 | mxChar>::value && 912 | std::is_compound::value && 913 | !std::is_signed::value, 914 | Container>::type& value) { 915 | const mwSize dimensions[] = {1, static_cast(value.size())}; 916 | mxArray* array = mxCreateCharArray(2, dimensions); 917 | MEXPLUS_CHECK_NOTNULL(array); 918 | std::copy(value.begin(), value.end(), mxGetChars(array)); 919 | return array; 920 | } 921 | 922 | template 923 | mxArray* MxArray::fromInternal(const typename std::enable_if< 924 | std::is_same::array_type, mxLogical>::value, 925 | T>::type& value) { 926 | mxArray* array = mxCreateLogicalScalar(value); 927 | MEXPLUS_CHECK_NOTNULL(array); 928 | return array; 929 | } 930 | 931 | template 932 | mxArray* MxArray::fromInternal(const typename std::enable_if< 933 | std::is_same::array_type, 934 | mxLogical>::value && 935 | std::is_compound::value, 936 | Container>::type& value) { 937 | mxArray* array = mxCreateLogicalMatrix(1, value.size()); 938 | MEXPLUS_CHECK_NOTNULL(array); 939 | std::copy(value.begin(), value.end(), mxGetLogicals(array)); 940 | return array; 941 | } 942 | 943 | template 944 | mxArray* MxArray::fromInternal(const typename std::enable_if< 945 | std::is_same::array_type, 946 | mxCell>::value && 947 | std::is_compound::value, 948 | Container>::type& value) { 949 | mxArray* array = mxCreateCellMatrix(1, value.size()); 950 | MEXPLUS_CHECK_NOTNULL(array); 951 | mwIndex index = 0; 952 | for (typename Container::const_iterator it = value.begin(); 953 | it != value.end(); 954 | ++it) 955 | mxSetCell(array, index++, from(*it)); 956 | return array; 957 | } 958 | 959 | template 960 | void MxArray::toInternal(const mxArray* array, typename std::enable_if< 961 | std::is_compound::value && 962 | std::is_arithmetic::value, 963 | T>::type* value) { 964 | MEXPLUS_CHECK_NOTNULL(array); 965 | MEXPLUS_CHECK_NOTNULL(value); 966 | switch (mxGetClassID(array)) { 967 | case mxINT8_CLASS: assignTo(array, value); break; 968 | case mxUINT8_CLASS: assignTo(array, value); break; 969 | case mxINT16_CLASS: assignTo(array, value); break; 970 | case mxUINT16_CLASS: assignTo(array, value); break; 971 | case mxINT32_CLASS: assignTo(array, value); break; 972 | case mxUINT32_CLASS: assignTo(array, value); break; 973 | case mxINT64_CLASS: assignTo(array, value); break; 974 | case mxUINT64_CLASS: assignTo(array, value); break; 975 | case mxSINGLE_CLASS: assignTo(array, value); break; 976 | case mxDOUBLE_CLASS: assignTo(array, value); break; 977 | case mxLOGICAL_CLASS: assignTo(array, value); break; 978 | case mxCHAR_CLASS: assignStringTo(array, value); break; 979 | case mxCELL_CLASS: assignCellTo(array, value); break; 980 | // case mxSPARSE_CLASS: 981 | default: 982 | MEXPLUS_ERROR("Cannot convert %s.", mxGetClassName(array)); 983 | } 984 | } 985 | 986 | template 987 | void MxArray::toInternal(const mxArray* array, typename std::enable_if< 988 | std::is_compound::value && 989 | !std::is_arithmetic::value, 990 | T>::type* value) { 991 | MEXPLUS_CHECK_NOTNULL(value); 992 | MEXPLUS_ASSERT(mxIsCell(array), "Expected a cell array."); 993 | for (int i = 0; i < mxGetNumberOfElements(array); ++i) { 994 | const mxArray* element = mxGetCell(array, i); 995 | value->push_back(to(element)); 996 | } 997 | } 998 | 999 | template 1000 | void MxArray::atInternal(const mxArray* array, 1001 | mwIndex index, 1002 | typename std::enable_if< 1003 | std::is_arithmetic::value, T>::type* value) { 1004 | MEXPLUS_CHECK_NOTNULL(array); 1005 | MEXPLUS_CHECK_NOTNULL(value); 1006 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1007 | "Index out of range: %u.", 1008 | index); 1009 | switch (mxGetClassID(array)) { 1010 | case mxINT8_CLASS: assignTo(array, index, value); break; 1011 | case mxUINT8_CLASS: assignTo(array, index, value); break; 1012 | case mxINT16_CLASS: assignTo(array, index, value); break; 1013 | case mxUINT16_CLASS: assignTo(array, index, value); break; 1014 | case mxINT32_CLASS: assignTo(array, index, value); break; 1015 | case mxUINT32_CLASS: assignTo(array, index, value); break; 1016 | case mxINT64_CLASS: assignTo(array, index, value); break; 1017 | case mxUINT64_CLASS: assignTo(array, index, value); break; 1018 | case mxSINGLE_CLASS: assignTo(array, index, value); break; 1019 | case mxDOUBLE_CLASS: assignTo(array, index, value); break; 1020 | case mxLOGICAL_CLASS: assignTo(array, index, value); break; 1021 | case mxCHAR_CLASS: assignCharTo(array, index, value); break; 1022 | case mxCELL_CLASS: assignCellTo(array, index, value); break; 1023 | // case mxSPARSE_CLASS: 1024 | default: 1025 | MEXPLUS_ASSERT(true, "Cannot convert %s", mxGetClassName(array)); 1026 | } 1027 | } 1028 | 1029 | template 1030 | void MxArray::atInternal(const mxArray* array, 1031 | mwIndex index, 1032 | typename std::enable_if< 1033 | std::is_compound::value, T>::type* value) { 1034 | MEXPLUS_CHECK_NOTNULL(array); 1035 | MEXPLUS_CHECK_NOTNULL(value); 1036 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1037 | "Index out of range: %u.", 1038 | index); 1039 | MEXPLUS_ASSERT(mxIsCell(array), "Expected a cell array."); 1040 | const mxArray* element = mxGetCell(array, index); 1041 | toInternal(element, value); 1042 | } 1043 | 1044 | template 1045 | void MxArray::atInternal(const mxArray* array, 1046 | const std::string& field, 1047 | mwIndex index, 1048 | T* value) { 1049 | MEXPLUS_CHECK_NOTNULL(array); 1050 | MEXPLUS_CHECK_NOTNULL(value); 1051 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1052 | "Index out of range: %u.", 1053 | index); 1054 | MEXPLUS_ASSERT(mxIsStruct(array), "Expected a struct array."); 1055 | const mxArray* element = mxGetField(array, index, field.c_str()); 1056 | MEXPLUS_ASSERT(element, "Invalid field name %s.", field.c_str()); 1057 | toInternal(element, value); 1058 | } 1059 | 1060 | template 1061 | void MxArray::setInternal(mxArray* array, 1062 | mwIndex index, 1063 | const typename std::enable_if< 1064 | std::is_arithmetic::value, 1065 | T>::type& value) { 1066 | MEXPLUS_CHECK_NOTNULL(array); 1067 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1068 | "Index out of range: %u.", 1069 | index); 1070 | switch (mxGetClassID(array)) { 1071 | case mxINT8_CLASS: assignFrom(array, index, value); break; 1072 | case mxUINT8_CLASS: assignFrom(array, index, value); break; 1073 | case mxINT16_CLASS: assignFrom(array, index, value); break; 1074 | case mxUINT16_CLASS: assignFrom(array, index, value); break; 1075 | case mxINT32_CLASS: assignFrom(array, index, value); break; 1076 | case mxUINT32_CLASS: assignFrom(array, index, value); break; 1077 | case mxINT64_CLASS: assignFrom(array, index, value); break; 1078 | case mxUINT64_CLASS: assignFrom(array, index, value); break; 1079 | case mxSINGLE_CLASS: assignFrom(array, index, value); break; 1080 | case mxDOUBLE_CLASS: assignFrom(array, index, value); break; 1081 | case mxCHAR_CLASS: assignCharFrom(array, index, value); break; 1082 | case mxLOGICAL_CLASS: assignFrom(array, index, value); break; 1083 | case mxCELL_CLASS: mxSetCell(array, index, from(value)); break; 1084 | default: 1085 | MEXPLUS_ERROR("Cannot assign to %s array.", mxGetClassName(array)); 1086 | } 1087 | } 1088 | 1089 | template 1090 | void MxArray::setInternal(mxArray* array, 1091 | mwIndex index, 1092 | const typename std::enable_if< 1093 | std::is_compound::value, 1094 | T>::type& value) { 1095 | MEXPLUS_CHECK_NOTNULL(array); 1096 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1097 | "Index out of range: %u.", 1098 | index); 1099 | MEXPLUS_ASSERT(mxIsCell(array), "Expected a cell array."); 1100 | mxSetCell(array, index, from(value)); 1101 | } 1102 | 1103 | template 1104 | void MxArray::setInternal(mxArray* array, 1105 | const std::string& field, 1106 | mwIndex index, 1107 | const T& value) { 1108 | MEXPLUS_CHECK_NOTNULL(array); 1109 | MEXPLUS_ASSERT(index < mxGetNumberOfElements(array), 1110 | "Index out of range: %u.", 1111 | index); 1112 | MEXPLUS_ASSERT(mxIsStruct(array), "Expected a struct array."); 1113 | if (!mxGetField(array, index, field.c_str())) 1114 | MEXPLUS_ASSERT(mxAddField(array, field.c_str()) >= 0, 1115 | "Failed to create a field '%s'", 1116 | field.c_str()); 1117 | mxSetField(array, index, field.c_str(), from(value)); 1118 | } 1119 | 1120 | template 1121 | mxArray* MxArray::Numeric(int rows, int columns) { 1122 | typedef typename std::enable_if< 1123 | std::is_same::array_type, mxNumeric>::value, 1124 | T>::type Scalar; 1125 | mxArray* numeric = mxCreateNumericMatrix(rows, 1126 | columns, 1127 | MxTypes::class_id, 1128 | MxTypes::complexity); 1129 | MEXPLUS_CHECK_NOTNULL(numeric); 1130 | return numeric; 1131 | } 1132 | 1133 | template 1134 | T* MxArray::getData() const { 1135 | MEXPLUS_CHECK_NOTNULL(array_); 1136 | MEXPLUS_ASSERT(MxTypes::class_id == classID(), 1137 | "Expected a %s array.", 1138 | typeid(T).name()); 1139 | return reinterpret_cast(mxGetData(array_)); 1140 | } 1141 | 1142 | template 1143 | T MxArray::at(mwIndex row, mwIndex column) const { 1144 | return at(subscriptIndex(row, column)); 1145 | } 1146 | 1147 | template 1148 | T MxArray::at(const std::vector& subscripts) const { 1149 | return at(subscriptIndex(subscripts)); 1150 | } 1151 | 1152 | template 1153 | void MxArray::set(mwIndex row, mwIndex column, const T& value) { 1154 | set(subscriptIndex(row, column), value); 1155 | } 1156 | 1157 | template 1158 | void MxArray::set(const std::vector& subscripts, const T& value) { 1159 | set(subscriptIndex(subscripts), value); 1160 | } 1161 | 1162 | } // namespace mexplus 1163 | 1164 | #endif // __MEXPLUS_MXARRAY_H__ 1165 | -------------------------------------------------------------------------------- /include/mexplus/mxtypes.h: -------------------------------------------------------------------------------- 1 | /** MxTypes and other type traits for template. 2 | * 3 | * Kota Yamaguchi 2014 4 | */ 5 | 6 | #ifndef __MEXPLUS_MXTYPES_H__ 7 | #define __MEXPLUS_MXTYPES_H__ 8 | 9 | #include 10 | #include 11 | 12 | namespace mexplus { 13 | 14 | /** Traits for mxLogical-convertibles. 15 | */ 16 | template 17 | struct MxLogicalType : std::false_type {}; 18 | 19 | template 20 | struct MxLogicalType::type, bool>::value || 22 | std::is_same::type, mxLogical>::value, 23 | T>::type> : 24 | std::true_type {}; 25 | 26 | /** Traits for mxChar-convertibles. 27 | * 28 | * Treat them as an integer types when they are specified signed or unsigned, 29 | * because uint8_t is exactly unsigned char and there is no way to tell them 30 | * apart. 31 | */ 32 | template 33 | struct MxCharType : std::false_type {}; 34 | 35 | template 36 | struct MxCharType::type, char>::value || 38 | // Visual Studio cannot distinguish these from uint. 39 | //std::is_same::type, char16_t>::value || 40 | //std::is_same::type, char32_t>::value || 41 | std::is_same::type, mxChar>::value || 42 | std::is_same::type, wchar_t>::value, 43 | T>::type> : 44 | std::true_type {}; 45 | 46 | /** Traits for integer numerics. 47 | */ 48 | template 49 | struct MxIntType : std::false_type {}; 50 | template 51 | struct MxIntType::value && 53 | !MxLogicalType::value && 54 | !MxCharType::value, 55 | T>::type> : 56 | std::true_type {}; 57 | 58 | typedef struct mxCell_tag {} mxCell; 59 | 60 | typedef struct mxNumeric_tag {} mxNumeric; 61 | 62 | /** Traits for mxArray. 63 | */ 64 | template 65 | struct MxTypes { 66 | typedef T type; 67 | typedef mxCell array_type; 68 | static const mxClassID class_id = mxUNKNOWN_CLASS; 69 | static const mxComplexity complexity = mxREAL; 70 | }; 71 | 72 | template 73 | struct MxTypes::value, T>::type> { 74 | typedef T type; 75 | typedef mxChar array_type; 76 | static const mxClassID class_id = mxCHAR_CLASS; 77 | static const mxComplexity complexity = mxREAL; 78 | }; 79 | 80 | template 81 | struct MxTypes::value, T>::type> { 82 | typedef T type; 83 | typedef mxLogical array_type; 84 | static const mxClassID class_id = mxLOGICAL_CLASS; 85 | static const mxComplexity complexity = mxREAL; 86 | }; 87 | 88 | template 89 | struct MxTypes::value && 90 | MxIntType::value && 91 | sizeof(T) == 1, T>::type> { 92 | typedef T type; 93 | typedef mxNumeric array_type; 94 | static const mxClassID class_id = mxINT8_CLASS; 95 | static const mxComplexity complexity = mxREAL; 96 | }; 97 | 98 | template 99 | struct MxTypes::value && 100 | MxIntType::value && 101 | sizeof(T) == 1, T>::type> { 102 | typedef T type; 103 | typedef mxNumeric array_type; 104 | static const mxClassID class_id = mxUINT8_CLASS; 105 | static const mxComplexity complexity = mxREAL; 106 | }; 107 | 108 | template 109 | struct MxTypes::value && 110 | MxIntType::value && 111 | sizeof(T) == 2, T>::type> { 112 | typedef T type; 113 | typedef mxNumeric array_type; 114 | static const mxClassID class_id = mxINT16_CLASS; 115 | static const mxComplexity complexity = mxREAL; 116 | }; 117 | 118 | template 119 | struct MxTypes::value && 120 | std::is_unsigned::value && 121 | sizeof(T) == 2, T>::type> { 122 | typedef T type; 123 | typedef mxNumeric array_type; 124 | static const mxClassID class_id = mxUINT16_CLASS; 125 | static const mxComplexity complexity = mxREAL; 126 | }; 127 | 128 | template 129 | struct MxTypes::value && 130 | MxIntType::value && 131 | sizeof(T) == 4, T>::type> { 132 | typedef T type; 133 | typedef mxNumeric array_type; 134 | static const mxClassID class_id = mxINT32_CLASS; 135 | static const mxComplexity complexity = mxREAL; 136 | }; 137 | 138 | template 139 | struct MxTypes::value && 140 | MxIntType::value && 141 | sizeof(T) == 4, T>::type> { 142 | typedef T type; 143 | typedef mxNumeric array_type; 144 | static const mxClassID class_id = mxUINT32_CLASS; 145 | static const mxComplexity complexity = mxREAL; 146 | }; 147 | 148 | template 149 | struct MxTypes::value && 150 | MxIntType::value && 151 | sizeof(T) == 8, T>::type> { 152 | typedef T type; 153 | typedef mxNumeric array_type; 154 | static const mxClassID class_id = mxINT64_CLASS; 155 | static const mxComplexity complexity = mxREAL; 156 | }; 157 | 158 | template 159 | struct MxTypes::value && 160 | MxIntType::value && 161 | sizeof(T) == 8, T>::type> { 162 | typedef T type; 163 | typedef mxNumeric array_type; 164 | static const mxClassID class_id = mxUINT64_CLASS; 165 | static const mxComplexity complexity = mxREAL; 166 | }; 167 | 168 | template 169 | struct MxTypes::value && 170 | sizeof(T) == 4, T>::type> { 171 | typedef T type; 172 | typedef mxNumeric array_type; 173 | static const mxClassID class_id = mxSINGLE_CLASS; 174 | static const mxComplexity complexity = mxREAL; 175 | }; 176 | 177 | template 178 | struct MxTypes::value && 179 | sizeof(T) == 8, T>::type> { 180 | typedef T type; 181 | typedef mxNumeric array_type; 182 | static const mxClassID class_id = mxDOUBLE_CLASS; 183 | static const mxComplexity complexity = mxREAL; 184 | }; 185 | 186 | } // namespace mexplus 187 | 188 | #endif // __MEXPLUS_MXTYPES_H__ 189 | -------------------------------------------------------------------------------- /src/LMDB_.cc: -------------------------------------------------------------------------------- 1 | /** LMDB Matlab wrapper. 2 | */ 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace mexplus; 9 | 10 | #define ERROR(...) \ 11 | mexErrMsgIdAndTxt("lmdb:error", __VA_ARGS__) 12 | #define ASSERT(cond, ...) \ 13 | if (!(cond)) mexErrMsgIdAndTxt("lmdb:error", __VA_ARGS__) 14 | #define OPTIONFLAG(flag, default_value) \ 15 | ((input.get(#flag, default_value)) ? MDB_##flag : 0) 16 | 17 | namespace { 18 | 19 | // Record wrapper. 20 | class Record { 21 | public: 22 | // Create an empty record. 23 | Record() { 24 | mdb_val_.mv_size = 0; 25 | mdb_val_.mv_data = NULL; 26 | } 27 | // Create from string. 28 | Record(const string& data) { 29 | initialize(data); 30 | } 31 | virtual ~Record() {} 32 | // Initialize with string. 33 | void initialize(const string& data) { 34 | data_.assign(data.begin(), data.end()); 35 | mdb_val_.mv_size = data_.size(); 36 | mdb_val_.mv_data = const_cast(data_.c_str()); 37 | } 38 | // Sync the buffer. 39 | void sync() { 40 | data_.assign(begin(), end()); 41 | mdb_val_.mv_size = data_.size(); 42 | mdb_val_.mv_data = const_cast(data_.c_str()); 43 | } 44 | // Get MDB_val pointer. 45 | MDB_val* get() { return &mdb_val_; } 46 | // Beginning of iterator. 47 | const char* begin() const { 48 | return reinterpret_cast(mdb_val_.mv_data); 49 | } 50 | // End of iterator. 51 | const char* end() const { return begin() + mdb_val_.mv_size; } 52 | 53 | private: 54 | // Data buffer. 55 | string data_; 56 | // Dumb MDB_val. 57 | MDB_val mdb_val_; 58 | }; 59 | 60 | // Database manager. 61 | class Database { 62 | public: 63 | // Create an empty database environment. 64 | Database() : env_(NULL) { 65 | int status = mdb_env_create(&env_); 66 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 67 | } 68 | virtual ~Database() { close(); } 69 | // Open an environment. 70 | void openEnv(const char* filename, unsigned int flags, mdb_mode_t mode) { 71 | ASSERT(env_, "MDB_env not created."); 72 | int status = mdb_env_open(env_, filename, flags, mode); 73 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 74 | } 75 | // Open a table. 76 | void openDBI(MDB_txn* txn, const char* name, unsigned int flags) { 77 | ASSERT(env_, "MDB_env not opened."); 78 | int status = mdb_dbi_open(txn, name, flags, &dbi_); 79 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 80 | } 81 | // Close both the table and the environment. 82 | void close() { 83 | if (env_) { 84 | mdb_dbi_close(env_, dbi_); 85 | mdb_env_close(env_); 86 | } 87 | env_ = NULL; 88 | } 89 | // Set the size of the memory map to use for this environment. 90 | void setMapsize(size_t mapsize) { 91 | int status = mdb_env_set_mapsize(env_, mapsize); 92 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 93 | } 94 | // Set the maximum number of threads/reader slots for the environment. 95 | void setMaxReaders(unsigned int readers) { 96 | int status = mdb_env_set_maxreaders(env_, readers); 97 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 98 | } 99 | // Set the maximum number of named databases for the environment. 100 | void setMaxDBS(MDB_dbi dbs) { 101 | int status = mdb_env_set_maxdbs(env_, dbs); 102 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 103 | } 104 | // Get the raw MDB_env pointer. 105 | MDB_env* getEnv() { return env_; } 106 | // Get the raw MDB_dbi pointer. 107 | MDB_dbi getDBI() { return dbi_; } 108 | 109 | private: 110 | // MDB_env pointer. 111 | MDB_env* env_; 112 | // MDB_dbi pointer. 113 | MDB_dbi dbi_; 114 | }; 115 | 116 | // Transaction manager. 117 | class Transaction { 118 | public: 119 | // Create an empty transaction. 120 | Transaction() : txn_(NULL), database_(NULL) {} 121 | // Shorthand for constructor-begin. 122 | Transaction(Database* database, MDB_txn* parent, unsigned int flags) : 123 | txn_(NULL), database_(NULL) { 124 | begin(database, parent, flags); 125 | } 126 | virtual ~Transaction() { abort(); } 127 | // Begin the transaction. 128 | void begin(Database* database, MDB_txn* parent, unsigned int flags) { 129 | ASSERT(database, "Null pointer exception."); 130 | ASSERT(database->getEnv(), "Null pointer exception."); 131 | abort(); 132 | database_ = database; 133 | int status = mdb_txn_begin(database_->getEnv(), parent, flags, &txn_); 134 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 135 | } 136 | // Commit the transaction. 137 | void commit() { 138 | if (txn_) { 139 | int status = mdb_txn_commit(txn_); 140 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 141 | } 142 | txn_ = NULL; 143 | database_ = NULL; 144 | } 145 | // Abort the transaction. 146 | void abort() { 147 | if (txn_) { 148 | mdb_txn_abort(txn_); 149 | } 150 | txn_ = NULL; 151 | database_ = NULL; 152 | } 153 | // Open database. 154 | void openDatabase(const string& name, unsigned int flags) { 155 | database_->openDBI(txn_, 156 | (name == "") ? NULL : name.c_str(), 157 | flags); 158 | } 159 | // Get the specified database record. 160 | bool getRecord(Record* key, Record* value) { 161 | int status = mdb_get(txn_, database_->getDBI(), key->get(), value->get()); 162 | ASSERT(status == MDB_SUCCESS || status == MDB_NOTFOUND, 163 | mdb_strerror(status)); 164 | return status == MDB_SUCCESS; 165 | } 166 | // Put a database record. 167 | void putRecord(Record* key, 168 | Record* value, 169 | unsigned int flags) { 170 | int status = mdb_put(txn_, 171 | database_->getDBI(), 172 | key->get(), 173 | value->get(), 174 | flags); 175 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 176 | } 177 | // Delete the specified database record. 178 | void removeRecord(Record* key) { 179 | int status = mdb_del(txn_, database_->getDBI(), key->get(), NULL); 180 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 181 | } 182 | // Get the raw transaction pointer. 183 | MDB_txn* get() { return txn_; } 184 | 185 | private: 186 | // MDB_txn pointer. 187 | MDB_txn* txn_; 188 | // Database pointer. 189 | Database* database_; 190 | }; 191 | 192 | // Cursor container. 193 | class Cursor { 194 | public: 195 | Cursor() : cursor_(NULL) {} 196 | virtual ~Cursor() { close(); } 197 | // Open the cursor. 198 | void open(MDB_txn *txn, MDB_dbi dbi) { 199 | close(); 200 | int status = mdb_cursor_open(txn, dbi, &cursor_); 201 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 202 | } 203 | // Close the cursor. 204 | void close() { 205 | if (cursor_) 206 | mdb_cursor_close(cursor_); 207 | cursor_ = NULL; 208 | } 209 | // Apply the cursor operation and get the value. 210 | bool get(MDB_cursor_op operation) { 211 | int status = mdb_cursor_get(cursor_, key_.get(), value_.get(), operation); 212 | ASSERT(status == MDB_SUCCESS || status == MDB_NOTFOUND, 213 | mdb_strerror(status)); 214 | return status == MDB_SUCCESS; 215 | } 216 | // Put the current key and value. 217 | void put(unsigned int flags) { 218 | int status = mdb_cursor_put(cursor_, key_.get(), value_.get(), flags); 219 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 220 | } 221 | // Delete the current key and value. 222 | void remove(unsigned int flags) { 223 | int status = mdb_cursor_del(cursor_, flags); 224 | ASSERT(status == MDB_SUCCESS, mdb_strerror(status)); 225 | } 226 | // Get the raw cursor. 227 | MDB_cursor* get() { return cursor_; } 228 | // Get the raw record. 229 | Record* getKey() { return &key_; } 230 | // Get the raw record. 231 | Record* getValue() { return &value_; } 232 | 233 | private: 234 | // MDB_cursor pointer. 235 | MDB_cursor* cursor_; 236 | // Key. 237 | Record key_; 238 | // Value. 239 | Record value_; 240 | }; 241 | 242 | // Create a directory. 243 | void createDirectoryIfNotExist(const mxArray* filename) { 244 | MxArray flag("dir"); 245 | mxArray* prhs[] = {const_cast(filename), 246 | const_cast(flag.get())}; 247 | mxArray* plhs; 248 | ASSERT(mexCallMATLAB(1, &plhs, 2, prhs, "exist") == 0, 249 | "Failed to check a directory."); 250 | MxArray status(plhs); 251 | if (!status.to()) 252 | ASSERT(mexCallMATLAB(0, NULL, 1, prhs, "mkdir") == 0, 253 | "Failed to create a directory."); 254 | } 255 | 256 | } // namespace 257 | 258 | namespace mexplus { 259 | 260 | // Template specialization of mxArray* to Record. 261 | template <> 262 | void MxArray::to(const mxArray* array, Record* value) { 263 | ASSERT(value, "Null pointer exception."); 264 | value->initialize(MxArray(array).to()); 265 | } 266 | 267 | // Template specialization of Record to mxArray*. 268 | template <> 269 | mxArray* MxArray::from(const Record& value) { 270 | return MxArray(string(value.begin(), value.end())).release(); 271 | } 272 | 273 | // Template specialization of MDB_stat to mxArray*. 274 | template <> 275 | mxArray* MxArray::from(const MDB_stat& stat) { 276 | MxArray value(Struct()); 277 | value.set("psize", stat.ms_psize); 278 | value.set("depth", stat.ms_depth); 279 | value.set("branch_pages", stat.ms_branch_pages); 280 | value.set("leaf_pages", stat.ms_leaf_pages); 281 | value.set("overflow_pages", stat.ms_overflow_pages); 282 | value.set("entries", stat.ms_entries); 283 | return value.release(); 284 | } 285 | 286 | // Session instance storage. 287 | template class Session; 288 | template class Session; 289 | template class Session; 290 | 291 | } // namespace mexplus 292 | 293 | namespace { 294 | 295 | MEX_DEFINE(new) (int nlhs, mxArray* plhs[], 296 | int nrhs, const mxArray* prhs[]) { 297 | InputArguments input(nrhs, prhs, 1, 23, "MODE", "FIXEDMAP", "NOSUBDIR", 298 | "NOSYNC", "RDONLY", "NOMETASYNC", "WRITEMAP", "MAPASYNC", "NOTLS", 299 | "NOLOCK", "NORDAHEAD", "NOMEMINIT", "REVERSEKEY", "DUPSORT", 300 | "INTEGERKEY", "DUPFIXED", "INTEGERDUP", "REVERSEDUP", "CREATE", 301 | "MAPSIZE", "MAXREADERS", "MAXDBS", "NAME"); 302 | OutputArguments output(nlhs, plhs, 1); 303 | unique_ptr database(new Database); 304 | ASSERT(database.get() != NULL, "Null pointer exception."); 305 | database->setMapsize(input.get("MAPSIZE", 10485760)); 306 | database->setMaxReaders(input.get("MAXREADERS", 126)); 307 | database->setMaxDBS(input.get("MAXDBS", 0)); 308 | bool read_only = input.get("RDONLY", false); 309 | string filename(input.get(0)); 310 | mdb_mode_t mode = input.get("MODE", 0664); 311 | unsigned int flags = OPTIONFLAG(RDONLY, false) | 312 | OPTIONFLAG(FIXEDMAP, false) | 313 | OPTIONFLAG(NOSUBDIR, false) | 314 | OPTIONFLAG(NOSYNC, false) | 315 | OPTIONFLAG(NOMETASYNC, false) | 316 | OPTIONFLAG(WRITEMAP, false) | 317 | OPTIONFLAG(MAPASYNC, false) | 318 | OPTIONFLAG(NOTLS, false) | 319 | OPTIONFLAG(NOLOCK, false) | 320 | OPTIONFLAG(NORDAHEAD, false) | 321 | OPTIONFLAG(NOMEMINIT, false); 322 | if (!read_only) 323 | createDirectoryIfNotExist(input.get(0)); 324 | database->openEnv(filename.c_str(), flags, mode); 325 | flags = OPTIONFLAG(REVERSEKEY, false) | 326 | OPTIONFLAG(DUPSORT, false) | 327 | OPTIONFLAG(INTEGERKEY, false) | 328 | OPTIONFLAG(DUPFIXED, false) | 329 | OPTIONFLAG(INTEGERDUP, false) | 330 | OPTIONFLAG(REVERSEDUP, false) | 331 | OPTIONFLAG(CREATE, !read_only); 332 | Transaction transaction(database.get(), NULL, (read_only) ? MDB_RDONLY : 0); 333 | transaction.openDatabase(input.get("NAME", ""), flags); 334 | transaction.commit(); 335 | output.set(0, Session::create(database.release())); 336 | } 337 | 338 | MEX_DEFINE(delete) (int nlhs, mxArray* plhs[], 339 | int nrhs, const mxArray* prhs[]) { 340 | InputArguments input(nrhs, prhs, 1); 341 | OutputArguments output(nlhs, plhs, 0); 342 | Session::destroy(input.get(0)); 343 | } 344 | 345 | MEX_DEFINE(get) (int nlhs, mxArray* plhs[], 346 | int nrhs, const mxArray* prhs[]) { 347 | InputArguments input(nrhs, prhs, 2); 348 | OutputArguments output(nlhs, plhs, 1); 349 | Database* database = Session::get(input.get(0)); 350 | Record key = input.get(1); 351 | Record value; 352 | Transaction transaction(database, NULL, MDB_RDONLY); 353 | transaction.getRecord(&key, &value); 354 | transaction.commit(); 355 | output.set(0, value); 356 | } 357 | 358 | MEX_DEFINE(put) (int nlhs, mxArray* plhs[], 359 | int nrhs, const mxArray* prhs[]) { 360 | InputArguments input(nrhs, prhs, 3, 4, "NODUPDATA", "NOOVERWRITE", "RESERVE", 361 | "APPEND"); 362 | OutputArguments output(nlhs, plhs, 0); 363 | Database* database = Session::get(input.get(0)); 364 | unsigned int flags = OPTIONFLAG(NODUPDATA, false) | 365 | OPTIONFLAG(NOOVERWRITE, false) | 366 | OPTIONFLAG(RESERVE, false) | 367 | OPTIONFLAG(APPEND, false); 368 | Record key = input.get(1); 369 | Record value = input.get(2); 370 | Transaction transaction(database, NULL, 0); 371 | transaction.putRecord(&key, &value, flags); 372 | transaction.commit(); 373 | } 374 | 375 | MEX_DEFINE(remove) (int nlhs, mxArray* plhs[], 376 | int nrhs, const mxArray* prhs[]) { 377 | InputArguments input(nrhs, prhs, 2); 378 | OutputArguments output(nlhs, plhs, 0); 379 | Database* database = Session::get(input.get(0)); 380 | Record key = input.get(1); 381 | Transaction transaction(database, NULL, 0); 382 | transaction.removeRecord(&key); 383 | transaction.commit(); 384 | } 385 | 386 | MEX_DEFINE(each) (int nlhs, mxArray* plhs[], 387 | int nrhs, const mxArray* prhs[]) { 388 | InputArguments input(nrhs, prhs, 2); 389 | OutputArguments output(nlhs, plhs, 0); 390 | Database* database = Session::get(input.get(0)); 391 | Transaction transaction(database, NULL, MDB_RDONLY); 392 | Cursor cursor; 393 | cursor.open(transaction.get(), database->getDBI()); 394 | while (cursor.get(MDB_NEXT)) { 395 | MxArray key_array(*cursor.getKey()); 396 | MxArray value_array(*cursor.getValue()); 397 | mxArray* prhs[] = {const_cast(input.get(1)), 398 | const_cast(key_array.get()), 399 | const_cast(value_array.get())}; 400 | ASSERT(mexCallMATLAB(0, NULL, 3, prhs, "feval") == 0, "Callback failure."); 401 | } 402 | cursor.close(); 403 | transaction.commit(); 404 | } 405 | 406 | MEX_DEFINE(reduce) (int nlhs, mxArray* plhs[], 407 | int nrhs, const mxArray* prhs[]) { 408 | InputArguments input(nrhs, prhs, 3); 409 | OutputArguments output(nlhs, plhs, 1); 410 | Database* database = Session::get(input.get(0)); 411 | MxArray accumulation(input.get(2)); 412 | Transaction transaction(database, NULL, MDB_RDONLY); 413 | Cursor cursor; 414 | cursor.open(transaction.get(), database->getDBI()); 415 | while (cursor.get(MDB_NEXT)) { 416 | MxArray key_array(*cursor.getKey()); 417 | MxArray value_array(*cursor.getValue()); 418 | mxArray* lhs = NULL; 419 | mxArray* prhs[] = {const_cast(input.get(1)), 420 | const_cast(key_array.get()), 421 | const_cast(value_array.get()), 422 | const_cast(accumulation.get())}; 423 | ASSERT(mexCallMATLAB(1, &lhs, 4, prhs, "feval") == 0, "Callback failure."); 424 | accumulation.reset(lhs); 425 | } 426 | cursor.close(); 427 | transaction.commit(); 428 | output.set(0, accumulation.release()); 429 | } 430 | 431 | MEX_DEFINE(txn_new) (int nlhs, mxArray* plhs[], 432 | int nrhs, const mxArray* prhs[]) { 433 | InputArguments input(nrhs, prhs, 1, 1, "RDONLY"); 434 | OutputArguments output(nlhs, plhs, 1); 435 | Database* database = Session::get(input.get(0)); 436 | unsigned int flags = (input.get("RDONLY", false)) ? MDB_RDONLY : 0; 437 | output.set(0, Session::create( 438 | new Transaction(database, NULL, flags))); 439 | } 440 | 441 | MEX_DEFINE(txn_delete) (int nlhs, mxArray* plhs[], 442 | int nrhs, const mxArray* prhs[]) { 443 | InputArguments input(nrhs, prhs, 1); 444 | OutputArguments output(nlhs, plhs, 0); 445 | Session::destroy(input.get(0)); 446 | } 447 | 448 | MEX_DEFINE(txn_commit) (int nlhs, mxArray* plhs[], 449 | int nrhs, const mxArray* prhs[]) { 450 | InputArguments input(nrhs, prhs, 1); 451 | OutputArguments output(nlhs, plhs, 0); 452 | Transaction* transaction = Session::get(input.get(0)); 453 | transaction->commit(); 454 | } 455 | 456 | MEX_DEFINE(txn_abort) (int nlhs, mxArray* plhs[], 457 | int nrhs, const mxArray* prhs[]) { 458 | InputArguments input(nrhs, prhs, 1); 459 | OutputArguments output(nlhs, plhs, 0); 460 | Transaction* transaction = Session::get(input.get(0)); 461 | transaction->abort(); 462 | } 463 | 464 | MEX_DEFINE(txn_get) (int nlhs, mxArray* plhs[], 465 | int nrhs, const mxArray* prhs[]) { 466 | InputArguments input(nrhs, prhs, 2); 467 | OutputArguments output(nlhs, plhs, 1); 468 | Transaction* transaction = Session::get(input.get(0)); 469 | Record key = input.get(1); 470 | Record value; 471 | transaction->getRecord(&key, &value); 472 | output.set(0, value); 473 | } 474 | 475 | MEX_DEFINE(txn_put) (int nlhs, mxArray* plhs[], 476 | int nrhs, const mxArray* prhs[]) { 477 | InputArguments input(nrhs, prhs, 3, 4, "NODUPDATA", "NOOVERWRITE", "RESERVE", 478 | "APPEND"); 479 | OutputArguments output(nlhs, plhs, 0); 480 | Transaction* transaction = Session::get(input.get(0)); 481 | unsigned int flags = OPTIONFLAG(NODUPDATA, false) | 482 | OPTIONFLAG(NOOVERWRITE, false) | 483 | OPTIONFLAG(RESERVE, false) | 484 | OPTIONFLAG(APPEND, false); 485 | Record key = input.get(1); 486 | Record value = input.get(2); 487 | transaction->putRecord(&key, &value, flags); 488 | } 489 | 490 | MEX_DEFINE(txn_remove) (int nlhs, mxArray* plhs[], 491 | int nrhs, const mxArray* prhs[]) { 492 | InputArguments input(nrhs, prhs, 2); 493 | OutputArguments output(nlhs, plhs, 0); 494 | Transaction* transaction = Session::get(input.get(0)); 495 | Record key = input.get(1); 496 | transaction->removeRecord(&key); 497 | } 498 | 499 | MEX_DEFINE(cursor_new) (int nlhs, mxArray* plhs[], 500 | int nrhs, const mxArray* prhs[]) { 501 | InputArguments input(nrhs, prhs, 2); 502 | OutputArguments output(nlhs, plhs, 1); 503 | Transaction* transaction = Session::get(input.get(0)); 504 | Database* database = Session::get(input.get(1)); 505 | unique_ptr cursor(new Cursor); 506 | cursor->open(transaction->get(), database->getDBI()); 507 | output.set(0, Session::create(cursor.release())); 508 | } 509 | 510 | MEX_DEFINE(cursor_delete) (int nlhs, mxArray* plhs[], 511 | int nrhs, const mxArray* prhs[]) { 512 | InputArguments input(nrhs, prhs, 1); 513 | OutputArguments output(nlhs, plhs, 0); 514 | Session::destroy(input.get(0)); 515 | } 516 | 517 | MEX_DEFINE(cursor_next) (int nlhs, mxArray* plhs[], 518 | int nrhs, const mxArray* prhs[]) { 519 | InputArguments input(nrhs, prhs, 1); 520 | OutputArguments output(nlhs, plhs, 1); 521 | Cursor* cursor = Session::get(input.get(0)); 522 | output.set(0, cursor->get(MDB_NEXT)); 523 | } 524 | 525 | MEX_DEFINE(cursor_previous) (int nlhs, mxArray* plhs[], 526 | int nrhs, const mxArray* prhs[]) { 527 | InputArguments input(nrhs, prhs, 1); 528 | OutputArguments output(nlhs, plhs, 1); 529 | Cursor* cursor = Session::get(input.get(0)); 530 | output.set(0, cursor->get(MDB_PREV)); 531 | } 532 | 533 | MEX_DEFINE(cursor_first) (int nlhs, mxArray* plhs[], 534 | int nrhs, const mxArray* prhs[]) { 535 | InputArguments input(nrhs, prhs, 1); 536 | OutputArguments output(nlhs, plhs, 1); 537 | Cursor* cursor = Session::get(input.get(0)); 538 | output.set(0, cursor->get(MDB_FIRST)); 539 | } 540 | 541 | MEX_DEFINE(cursor_last) (int nlhs, mxArray* plhs[], 542 | int nrhs, const mxArray* prhs[]) { 543 | InputArguments input(nrhs, prhs, 1); 544 | OutputArguments output(nlhs, plhs, 1); 545 | Cursor* cursor = Session::get(input.get(0)); 546 | output.set(0, cursor->get(MDB_LAST)); 547 | } 548 | 549 | MEX_DEFINE(cursor_find) (int nlhs, mxArray* plhs[], 550 | int nrhs, const mxArray* prhs[]) { 551 | InputArguments input(nrhs, prhs, 2); 552 | OutputArguments output(nlhs, plhs, 1); 553 | Cursor* cursor = Session::get(input.get(0)); 554 | input.get(1, cursor->getKey()); 555 | output.set(0, cursor->get(MDB_SET)); 556 | } 557 | 558 | MEX_DEFINE(cursor_getkey) (int nlhs, mxArray* plhs[], 559 | int nrhs, const mxArray* prhs[]) { 560 | InputArguments input(nrhs, prhs, 1); 561 | OutputArguments output(nlhs, plhs, 1); 562 | Cursor* cursor = Session::get(input.get(0)); 563 | output.set(0, *cursor->getKey()); 564 | } 565 | 566 | MEX_DEFINE(cursor_setkey) (int nlhs, mxArray* plhs[], 567 | int nrhs, const mxArray* prhs[]) { 568 | InputArguments input(nrhs, prhs, 2, 7, "CURRENT", "NODUPDATA", "NOOVERWRITE", 569 | "RESERVE", "APPEND", "APPENDDUP", "MULTIPLE"); 570 | OutputArguments output(nlhs, plhs, 0); 571 | Cursor* cursor = Session::get(input.get(0)); 572 | input.get(1, cursor->getKey()); 573 | cursor->getValue()->sync(); 574 | unsigned int flags = OPTIONFLAG(CURRENT, true) | 575 | OPTIONFLAG(NODUPDATA, false) | 576 | OPTIONFLAG(NOOVERWRITE, false) | 577 | OPTIONFLAG(RESERVE, false) | 578 | OPTIONFLAG(APPEND, false) | 579 | OPTIONFLAG(APPENDDUP, false) | 580 | OPTIONFLAG(MULTIPLE, false); 581 | cursor->put(flags); 582 | } 583 | 584 | MEX_DEFINE(cursor_getvalue) (int nlhs, mxArray* plhs[], 585 | int nrhs, const mxArray* prhs[]) { 586 | InputArguments input(nrhs, prhs, 1); 587 | OutputArguments output(nlhs, plhs, 1); 588 | Cursor* cursor = Session::get(input.get(0)); 589 | output.set(0, *cursor->getValue()); 590 | } 591 | 592 | MEX_DEFINE(cursor_setvalue) (int nlhs, mxArray* plhs[], 593 | int nrhs, const mxArray* prhs[]) { 594 | InputArguments input(nrhs, prhs, 2, 7, "CURRENT", "NODUPDATA", "NOOVERWRITE", 595 | "RESERVE", "APPEND", "APPENDDUP", "MULTIPLE"); 596 | OutputArguments output(nlhs, plhs, 0); 597 | Cursor* cursor = Session::get(input.get(0)); 598 | input.get(1, cursor->getValue()); 599 | cursor->getKey()->sync(); 600 | unsigned int flags = OPTIONFLAG(CURRENT, true) | 601 | OPTIONFLAG(NODUPDATA, false) | 602 | OPTIONFLAG(NOOVERWRITE, false) | 603 | OPTIONFLAG(RESERVE, false) | 604 | OPTIONFLAG(APPEND, false) | 605 | OPTIONFLAG(APPENDDUP, false) | 606 | OPTIONFLAG(MULTIPLE, false); 607 | cursor->put(flags); 608 | } 609 | 610 | MEX_DEFINE(cursor_remove) (int nlhs, mxArray* plhs[], 611 | int nrhs, const mxArray* prhs[]) { 612 | InputArguments input(nrhs, prhs, 1); 613 | OutputArguments output(nlhs, plhs, 0); 614 | Cursor* cursor = Session::get(input.get(0)); 615 | unsigned int flags = OPTIONFLAG(NODUPDATA, true); 616 | cursor->remove(flags); 617 | } 618 | 619 | MEX_DEFINE(keys) (int nlhs, mxArray* plhs[], 620 | int nrhs, const mxArray* prhs[]) { 621 | InputArguments input(nrhs, prhs, 1); 622 | OutputArguments output(nlhs, plhs, 1); 623 | Database* database = Session::get(input.get(0)); 624 | Transaction transaction(database, NULL, MDB_RDONLY); 625 | Cursor cursor; 626 | cursor.open(transaction.get(), database->getDBI()); 627 | vector key_values; 628 | while (cursor.get(MDB_NEXT)) { 629 | Record* key = cursor.getKey(); 630 | key_values.push_back(string(key->begin(), key->end())); 631 | } 632 | cursor.close(); 633 | transaction.commit(); 634 | output.set(0, key_values); 635 | } 636 | 637 | MEX_DEFINE(values) (int nlhs, mxArray* plhs[], 638 | int nrhs, const mxArray* prhs[]) { 639 | InputArguments input(nrhs, prhs, 1); 640 | OutputArguments output(nlhs, plhs, 1); 641 | Database* database = Session::get(input.get(0)); 642 | Transaction transaction(database, NULL, MDB_RDONLY); 643 | Cursor cursor; 644 | cursor.open(transaction.get(), database->getDBI()); 645 | vector value_values; 646 | while (cursor.get(MDB_NEXT)) { 647 | Record* value = cursor.getValue(); 648 | value_values.push_back(string(value->begin(), value->end())); 649 | } 650 | cursor.close(); 651 | transaction.commit(); 652 | output.set(0, value_values); 653 | } 654 | 655 | MEX_DEFINE(stat) (int nlhs, mxArray* plhs[], 656 | int nrhs, const mxArray* prhs[]) { 657 | InputArguments input(nrhs, prhs, 1); 658 | OutputArguments output(nlhs, plhs, 1); 659 | Database* database = Session::get(input.get(0)); 660 | MDB_stat stat; 661 | mdb_env_stat(database->getEnv(), &stat); 662 | output.set(0, stat); 663 | } 664 | 665 | } // namespace 666 | 667 | MEX_DISPATCH 668 | -------------------------------------------------------------------------------- /src/liblmdb/.gitignore: -------------------------------------------------------------------------------- 1 | mtest 2 | mtest[23456] 3 | testdb 4 | mdb_copy 5 | mdb_stat 6 | mdb_dump 7 | mdb_load 8 | *.lo 9 | *.[ao] 10 | *.so 11 | *.exe 12 | *[~#] 13 | *.bak 14 | *.orig 15 | *.rej 16 | *.gcov 17 | *.gcda 18 | *.gcno 19 | core 20 | core.* 21 | valgrind.* 22 | man/ 23 | html/ 24 | -------------------------------------------------------------------------------- /src/liblmdb/CHANGES: -------------------------------------------------------------------------------- 1 | LMDB 0.9 Change Log 2 | 3 | LMDB 0.9.14 Release (2014/09/20) 4 | Fix to support 64K page size (ITS#7713) 5 | Fix to persist decreased as well as increased mapsizes (ITS#7789) 6 | Fix cursor bug when deleting last node of a DUPSORT key 7 | Fix mdb_env_info to return FIXEDMAP address 8 | Fix ambiguous error code from writing to closed DBI (ITS#7825) 9 | Fix mdb_copy copying past end of file (ITS#7886) 10 | Fix cursor bugs from page_merge/rebalance 11 | Fix to dirty fewer pages in deletes (mdb_page_loose()) 12 | Fix mdb_dbi_open creating subDBs (ITS#7917) 13 | Fix mdb_cursor_get(_DUP) with single value (ITS#7913) 14 | Fix Windows compat issues in mtests (ITS#7879) 15 | Add compacting variant of mdb_copy 16 | Add BigEndian integer key compare code 17 | Add mdb_dump/mdb_load utilities 18 | 19 | LMDB 0.9.13 Release (2014/06/18) 20 | Fix mdb_page_alloc unlimited overflow page search 21 | Documentation 22 | Re-fix MDB_CURRENT doc (ITS#7793) 23 | Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc 24 | 25 | LMDB 0.9.12 Release (2014/06/13) 26 | Fix MDB_GET_BOTH regression (ITS#7875,#7681) 27 | Fix MDB_MULTIPLE writing multiple keys (ITS#7834) 28 | Fix mdb_rebalance (ITS#7829) 29 | Fix mdb_page_split (ITS#7815) 30 | Fix md_entries count (ITS#7861,#7828,#7793) 31 | Fix MDB_CURRENT (ITS#7793) 32 | Fix possible crash on Windows DLL detach 33 | Misc code cleanup 34 | Documentation 35 | mdb_cursor_put: cursor moves on error (ITS#7771) 36 | 37 | 38 | LMDB 0.9.11 Release (2014/01/15) 39 | Add mdb_env_set_assert() (ITS#7775) 40 | Fix: invalidate txn on page allocation errors (ITS#7377) 41 | Fix xcursor tracking in mdb_cursor_del0() (ITS#7771) 42 | Fix corruption from deletes (ITS#7756) 43 | Fix Windows/MSVC build issues 44 | Raise safe limit of max MDB_MAXKEYSIZE 45 | Misc code cleanup 46 | Documentation 47 | Remove spurious note about non-overlapping flags (ITS#7665) 48 | 49 | LMDB 0.9.10 Release (2013/11/12) 50 | Add MDB_NOMEMINIT option 51 | Fix mdb_page_split() again (ITS#7589) 52 | Fix MDB_NORDAHEAD definition (ITS#7734) 53 | Fix mdb_cursor_del() positioning (ITS#7733) 54 | Partial fix for larger page sizes (ITS#7713) 55 | Fix Windows64/MSVC build issues 56 | 57 | LMDB 0.9.9 Release (2013/10/24) 58 | Add mdb_env_get_fd() 59 | Add MDB_NORDAHEAD option 60 | Add MDB_NOLOCK option 61 | Avoid wasting space in mdb_page_split() (ITS#7589) 62 | Fix mdb_page_merge() cursor fixup (ITS#7722) 63 | Fix mdb_cursor_del() on last delete (ITS#7718) 64 | Fix adding WRITEMAP on existing env (ITS#7715) 65 | Fix nested txns (ITS#7515) 66 | Fix mdb_env_copy() O_DIRECT bug (ITS#7682) 67 | Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681) 68 | Fix mdb_rebalance() cursor fixup (ITS#7701) 69 | Misc code cleanup 70 | Documentation 71 | Note that by default, readers need write access 72 | 73 | 74 | LMDB 0.9.8 Release (2013/09/09) 75 | Allow mdb_env_set_mapsize() on an open environment 76 | Fix mdb_dbi_flags() (ITS#7672) 77 | Fix mdb_page_unspill() in nested txns 78 | Fix mdb_cursor_get(CURRENT|NEXT) after a delete 79 | Fix mdb_cursor_get(DUP) to always return key (ITS#7671) 80 | Fix mdb_cursor_del() to always advance to next item (ITS#7670) 81 | Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681) 82 | Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682) 83 | Tweak mdb_page_spill() to be less aggressive 84 | Documentation 85 | Update caveats since mdb_reader_check() added in 0.9.7 86 | 87 | LMDB 0.9.7 Release (2013/08/17) 88 | Don't leave stale lockfile on failed RDONLY open (ITS#7664) 89 | Fix mdb_page_split() ref beyond cursor depth 90 | Fix read txn data race (ITS#7635) 91 | Fix mdb_rebalance (ITS#7536, #7538) 92 | Fix mdb_drop() (ITS#7561) 93 | Misc DEBUG macro fixes 94 | Add MDB_NOTLS envflag 95 | Add mdb_env_copyfd() 96 | Add mdb_txn_env() (ITS#7660) 97 | Add mdb_dbi_flags() (ITS#7661) 98 | Add mdb_env_get_maxkeysize() 99 | Add mdb_env_reader_list()/mdb_env_reader_check() 100 | Add mdb_page_spill/unspill, remove hard txn size limit 101 | Use shorter names for semaphores (ITS#7615) 102 | Build 103 | Fix install target (ITS#7656) 104 | Documentation 105 | Misc updates for cursors, DB handles, data lifetime 106 | 107 | LMDB 0.9.6 Release (2013/02/25) 108 | Many fixes/enhancements 109 | 110 | LMDB 0.9.5 Release (2012/11/30) 111 | Renamed from libmdb to liblmdb 112 | Many fixes/enhancements 113 | -------------------------------------------------------------------------------- /src/liblmdb/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2011-2014 Howard Chu, Symas Corp. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted only as authorized by the OpenLDAP 6 | Public License. 7 | 8 | A copy of this license is available in the file LICENSE in the 9 | top-level directory of the distribution or, alternatively, at 10 | . 11 | 12 | OpenLDAP is a registered trademark of the OpenLDAP Foundation. 13 | 14 | Individual files and/or contributed packages may be copyright by 15 | other parties and/or subject to additional restrictions. 16 | 17 | This work also contains materials derived from public sources. 18 | 19 | Additional information about OpenLDAP can be obtained at 20 | . 21 | -------------------------------------------------------------------------------- /src/liblmdb/LICENSE: -------------------------------------------------------------------------------- 1 | The OpenLDAP Public License 2 | Version 2.8, 17 August 2003 3 | 4 | Redistribution and use of this software and associated documentation 5 | ("Software"), with or without modification, are permitted provided 6 | that the following conditions are met: 7 | 8 | 1. Redistributions in source form must retain copyright statements 9 | and notices, 10 | 11 | 2. Redistributions in binary form must reproduce applicable copyright 12 | statements and notices, this list of conditions, and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution, and 15 | 16 | 3. Redistributions must contain a verbatim copy of this document. 17 | 18 | The OpenLDAP Foundation may revise this license from time to time. 19 | Each revision is distinguished by a version number. You may use 20 | this Software under terms of this license revision or under the 21 | terms of any subsequent revision of the license. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS 24 | CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 25 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 26 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 27 | SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) 28 | OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | POSSIBILITY OF SUCH DAMAGE. 36 | 37 | The names of the authors and copyright holders must not be used in 38 | advertising or otherwise to promote the sale, use or other dealing 39 | in this Software without specific, written prior permission. Title 40 | to copyright in this Software shall at all times remain with copyright 41 | holders. 42 | 43 | OpenLDAP is a registered trademark of the OpenLDAP Foundation. 44 | 45 | Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, 46 | California, USA. All Rights Reserved. Permission to copy and 47 | distribute verbatim copies of this document is granted. 48 | -------------------------------------------------------------------------------- /src/liblmdb/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for liblmdb (Lightning memory-mapped database library). 2 | 3 | ######################################################################## 4 | # Configuration. The compiler options must enable threaded compilation. 5 | # 6 | # Preprocessor macros (for CPPFLAGS) of interest... 7 | # Note that the defaults should already be correct for most 8 | # platforms; you should not need to change any of these. 9 | # Read their descriptions in mdb.c if you do: 10 | # 11 | # - MDB_USE_POSIX_SEM 12 | # - MDB_DSYNC 13 | # - MDB_FDATASYNC 14 | # - MDB_USE_PWRITEV 15 | # 16 | # There may be other macros in mdb.c of interest. You should 17 | # read mdb.c before changing any of them. 18 | # 19 | CC = gcc 20 | W = -W -Wall -Wno-unused-parameter -Wbad-function-cast -Wuninitialized 21 | THREADS = -pthread 22 | OPT = -O2 -g 23 | CFLAGS = -fPIC $(THREADS) $(OPT) $(W) $(XCFLAGS) 24 | LDLIBS = 25 | SOLIBS = 26 | prefix = /usr/local 27 | 28 | ######################################################################## 29 | 30 | IHDRS = lmdb.h 31 | ILIBS = liblmdb.a liblmdb.so 32 | IPROGS = mdb_stat mdb_copy mdb_dump mdb_load 33 | IDOCS = mdb_stat.1 mdb_copy.1 mdb_dump.1 mdb_load.1 34 | PROGS = $(IPROGS) mtest mtest2 mtest3 mtest4 mtest5 35 | all: $(ILIBS) $(PROGS) 36 | 37 | install: $(ILIBS) $(IPROGS) $(IHDRS) 38 | for f in $(IPROGS); do cp $$f $(DESTDIR)$(prefix)/bin; done 39 | for f in $(ILIBS); do cp $$f $(DESTDIR)$(prefix)/lib; done 40 | for f in $(IHDRS); do cp $$f $(DESTDIR)$(prefix)/include; done 41 | for f in $(IDOCS); do cp $$f $(DESTDIR)$(prefix)/man/man1; done 42 | 43 | clean: 44 | rm -rf $(PROGS) *.[ao] *.so *~ testdb 45 | 46 | test: all 47 | rm -rf testdb && mkdir testdb 48 | ./mtest && ./mdb_stat testdb 49 | 50 | liblmdb.a: mdb.o midl.o 51 | ar rs $@ mdb.o midl.o 52 | 53 | liblmdb.so: mdb.lo midl.lo 54 | # $(CC) $(LDFLAGS) -pthread -shared -Wl,-Bsymbolic -o $@ mdb.o midl.o $(SOLIBS) 55 | $(CC) $(LDFLAGS) -pthread -shared -o $@ mdb.lo midl.lo $(SOLIBS) 56 | 57 | mdb_stat: mdb_stat.o liblmdb.a 58 | mdb_copy: mdb_copy.o liblmdb.a 59 | mdb_dump: mdb_dump.o liblmdb.a 60 | mdb_load: mdb_load.o liblmdb.a 61 | mtest: mtest.o liblmdb.a 62 | mtest2: mtest2.o liblmdb.a 63 | mtest3: mtest3.o liblmdb.a 64 | mtest4: mtest4.o liblmdb.a 65 | mtest5: mtest5.o liblmdb.a 66 | mtest6: mtest6.o liblmdb.a 67 | 68 | mdb.o: mdb.c lmdb.h midl.h 69 | $(CC) $(CFLAGS) $(CPPFLAGS) -c mdb.c 70 | 71 | midl.o: midl.c midl.h 72 | $(CC) $(CFLAGS) $(CPPFLAGS) -c midl.c 73 | 74 | mdb.lo: mdb.c lmdb.h midl.h 75 | $(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -c mdb.c -o $@ 76 | 77 | midl.lo: midl.c midl.h 78 | $(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -c midl.c -o $@ 79 | 80 | %: %.o 81 | $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ 82 | 83 | %.o: %.c lmdb.h 84 | $(CC) $(CFLAGS) $(CPPFLAGS) -c $< 85 | 86 | COV_FLAGS=-fprofile-arcs -ftest-coverage 87 | COV_OBJS=xmdb.o xmidl.o 88 | 89 | coverage: xmtest 90 | for i in mtest*.c [0-9]*.c; do j=`basename \$$i .c`; $(MAKE) $$j.o; \ 91 | gcc -o x$$j $$j.o $(COV_OBJS) -pthread $(COV_FLAGS); \ 92 | rm -rf testdb; mkdir testdb; ./x$$j; done 93 | gcov xmdb.c 94 | gcov xmidl.c 95 | 96 | xmtest: mtest.o xmdb.o xmidl.o 97 | gcc -o xmtest mtest.o xmdb.o xmidl.o -pthread $(COV_FLAGS) 98 | 99 | xmdb.o: mdb.c lmdb.h midl.h 100 | $(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -O0 $(COV_FLAGS) -c mdb.c -o $@ 101 | 102 | xmidl.o: midl.c midl.h 103 | $(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -O0 $(COV_FLAGS) -c midl.c -o $@ 104 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_copy.1: -------------------------------------------------------------------------------- 1 | .TH MDB_COPY 1 "2014/06/20" "LMDB 0.9.14" 2 | .\" Copyright 2012-2014 Howard Chu, Symas Corp. All Rights Reserved. 3 | .\" Copying restrictions apply. See COPYRIGHT/LICENSE. 4 | .SH NAME 5 | mdb_copy \- LMDB environment copy tool 6 | .SH SYNOPSIS 7 | .B mdb_copy 8 | [\c 9 | .BR \-V ] 10 | [\c 11 | .BR \-c ] 12 | [\c 13 | .BR \-n ] 14 | .B srcpath 15 | [\c 16 | .BR dstpath ] 17 | .SH DESCRIPTION 18 | The 19 | .B mdb_copy 20 | utility copies an LMDB environment. The environment can 21 | be copied regardless of whether it is currently in use. 22 | No lockfile is created, since it gets recreated at need. 23 | 24 | If 25 | .I dstpath 26 | is specified it must be the path of an empty directory 27 | for storing the backup. Otherwise, the backup will be 28 | written to stdout. 29 | 30 | .SH OPTIONS 31 | .TP 32 | .BR \-V 33 | Write the library version number to the standard output, and exit. 34 | .TP 35 | .BR \-c 36 | Compact while copying. Only current data pages will be copied; freed 37 | or unused pages will be omitted from the copy. This option will 38 | slow down the backup process as it is more CPU-intensive. 39 | .TP 40 | .BR \-n 41 | Open LDMB environment(s) which do not use subdirectories. 42 | 43 | .SH DIAGNOSTICS 44 | Exit status is zero if no errors occur. 45 | Errors result in a non-zero exit status and 46 | a diagnostic message being written to standard error. 47 | .SH CAVEATS 48 | This utility can trigger significant file size growth if run 49 | in parallel with write transactions, because pages which they 50 | free during copying cannot be reused until the copy is done. 51 | .SH "SEE ALSO" 52 | .BR mdb_stat (1) 53 | .SH AUTHOR 54 | Howard Chu of Symas Corporation 55 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_copy.c: -------------------------------------------------------------------------------- 1 | /* mdb_copy.c - memory-mapped database backup tool */ 2 | /* 3 | * Copyright 2012 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | #ifdef _WIN32 15 | #include 16 | #define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE) 17 | #else 18 | #define MDB_STDOUT 1 19 | #endif 20 | #include 21 | #include 22 | #include 23 | #include "lmdb.h" 24 | 25 | static void 26 | sighandle(int sig) 27 | { 28 | } 29 | 30 | int main(int argc,char * argv[]) 31 | { 32 | int rc; 33 | MDB_env *env; 34 | const char *progname = argv[0], *act; 35 | unsigned flags = MDB_RDONLY; 36 | unsigned cpflags = 0; 37 | 38 | for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) { 39 | if (argv[1][1] == 'n' && argv[1][2] == '\0') 40 | flags |= MDB_NOSUBDIR; 41 | else if (argv[1][1] == 'c' && argv[1][2] == '\0') 42 | cpflags |= MDB_CP_COMPACT; 43 | else if (argv[1][1] == 'V' && argv[1][2] == '\0') { 44 | printf("%s\n", MDB_VERSION_STRING); 45 | exit(0); 46 | } else 47 | argc = 0; 48 | } 49 | 50 | if (argc<2 || argc>3) { 51 | fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname); 52 | exit(EXIT_FAILURE); 53 | } 54 | 55 | #ifdef SIGPIPE 56 | signal(SIGPIPE, sighandle); 57 | #endif 58 | #ifdef SIGHUP 59 | signal(SIGHUP, sighandle); 60 | #endif 61 | signal(SIGINT, sighandle); 62 | signal(SIGTERM, sighandle); 63 | 64 | act = "opening environment"; 65 | rc = mdb_env_create(&env); 66 | if (rc == MDB_SUCCESS) { 67 | rc = mdb_env_open(env, argv[1], flags, 0600); 68 | } 69 | if (rc == MDB_SUCCESS) { 70 | act = "copying"; 71 | if (argc == 2) 72 | rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags); 73 | else 74 | rc = mdb_env_copy2(env, argv[2], cpflags); 75 | } 76 | if (rc) 77 | fprintf(stderr, "%s: %s failed, error %d (%s)\n", 78 | progname, act, rc, mdb_strerror(rc)); 79 | mdb_env_close(env); 80 | 81 | return rc ? EXIT_FAILURE : EXIT_SUCCESS; 82 | } 83 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_dump.1: -------------------------------------------------------------------------------- 1 | .TH MDB_DUMP 1 "2014/06/20" "LMDB 0.9.14" 2 | .\" Copyright 2014 Howard Chu, Symas Corp. All Rights Reserved. 3 | .\" Copying restrictions apply. See COPYRIGHT/LICENSE. 4 | .SH NAME 5 | mdb_dump \- LMDB environment export tool 6 | .SH SYNOPSIS 7 | .B mdb_dump 8 | .BR \ envpath 9 | [\c 10 | .BR \-V ] 11 | [\c 12 | .BI \-f \ file\fR] 13 | [\c 14 | .BR \-l ] 15 | [\c 16 | .BR \-n ] 17 | [\c 18 | .BR \-p ] 19 | [\c 20 | .BR \-a \ | 21 | .BI \-s \ subdb\fR] 22 | .SH DESCRIPTION 23 | The 24 | .B mdb_dump 25 | utility reads a database and writes its contents to the 26 | standard output using a portable flat-text format 27 | understood by the 28 | .BR mdb_load (1) 29 | utility. 30 | .SH OPTIONS 31 | .TP 32 | .BR \-V 33 | Write the library version number to the standard output, and exit. 34 | .TP 35 | .BR \-f \ file 36 | Write to the specified file instead of to the standard output. 37 | .TP 38 | .BR \-l 39 | List the databases stored in the environment. Just the 40 | names will be listed, no data will be output. 41 | .TP 42 | .BR \-n 43 | Dump an LMDB database which does not use subdirectories. 44 | .TP 45 | .BR \-p 46 | If characters in either the key or data items are printing characters (as 47 | defined by isprint(3)), output them directly. This option permits users to 48 | use standard text editors and tools to modify the contents of databases. 49 | 50 | Note: different systems may have different notions about what characters 51 | are considered printing characters, and databases dumped in this manner may 52 | be less portable to external systems. 53 | .TP 54 | .BR \-a 55 | Dump all of the subdatabases in the environment. 56 | .TP 57 | .BR \-s \ subdb 58 | Dump a specific subdatabase. If no database is specified, only the main database is dumped. 59 | .SH DIAGNOSTICS 60 | Exit status is zero if no errors occur. 61 | Errors result in a non-zero exit status and 62 | a diagnostic message being written to standard error. 63 | 64 | Dumping and reloading databases that use user-defined comparison functions 65 | will result in new databases that use the default comparison functions. 66 | \fBIn this case it is quite likely that the reloaded database will be 67 | damaged beyond repair permitting neither record storage nor retrieval.\fP 68 | 69 | The only available workaround is to modify the source for the 70 | .BR mdb_load (1) 71 | utility to load the database using the correct comparison functions. 72 | .SH "SEE ALSO" 73 | .BR mdb_load (1) 74 | .SH AUTHOR 75 | Howard Chu of Symas Corporation 76 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_dump.c: -------------------------------------------------------------------------------- 1 | /* mdb_dump.c - memory-mapped database dump tool */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "lmdb.h" 22 | 23 | #ifdef _WIN32 24 | #define Z "I" 25 | #else 26 | #define Z "z" 27 | #endif 28 | 29 | #define PRINT 1 30 | static int mode; 31 | 32 | typedef struct flagbit { 33 | int bit; 34 | char *name; 35 | } flagbit; 36 | 37 | flagbit dbflags[] = { 38 | { MDB_REVERSEKEY, "reversekey" }, 39 | { MDB_DUPSORT, "dupsort" }, 40 | { MDB_INTEGERKEY, "integerkey" }, 41 | { MDB_DUPFIXED, "dupfixed" }, 42 | { MDB_INTEGERDUP, "integerdup" }, 43 | { MDB_REVERSEDUP, "reversedup" }, 44 | { 0, NULL } 45 | }; 46 | 47 | static volatile sig_atomic_t gotsig; 48 | 49 | static void dumpsig( int sig ) 50 | { 51 | gotsig=1; 52 | } 53 | 54 | static const char hexc[] = "0123456789abcdef"; 55 | 56 | static void hex(unsigned char c) 57 | { 58 | putchar(hexc[c >> 4]); 59 | putchar(hexc[c & 0xf]); 60 | } 61 | 62 | static void text(MDB_val *v) 63 | { 64 | unsigned char *c, *end; 65 | 66 | putchar(' '); 67 | c = v->mv_data; 68 | end = c + v->mv_size; 69 | while (c < end) { 70 | if (isprint(*c)) { 71 | putchar(*c); 72 | } else { 73 | putchar('\\'); 74 | hex(*c); 75 | } 76 | c++; 77 | } 78 | putchar('\n'); 79 | } 80 | 81 | static void byte(MDB_val *v) 82 | { 83 | unsigned char *c, *end; 84 | 85 | putchar(' '); 86 | c = v->mv_data; 87 | end = c + v->mv_size; 88 | while (c < end) { 89 | hex(*c++); 90 | } 91 | putchar('\n'); 92 | } 93 | 94 | /* Dump in BDB-compatible format */ 95 | static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name) 96 | { 97 | MDB_cursor *mc; 98 | MDB_stat ms; 99 | MDB_val key, data; 100 | MDB_envinfo info; 101 | unsigned int flags; 102 | int rc, i; 103 | 104 | rc = mdb_dbi_flags(txn, dbi, &flags); 105 | if (rc) return rc; 106 | 107 | rc = mdb_stat(txn, dbi, &ms); 108 | if (rc) return rc; 109 | 110 | rc = mdb_env_info(mdb_txn_env(txn), &info); 111 | if (rc) return rc; 112 | 113 | printf("VERSION=3\n"); 114 | printf("format=%s\n", mode & PRINT ? "print" : "bytevalue"); 115 | if (name) 116 | printf("database=%s\n", name); 117 | printf("type=btree\n"); 118 | printf("mapsize=%" Z "u\n", info.me_mapsize); 119 | if (info.me_mapaddr) 120 | printf("mapaddr=%p\n", info.me_mapaddr); 121 | printf("maxreaders=%u\n", info.me_maxreaders); 122 | 123 | if (flags & MDB_DUPSORT) 124 | printf("duplicates=1\n"); 125 | 126 | for (i=0; dbflags[i].bit; i++) 127 | if (flags & dbflags[i].bit) 128 | printf("%s=1\n", dbflags[i].name); 129 | 130 | printf("db_pagesize=%d\n", ms.ms_psize); 131 | printf("HEADER=END\n"); 132 | 133 | rc = mdb_cursor_open(txn, dbi, &mc); 134 | if (rc) return rc; 135 | 136 | while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT) == MDB_SUCCESS)) { 137 | if (gotsig) { 138 | rc = EINTR; 139 | break; 140 | } 141 | if (mode & PRINT) { 142 | text(&key); 143 | text(&data); 144 | } else { 145 | byte(&key); 146 | byte(&data); 147 | } 148 | } 149 | printf("DATA=END\n"); 150 | if (rc == MDB_NOTFOUND) 151 | rc = MDB_SUCCESS; 152 | 153 | return rc; 154 | } 155 | 156 | static void usage(char *prog) 157 | { 158 | fprintf(stderr, "usage: %s dbpath [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb]\n", prog); 159 | exit(EXIT_FAILURE); 160 | } 161 | 162 | int main(int argc, char *argv[]) 163 | { 164 | int i, rc; 165 | MDB_env *env; 166 | MDB_txn *txn; 167 | MDB_dbi dbi; 168 | char *prog = argv[0]; 169 | char *envname; 170 | char *subname = NULL; 171 | int alldbs = 0, envflags = 0, list = 0; 172 | 173 | if (argc < 2) { 174 | usage(prog); 175 | } 176 | 177 | /* -a: dump main DB and all subDBs 178 | * -s: dump only the named subDB 179 | * -n: use NOSUBDIR flag on env_open 180 | * -p: use printable characters 181 | * -f: write to file instead of stdout 182 | * -V: print version and exit 183 | * (default) dump only the main DB 184 | */ 185 | while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) { 186 | switch(i) { 187 | case 'V': 188 | printf("%s\n", MDB_VERSION_STRING); 189 | exit(0); 190 | break; 191 | case 'l': 192 | list = 1; 193 | /*FALLTHROUGH*/; 194 | case 'a': 195 | if (subname) 196 | usage(prog); 197 | alldbs++; 198 | break; 199 | case 'f': 200 | if (freopen(optarg, "w", stdout) == NULL) { 201 | fprintf(stderr, "%s: %s: reopen: %s\n", 202 | prog, optarg, strerror(errno)); 203 | exit(EXIT_FAILURE); 204 | } 205 | break; 206 | case 'n': 207 | envflags |= MDB_NOSUBDIR; 208 | break; 209 | case 'p': 210 | mode |= PRINT; 211 | break; 212 | case 's': 213 | if (alldbs) 214 | usage(prog); 215 | subname = optarg; 216 | break; 217 | default: 218 | usage(prog); 219 | } 220 | } 221 | 222 | if (optind != argc - 1) 223 | usage(prog); 224 | 225 | #ifdef SIGPIPE 226 | signal(SIGPIPE, dumpsig); 227 | #endif 228 | #ifdef SIGHUP 229 | signal(SIGHUP, dumpsig); 230 | #endif 231 | signal(SIGINT, dumpsig); 232 | signal(SIGTERM, dumpsig); 233 | 234 | envname = argv[optind]; 235 | rc = mdb_env_create(&env); 236 | if (rc) { 237 | fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); 238 | return EXIT_FAILURE; 239 | } 240 | 241 | if (alldbs || subname) { 242 | mdb_env_set_maxdbs(env, 2); 243 | } 244 | 245 | rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664); 246 | if (rc) { 247 | fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); 248 | goto env_close; 249 | } 250 | 251 | rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); 252 | if (rc) { 253 | fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); 254 | goto env_close; 255 | } 256 | 257 | rc = mdb_open(txn, subname, 0, &dbi); 258 | if (rc) { 259 | fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); 260 | goto txn_abort; 261 | } 262 | 263 | if (alldbs) { 264 | MDB_cursor *cursor; 265 | MDB_val key; 266 | int count = 0; 267 | 268 | rc = mdb_cursor_open(txn, dbi, &cursor); 269 | if (rc) { 270 | fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); 271 | goto txn_abort; 272 | } 273 | while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) { 274 | char *str; 275 | MDB_dbi db2; 276 | if (memchr(key.mv_data, '\0', key.mv_size)) 277 | continue; 278 | count++; 279 | str = malloc(key.mv_size+1); 280 | memcpy(str, key.mv_data, key.mv_size); 281 | str[key.mv_size] = '\0'; 282 | rc = mdb_open(txn, str, 0, &db2); 283 | if (rc == MDB_SUCCESS) { 284 | if (list) { 285 | printf("%s\n", str); 286 | list++; 287 | } else { 288 | rc = dumpit(txn, db2, str); 289 | if (rc) 290 | break; 291 | } 292 | mdb_close(env, db2); 293 | } 294 | free(str); 295 | if (rc) continue; 296 | } 297 | mdb_cursor_close(cursor); 298 | if (!count) { 299 | fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname); 300 | rc = MDB_NOTFOUND; 301 | } else if (rc == MDB_NOTFOUND) { 302 | rc = MDB_SUCCESS; 303 | } 304 | } else { 305 | rc = dumpit(txn, dbi, subname); 306 | } 307 | if (rc && rc != MDB_NOTFOUND) 308 | fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc)); 309 | 310 | mdb_close(env, dbi); 311 | txn_abort: 312 | mdb_txn_abort(txn); 313 | env_close: 314 | mdb_env_close(env); 315 | 316 | return rc ? EXIT_FAILURE : EXIT_SUCCESS; 317 | } 318 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_load.1: -------------------------------------------------------------------------------- 1 | .TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14" 2 | .\" Copyright 2014 Howard Chu, Symas Corp. All Rights Reserved. 3 | .\" Copying restrictions apply. See COPYRIGHT/LICENSE. 4 | .SH NAME 5 | mdb_load \- LMDB environment import tool 6 | .SH SYNOPSIS 7 | .B mdb_load 8 | .BR \ envpath 9 | [\c 10 | .BR \-V ] 11 | [\c 12 | .BI \-f \ file\fR] 13 | [\c 14 | .BR \-n ] 15 | [\c 16 | .BI \-s \ subdb\fR] 17 | [\c 18 | .BR \-N ] 19 | [\c 20 | .BR \-T ] 21 | .SH DESCRIPTION 22 | The 23 | .B mdb_load 24 | utility reads from the standard input and loads it into the 25 | LMDB environment 26 | .BR envpath . 27 | 28 | The input to 29 | .B mdb_load 30 | must be in the output format specified by the 31 | .BR mdb_dump (1) 32 | utility or as specified by the 33 | .B -T 34 | option below. 35 | .SH OPTIONS 36 | .TP 37 | .BR \-V 38 | Write the library version number to the standard output, and exit. 39 | .TP 40 | .BR \-f \ file 41 | Read from the specified file instead of from the standard input. 42 | .TP 43 | .BR \-n 44 | Load an LMDB database which does not use subdirectories. 45 | .TP 46 | .BR \-s \ subdb 47 | Load a specific subdatabase. If no database is specified, data is loaded into the main database. 48 | .TP 49 | .BR \-N 50 | Don't overwrite existing records when loading into an already existing database; just skip them. 51 | .TP 52 | .BR \-T 53 | Load data from simple text files. The input must be paired lines of text, where the first 54 | line of the pair is the key item, and the second line of the pair is its corresponding 55 | data item. 56 | 57 | A simple escape mechanism, where newline and backslash (\\) characters are special, is 58 | applied to the text input. Newline characters are interpreted as record separators. 59 | Backslash characters in the text will be interpreted in one of two ways: If the backslash 60 | character precedes another backslash character, the pair will be interpreted as a literal 61 | backslash. If the backslash character precedes any other character, the two characters 62 | following the backslash will be interpreted as a hexadecimal specification of a single 63 | character; for example, \\0a is a newline character in the ASCII character set. 64 | 65 | For this reason, any backslash or newline characters that naturally occur in the text 66 | input must be escaped to avoid misinterpretation by 67 | .BR mdb_load . 68 | 69 | .SH DIAGNOSTICS 70 | Exit status is zero if no errors occur. 71 | Errors result in a non-zero exit status and 72 | a diagnostic message being written to standard error. 73 | 74 | .SH "SEE ALSO" 75 | .BR mdb_dump (1) 76 | .SH AUTHOR 77 | Howard Chu of Symas Corporation 78 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_load.c: -------------------------------------------------------------------------------- 1 | /* mdb_load.c - memory-mapped database load tool */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define PRINT 1 23 | #define NOHDR 2 24 | static int mode; 25 | 26 | static char *subname = NULL; 27 | 28 | static size_t lineno; 29 | static int version; 30 | 31 | static int flags; 32 | 33 | static char *prog; 34 | 35 | static int Eof; 36 | 37 | static MDB_envinfo info; 38 | 39 | static MDB_val kbuf, dbuf; 40 | 41 | #ifdef _WIN32 42 | #define Z "I" 43 | #else 44 | #define Z "z" 45 | #endif 46 | 47 | #define STRLENOF(s) (sizeof(s)-1) 48 | 49 | typedef struct flagbit { 50 | int bit; 51 | char *name; 52 | int len; 53 | } flagbit; 54 | 55 | #define S(s) s, STRLENOF(s) 56 | 57 | flagbit dbflags[] = { 58 | { MDB_REVERSEKEY, S("reversekey") }, 59 | { MDB_DUPSORT, S("dupsort") }, 60 | { MDB_INTEGERKEY, S("integerkey") }, 61 | { MDB_DUPFIXED, S("dupfixed") }, 62 | { MDB_INTEGERDUP, S("integerdup") }, 63 | { MDB_REVERSEDUP, S("reversedup") }, 64 | { 0, NULL, 0 } 65 | }; 66 | 67 | static void readhdr(void) 68 | { 69 | char *ptr; 70 | 71 | while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) { 72 | lineno++; 73 | if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) { 74 | version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION=")); 75 | if (version > 3) { 76 | fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n", 77 | prog, lineno, version); 78 | exit(EXIT_FAILURE); 79 | } 80 | } else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) { 81 | break; 82 | } else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) { 83 | if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print"))) 84 | mode |= PRINT; 85 | else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) { 86 | fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n", 87 | prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT=")); 88 | exit(EXIT_FAILURE); 89 | } 90 | } else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) { 91 | ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); 92 | if (ptr) *ptr = '\0'; 93 | if (subname) free(subname); 94 | subname = strdup((char *)dbuf.mv_data+STRLENOF("database=")); 95 | } else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) { 96 | if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) { 97 | fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n", 98 | prog, lineno, (char *)dbuf.mv_data+STRLENOF("type=")); 99 | exit(EXIT_FAILURE); 100 | } 101 | } else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) { 102 | int i; 103 | ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); 104 | if (ptr) *ptr = '\0'; 105 | i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr); 106 | if (i != 1) { 107 | fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n", 108 | prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr=")); 109 | exit(EXIT_FAILURE); 110 | } 111 | } else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) { 112 | int i; 113 | ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); 114 | if (ptr) *ptr = '\0'; 115 | i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize); 116 | if (i != 1) { 117 | fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n", 118 | prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize=")); 119 | exit(EXIT_FAILURE); 120 | } 121 | } else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) { 122 | int i; 123 | ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); 124 | if (ptr) *ptr = '\0'; 125 | i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders); 126 | if (i != 1) { 127 | fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n", 128 | prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders=")); 129 | exit(EXIT_FAILURE); 130 | } 131 | } else { 132 | int i; 133 | for (i=0; dbflags[i].bit; i++) { 134 | if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) && 135 | ((char *)dbuf.mv_data)[dbflags[i].len] == '=') { 136 | flags |= dbflags[i].bit; 137 | break; 138 | } 139 | } 140 | if (!dbflags[i].bit) { 141 | ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size); 142 | if (!ptr) { 143 | fprintf(stderr, "%s: line %" Z "d: unexpected format\n", 144 | prog, lineno); 145 | exit(EXIT_FAILURE); 146 | } else { 147 | *ptr = '\0'; 148 | fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n", 149 | prog, lineno, (char *)dbuf.mv_data); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | static void badend(void) 157 | { 158 | fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n", 159 | prog, lineno); 160 | } 161 | 162 | static int unhex(unsigned char *c2) 163 | { 164 | int x, c; 165 | x = *c2++ & 0x4f; 166 | if (x & 0x40) 167 | x -= 55; 168 | c = x << 4; 169 | x = *c2 & 0x4f; 170 | if (x & 0x40) 171 | x -= 55; 172 | c |= x; 173 | return c; 174 | } 175 | 176 | static int readline(MDB_val *out, MDB_val *buf) 177 | { 178 | unsigned char *c1, *c2, *end; 179 | size_t len; 180 | int c; 181 | 182 | if (!(mode & NOHDR)) { 183 | c = fgetc(stdin); 184 | if (c == EOF) { 185 | Eof = 1; 186 | return EOF; 187 | } 188 | if (c != ' ') { 189 | lineno++; 190 | if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) { 191 | badend: 192 | Eof = 1; 193 | badend(); 194 | return EOF; 195 | } 196 | if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END"))) 197 | return EOF; 198 | goto badend; 199 | } 200 | } 201 | if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) { 202 | Eof = 1; 203 | return EOF; 204 | } 205 | lineno++; 206 | 207 | c1 = buf->mv_data; 208 | len = strlen((char *)c1); 209 | 210 | /* Is buffer too short? */ 211 | while (c1[len-1] != '\n') { 212 | buf->mv_data = realloc(buf->mv_data, buf->mv_size*2); 213 | if (!buf->mv_data) { 214 | Eof = 1; 215 | fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n", 216 | prog, lineno); 217 | return EOF; 218 | } 219 | c1 = buf->mv_data; 220 | c1 += buf->mv_size; 221 | if (fgets((char *)c1, buf->mv_size, stdin) == NULL) { 222 | Eof = 1; 223 | badend(); 224 | return EOF; 225 | } 226 | buf->mv_size *= 2; 227 | len = strlen((char *)c1); 228 | } 229 | c1 = c2 = buf->mv_data; 230 | len = strlen((char *)c1); 231 | c1[--len] = '\0'; 232 | end = c1 + len; 233 | 234 | if (mode & PRINT) { 235 | while (c2 < end) { 236 | if (*c2 == '\\') { 237 | if (c2[1] == '\\') { 238 | c1++; c2 += 2; 239 | } else { 240 | if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) { 241 | Eof = 1; 242 | badend(); 243 | return EOF; 244 | } 245 | *c1++ = unhex(++c2); 246 | c2 += 2; 247 | } 248 | } else { 249 | c1++; c2++; 250 | } 251 | } 252 | } else { 253 | /* odd length not allowed */ 254 | if (len & 1) { 255 | Eof = 1; 256 | badend(); 257 | return EOF; 258 | } 259 | while (c2 < end) { 260 | if (!isxdigit(*c2) || !isxdigit(c2[1])) { 261 | Eof = 1; 262 | badend(); 263 | return EOF; 264 | } 265 | *c1++ = unhex(c2); 266 | c2 += 2; 267 | } 268 | } 269 | c2 = out->mv_data = buf->mv_data; 270 | out->mv_size = c1 - c2; 271 | 272 | return 0; 273 | } 274 | 275 | static void usage(void) 276 | { 277 | fprintf(stderr, "usage: %s dbpath [-V] [-f input] [-n] [-s name] [-N] [-T]\n", prog); 278 | exit(EXIT_FAILURE); 279 | } 280 | 281 | int main(int argc, char *argv[]) 282 | { 283 | int i, rc; 284 | MDB_env *env; 285 | MDB_txn *txn; 286 | MDB_cursor *mc; 287 | MDB_dbi dbi; 288 | char *envname; 289 | int envflags = 0, putflags = 0; 290 | int dohdr = 0; 291 | 292 | prog = argv[0]; 293 | 294 | if (argc < 2) { 295 | usage(); 296 | } 297 | 298 | /* -f: load file instead of stdin 299 | * -n: use NOSUBDIR flag on env_open 300 | * -s: load into named subDB 301 | * -N: use NOOVERWRITE on puts 302 | * -T: read plaintext 303 | * -V: print version and exit 304 | */ 305 | while ((i = getopt(argc, argv, "f:ns:NTV")) != EOF) { 306 | switch(i) { 307 | case 'V': 308 | printf("%s\n", MDB_VERSION_STRING); 309 | exit(0); 310 | break; 311 | case 'f': 312 | if (freopen(optarg, "r", stdin) == NULL) { 313 | fprintf(stderr, "%s: %s: reopen: %s\n", 314 | prog, optarg, strerror(errno)); 315 | exit(EXIT_FAILURE); 316 | } 317 | break; 318 | case 'n': 319 | envflags |= MDB_NOSUBDIR; 320 | break; 321 | case 's': 322 | subname = strdup(optarg); 323 | break; 324 | case 'N': 325 | putflags = MDB_NOOVERWRITE|MDB_NODUPDATA; 326 | break; 327 | case 'T': 328 | mode |= NOHDR; 329 | break; 330 | default: 331 | usage(); 332 | } 333 | } 334 | 335 | if (optind != argc - 1) 336 | usage(); 337 | 338 | dbuf.mv_size = 4096; 339 | dbuf.mv_data = malloc(dbuf.mv_size); 340 | 341 | if (!(mode & NOHDR)) 342 | readhdr(); 343 | 344 | envname = argv[optind]; 345 | rc = mdb_env_create(&env); 346 | if (rc) { 347 | fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); 348 | return EXIT_FAILURE; 349 | } 350 | 351 | mdb_env_set_maxdbs(env, 2); 352 | 353 | if (info.me_maxreaders) 354 | mdb_env_set_maxreaders(env, info.me_maxreaders); 355 | 356 | if (info.me_mapsize) 357 | mdb_env_set_mapsize(env, info.me_mapsize); 358 | 359 | if (info.me_mapaddr) 360 | envflags |= MDB_FIXEDMAP; 361 | 362 | rc = mdb_env_open(env, envname, envflags, 0664); 363 | if (rc) { 364 | fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); 365 | goto env_close; 366 | } 367 | 368 | kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2; 369 | kbuf.mv_data = malloc(kbuf.mv_size); 370 | 371 | while(!Eof) { 372 | MDB_val key, data; 373 | int batch = 0; 374 | flags = 0; 375 | 376 | if (!dohdr) { 377 | dohdr = 1; 378 | } else if (!(mode & NOHDR)) 379 | readhdr(); 380 | 381 | rc = mdb_txn_begin(env, NULL, 0, &txn); 382 | if (rc) { 383 | fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); 384 | goto env_close; 385 | } 386 | 387 | rc = mdb_open(txn, subname, flags|MDB_CREATE, &dbi); 388 | if (rc) { 389 | fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); 390 | goto txn_abort; 391 | } 392 | 393 | rc = mdb_cursor_open(txn, dbi, &mc); 394 | if (rc) { 395 | fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); 396 | goto txn_abort; 397 | } 398 | 399 | while(1) { 400 | rc = readline(&key, &kbuf); 401 | if (rc == EOF) 402 | break; 403 | if (rc) 404 | goto txn_abort; 405 | 406 | rc = readline(&data, &dbuf); 407 | if (rc) 408 | goto txn_abort; 409 | 410 | rc = mdb_cursor_put(mc, &key, &data, putflags); 411 | if (rc == MDB_KEYEXIST && putflags) 412 | continue; 413 | if (rc) 414 | goto txn_abort; 415 | batch++; 416 | if (batch == 100) { 417 | rc = mdb_txn_commit(txn); 418 | if (rc) { 419 | fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n", 420 | prog, lineno, mdb_strerror(rc)); 421 | goto env_close; 422 | } 423 | rc = mdb_txn_begin(env, NULL, 0, &txn); 424 | if (rc) { 425 | fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); 426 | goto env_close; 427 | } 428 | rc = mdb_cursor_open(txn, dbi, &mc); 429 | if (rc) { 430 | fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); 431 | goto txn_abort; 432 | } 433 | batch = 0; 434 | } 435 | } 436 | rc = mdb_txn_commit(txn); 437 | txn = NULL; 438 | if (rc) { 439 | fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n", 440 | prog, lineno, mdb_strerror(rc)); 441 | goto env_close; 442 | } 443 | mdb_dbi_close(env, dbi); 444 | } 445 | 446 | txn_abort: 447 | mdb_txn_abort(txn); 448 | env_close: 449 | mdb_env_close(env); 450 | 451 | return rc ? EXIT_FAILURE : EXIT_SUCCESS; 452 | } 453 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_stat.1: -------------------------------------------------------------------------------- 1 | .TH MDB_STAT 1 "2014/06/20" "LMDB 0.9.14" 2 | .\" Copyright 2012-2014 Howard Chu, Symas Corp. All Rights Reserved. 3 | .\" Copying restrictions apply. See COPYRIGHT/LICENSE. 4 | .SH NAME 5 | mdb_stat \- LMDB environment status tool 6 | .SH SYNOPSIS 7 | .B mdb_stat 8 | .BR \ envpath 9 | [\c 10 | .BR \-V ] 11 | [\c 12 | .BR \-e ] 13 | [\c 14 | .BR \-f [ f [ f ]]] 15 | [\c 16 | .BR \-n ] 17 | [\c 18 | .BR \-r [ r ]] 19 | [\c 20 | .BR \-a \ | 21 | .BI \-s \ subdb\fR] 22 | .SH DESCRIPTION 23 | The 24 | .B mdb_stat 25 | utility displays the status of an LMDB environment. 26 | .SH OPTIONS 27 | .TP 28 | .BR \-V 29 | Write the library version number to the standard output, and exit. 30 | .TP 31 | .BR \-e 32 | Display information about the database environment. 33 | .TP 34 | .BR \-f 35 | Display information about the environment freelist. 36 | If \fB\-ff\fP is given, summarize each freelist entry. 37 | If \fB\-fff\fP is given, display the full list of page IDs in the freelist. 38 | .TP 39 | .BR \-n 40 | Display the status of an LMDB database which does not use subdirectories. 41 | .TP 42 | .BR \-r 43 | Display information about the environment reader table. 44 | Shows the process ID, thread ID, and transaction ID for each active 45 | reader slot. The process ID and transaction ID are in decimal, the 46 | thread ID is in hexadecimal. The transaction ID is displayed as "-" 47 | if the reader does not currently have a read transaction open. 48 | If \fB\-rr\fP is given, check for stale entries in the reader 49 | table and clear them. The reader table will be printed again 50 | after the check is performed. 51 | .TP 52 | .BR \-a 53 | Display the status of all of the subdatabases in the environment. 54 | .TP 55 | .BR \-s \ subdb 56 | Display the status of a specific subdatabase. 57 | .SH DIAGNOSTICS 58 | Exit status is zero if no errors occur. 59 | Errors result in a non-zero exit status and 60 | a diagnostic message being written to standard error. 61 | .SH "SEE ALSO" 62 | .BR mdb_copy (1) 63 | .SH AUTHOR 64 | Howard Chu of Symas Corporation 65 | -------------------------------------------------------------------------------- /src/liblmdb/mdb_stat.c: -------------------------------------------------------------------------------- 1 | /* mdb_stat.c - memory-mapped database status tool */ 2 | /* 3 | * Copyright 2011-2013 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "lmdb.h" 19 | 20 | #ifdef _WIN32 21 | #define Z "I" 22 | #else 23 | #define Z "z" 24 | #endif 25 | 26 | static void prstat(MDB_stat *ms) 27 | { 28 | #if 0 29 | printf(" Page size: %u\n", ms->ms_psize); 30 | #endif 31 | printf(" Tree depth: %u\n", ms->ms_depth); 32 | printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages); 33 | printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages); 34 | printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages); 35 | printf(" Entries: %"Z"u\n", ms->ms_entries); 36 | } 37 | 38 | static void usage(char *prog) 39 | { 40 | fprintf(stderr, "usage: %s dbpath [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb]\n", prog); 41 | exit(EXIT_FAILURE); 42 | } 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | int i, rc; 47 | MDB_env *env; 48 | MDB_txn *txn; 49 | MDB_dbi dbi; 50 | MDB_stat mst; 51 | MDB_envinfo mei; 52 | char *prog = argv[0]; 53 | char *envname; 54 | char *subname = NULL; 55 | int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0; 56 | 57 | if (argc < 2) { 58 | usage(prog); 59 | } 60 | 61 | /* -a: print stat of main DB and all subDBs 62 | * -s: print stat of only the named subDB 63 | * -e: print env info 64 | * -f: print freelist info 65 | * -r: print reader info 66 | * -n: use NOSUBDIR flag on env_open 67 | * -V: print version and exit 68 | * (default) print stat of only the main DB 69 | */ 70 | while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) { 71 | switch(i) { 72 | case 'V': 73 | printf("%s\n", MDB_VERSION_STRING); 74 | exit(0); 75 | break; 76 | case 'a': 77 | if (subname) 78 | usage(prog); 79 | alldbs++; 80 | break; 81 | case 'e': 82 | envinfo++; 83 | break; 84 | case 'f': 85 | freinfo++; 86 | break; 87 | case 'n': 88 | envflags |= MDB_NOSUBDIR; 89 | break; 90 | case 'r': 91 | rdrinfo++; 92 | break; 93 | case 's': 94 | if (alldbs) 95 | usage(prog); 96 | subname = optarg; 97 | break; 98 | default: 99 | usage(prog); 100 | } 101 | } 102 | 103 | if (optind != argc - 1) 104 | usage(prog); 105 | 106 | envname = argv[optind]; 107 | rc = mdb_env_create(&env); 108 | if (rc) { 109 | fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); 110 | return EXIT_FAILURE; 111 | } 112 | 113 | if (alldbs || subname) { 114 | mdb_env_set_maxdbs(env, 4); 115 | } 116 | 117 | rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664); 118 | if (rc) { 119 | fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); 120 | goto env_close; 121 | } 122 | 123 | if (envinfo) { 124 | (void)mdb_env_stat(env, &mst); 125 | (void)mdb_env_info(env, &mei); 126 | printf("Environment Info\n"); 127 | printf(" Map address: %p\n", mei.me_mapaddr); 128 | printf(" Map size: %"Z"u\n", mei.me_mapsize); 129 | printf(" Page size: %u\n", mst.ms_psize); 130 | printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize); 131 | printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1); 132 | printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid); 133 | printf(" Max readers: %u\n", mei.me_maxreaders); 134 | printf(" Number of readers used: %u\n", mei.me_numreaders); 135 | } 136 | 137 | if (rdrinfo) { 138 | printf("Reader Table Status\n"); 139 | rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout); 140 | if (rdrinfo > 1) { 141 | int dead; 142 | mdb_reader_check(env, &dead); 143 | printf(" %d stale readers cleared.\n", dead); 144 | rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout); 145 | } 146 | if (!(subname || alldbs || freinfo)) 147 | goto env_close; 148 | } 149 | 150 | rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); 151 | if (rc) { 152 | fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); 153 | goto env_close; 154 | } 155 | 156 | if (freinfo) { 157 | MDB_cursor *cursor; 158 | MDB_val key, data; 159 | size_t pages = 0, *iptr; 160 | 161 | printf("Freelist Status\n"); 162 | dbi = 0; 163 | rc = mdb_cursor_open(txn, dbi, &cursor); 164 | if (rc) { 165 | fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); 166 | goto txn_abort; 167 | } 168 | rc = mdb_stat(txn, dbi, &mst); 169 | if (rc) { 170 | fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); 171 | goto txn_abort; 172 | } 173 | prstat(&mst); 174 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 175 | iptr = data.mv_data; 176 | pages += *iptr; 177 | if (freinfo > 1) { 178 | char *bad = ""; 179 | size_t pg, prev; 180 | ssize_t i, j, span = 0; 181 | j = *iptr++; 182 | for (i = j, prev = 1; --i >= 0; ) { 183 | pg = iptr[i]; 184 | if (pg <= prev) 185 | bad = " [bad sequence]"; 186 | prev = pg; 187 | pg += span; 188 | for (; i >= span && iptr[i-span] == pg; span++, pg++) ; 189 | } 190 | printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n", 191 | *(size_t *)key.mv_data, j, span, bad); 192 | if (freinfo > 2) { 193 | for (--j; j >= 0; ) { 194 | pg = iptr[j]; 195 | for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ; 196 | printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n", 197 | pg, span); 198 | } 199 | } 200 | } 201 | } 202 | mdb_cursor_close(cursor); 203 | printf(" Free pages: %"Z"u\n", pages); 204 | } 205 | 206 | rc = mdb_open(txn, subname, 0, &dbi); 207 | if (rc) { 208 | fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); 209 | goto txn_abort; 210 | } 211 | 212 | rc = mdb_stat(txn, dbi, &mst); 213 | if (rc) { 214 | fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); 215 | goto txn_abort; 216 | } 217 | printf("Status of %s\n", subname ? subname : "Main DB"); 218 | prstat(&mst); 219 | 220 | if (alldbs) { 221 | MDB_cursor *cursor; 222 | MDB_val key; 223 | 224 | rc = mdb_cursor_open(txn, dbi, &cursor); 225 | if (rc) { 226 | fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); 227 | goto txn_abort; 228 | } 229 | while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) { 230 | char *str; 231 | MDB_dbi db2; 232 | if (memchr(key.mv_data, '\0', key.mv_size)) 233 | continue; 234 | str = malloc(key.mv_size+1); 235 | memcpy(str, key.mv_data, key.mv_size); 236 | str[key.mv_size] = '\0'; 237 | rc = mdb_open(txn, str, 0, &db2); 238 | if (rc == MDB_SUCCESS) 239 | printf("Status of %s\n", str); 240 | free(str); 241 | if (rc) continue; 242 | rc = mdb_stat(txn, db2, &mst); 243 | if (rc) { 244 | fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); 245 | goto txn_abort; 246 | } 247 | prstat(&mst); 248 | mdb_close(env, db2); 249 | } 250 | mdb_cursor_close(cursor); 251 | } 252 | 253 | if (rc == MDB_NOTFOUND) 254 | rc = MDB_SUCCESS; 255 | 256 | mdb_close(env, dbi); 257 | txn_abort: 258 | mdb_txn_abort(txn); 259 | env_close: 260 | mdb_env_close(env); 261 | 262 | return rc ? EXIT_FAILURE : EXIT_SUCCESS; 263 | } 264 | -------------------------------------------------------------------------------- /src/liblmdb/midl.c: -------------------------------------------------------------------------------- 1 | /** @file midl.c 2 | * @brief ldap bdb back-end ID List functions */ 3 | /* $OpenLDAP$ */ 4 | /* This work is part of OpenLDAP Software . 5 | * 6 | * Copyright 2000-2014 The OpenLDAP Foundation. 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted only as authorized by the OpenLDAP 11 | * Public License. 12 | * 13 | * A copy of this license is available in the file LICENSE in the 14 | * top-level directory of the distribution or, alternatively, at 15 | * . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "midl.h" 24 | 25 | /** @defgroup internal LMDB Internals 26 | * @{ 27 | */ 28 | /** @defgroup idls ID List Management 29 | * @{ 30 | */ 31 | #define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) 32 | 33 | unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ) 34 | { 35 | /* 36 | * binary search of id in ids 37 | * if found, returns position of id 38 | * if not found, returns first position greater than id 39 | */ 40 | unsigned base = 0; 41 | unsigned cursor = 1; 42 | int val = 0; 43 | unsigned n = ids[0]; 44 | 45 | while( 0 < n ) { 46 | unsigned pivot = n >> 1; 47 | cursor = base + pivot + 1; 48 | val = CMP( ids[cursor], id ); 49 | 50 | if( val < 0 ) { 51 | n = pivot; 52 | 53 | } else if ( val > 0 ) { 54 | base = cursor; 55 | n -= pivot + 1; 56 | 57 | } else { 58 | return cursor; 59 | } 60 | } 61 | 62 | if( val > 0 ) { 63 | ++cursor; 64 | } 65 | return cursor; 66 | } 67 | 68 | #if 0 /* superseded by append/sort */ 69 | int mdb_midl_insert( MDB_IDL ids, MDB_ID id ) 70 | { 71 | unsigned x, i; 72 | 73 | x = mdb_midl_search( ids, id ); 74 | assert( x > 0 ); 75 | 76 | if( x < 1 ) { 77 | /* internal error */ 78 | return -2; 79 | } 80 | 81 | if ( x <= ids[0] && ids[x] == id ) { 82 | /* duplicate */ 83 | assert(0); 84 | return -1; 85 | } 86 | 87 | if ( ++ids[0] >= MDB_IDL_DB_MAX ) { 88 | /* no room */ 89 | --ids[0]; 90 | return -2; 91 | 92 | } else { 93 | /* insert id */ 94 | for (i=ids[0]; i>x; i--) 95 | ids[i] = ids[i-1]; 96 | ids[x] = id; 97 | } 98 | 99 | return 0; 100 | } 101 | #endif 102 | 103 | MDB_IDL mdb_midl_alloc(int num) 104 | { 105 | MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID)); 106 | if (ids) { 107 | *ids++ = num; 108 | *ids = 0; 109 | } 110 | return ids; 111 | } 112 | 113 | void mdb_midl_free(MDB_IDL ids) 114 | { 115 | if (ids) 116 | free(ids-1); 117 | } 118 | 119 | int mdb_midl_shrink( MDB_IDL *idp ) 120 | { 121 | MDB_IDL ids = *idp; 122 | if (*(--ids) > MDB_IDL_UM_MAX && 123 | (ids = realloc(ids, (MDB_IDL_UM_MAX+1) * sizeof(MDB_ID)))) 124 | { 125 | *ids++ = MDB_IDL_UM_MAX; 126 | *idp = ids; 127 | return 1; 128 | } 129 | return 0; 130 | } 131 | 132 | static int mdb_midl_grow( MDB_IDL *idp, int num ) 133 | { 134 | MDB_IDL idn = *idp-1; 135 | /* grow it */ 136 | idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID)); 137 | if (!idn) 138 | return ENOMEM; 139 | *idn++ += num; 140 | *idp = idn; 141 | return 0; 142 | } 143 | 144 | int mdb_midl_need( MDB_IDL *idp, unsigned num ) 145 | { 146 | MDB_IDL ids = *idp; 147 | num += ids[0]; 148 | if (num > ids[-1]) { 149 | num = (num + num/4 + (256 + 2)) & -256; 150 | if (!(ids = realloc(ids-1, num * sizeof(MDB_ID)))) 151 | return ENOMEM; 152 | *ids++ = num - 2; 153 | *idp = ids; 154 | } 155 | return 0; 156 | } 157 | 158 | int mdb_midl_append( MDB_IDL *idp, MDB_ID id ) 159 | { 160 | MDB_IDL ids = *idp; 161 | /* Too big? */ 162 | if (ids[0] >= ids[-1]) { 163 | if (mdb_midl_grow(idp, MDB_IDL_UM_MAX)) 164 | return ENOMEM; 165 | ids = *idp; 166 | } 167 | ids[0]++; 168 | ids[ids[0]] = id; 169 | return 0; 170 | } 171 | 172 | int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ) 173 | { 174 | MDB_IDL ids = *idp; 175 | /* Too big? */ 176 | if (ids[0] + app[0] >= ids[-1]) { 177 | if (mdb_midl_grow(idp, app[0])) 178 | return ENOMEM; 179 | ids = *idp; 180 | } 181 | memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID)); 182 | ids[0] += app[0]; 183 | return 0; 184 | } 185 | 186 | int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n ) 187 | { 188 | MDB_ID *ids = *idp, len = ids[0]; 189 | /* Too big? */ 190 | if (len + n > ids[-1]) { 191 | if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX)) 192 | return ENOMEM; 193 | ids = *idp; 194 | } 195 | ids[0] = len + n; 196 | ids += len; 197 | while (n) 198 | ids[n--] = id++; 199 | return 0; 200 | } 201 | 202 | void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge ) 203 | { 204 | MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k; 205 | idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */ 206 | old_id = idl[j]; 207 | while (i) { 208 | merge_id = merge[i--]; 209 | for (; old_id < merge_id; old_id = idl[--j]) 210 | idl[k--] = old_id; 211 | idl[k--] = merge_id; 212 | } 213 | idl[0] = total; 214 | } 215 | 216 | /* Quicksort + Insertion sort for small arrays */ 217 | 218 | #define SMALL 8 219 | #define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; } 220 | 221 | void 222 | mdb_midl_sort( MDB_IDL ids ) 223 | { 224 | /* Max possible depth of int-indexed tree * 2 items/level */ 225 | int istack[sizeof(int)*CHAR_BIT * 2]; 226 | int i,j,k,l,ir,jstack; 227 | MDB_ID a, itmp; 228 | 229 | ir = (int)ids[0]; 230 | l = 1; 231 | jstack = 0; 232 | for(;;) { 233 | if (ir - l < SMALL) { /* Insertion sort */ 234 | for (j=l+1;j<=ir;j++) { 235 | a = ids[j]; 236 | for (i=j-1;i>=1;i--) { 237 | if (ids[i] >= a) break; 238 | ids[i+1] = ids[i]; 239 | } 240 | ids[i+1] = a; 241 | } 242 | if (jstack == 0) break; 243 | ir = istack[jstack--]; 244 | l = istack[jstack--]; 245 | } else { 246 | k = (l + ir) >> 1; /* Choose median of left, center, right */ 247 | MIDL_SWAP(ids[k], ids[l+1]); 248 | if (ids[l] < ids[ir]) { 249 | MIDL_SWAP(ids[l], ids[ir]); 250 | } 251 | if (ids[l+1] < ids[ir]) { 252 | MIDL_SWAP(ids[l+1], ids[ir]); 253 | } 254 | if (ids[l] < ids[l+1]) { 255 | MIDL_SWAP(ids[l], ids[l+1]); 256 | } 257 | i = l+1; 258 | j = ir; 259 | a = ids[l+1]; 260 | for(;;) { 261 | do i++; while(ids[i] > a); 262 | do j--; while(ids[j] < a); 263 | if (j < i) break; 264 | MIDL_SWAP(ids[i],ids[j]); 265 | } 266 | ids[l+1] = ids[j]; 267 | ids[j] = a; 268 | jstack += 2; 269 | if (ir-i+1 >= j-l) { 270 | istack[jstack] = ir; 271 | istack[jstack-1] = i; 272 | ir = j-1; 273 | } else { 274 | istack[jstack] = j-1; 275 | istack[jstack-1] = l; 276 | l = i; 277 | } 278 | } 279 | } 280 | } 281 | 282 | unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ) 283 | { 284 | /* 285 | * binary search of id in ids 286 | * if found, returns position of id 287 | * if not found, returns first position greater than id 288 | */ 289 | unsigned base = 0; 290 | unsigned cursor = 1; 291 | int val = 0; 292 | unsigned n = (unsigned)ids[0].mid; 293 | 294 | while( 0 < n ) { 295 | unsigned pivot = n >> 1; 296 | cursor = base + pivot + 1; 297 | val = CMP( id, ids[cursor].mid ); 298 | 299 | if( val < 0 ) { 300 | n = pivot; 301 | 302 | } else if ( val > 0 ) { 303 | base = cursor; 304 | n -= pivot + 1; 305 | 306 | } else { 307 | return cursor; 308 | } 309 | } 310 | 311 | if( val > 0 ) { 312 | ++cursor; 313 | } 314 | return cursor; 315 | } 316 | 317 | int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ) 318 | { 319 | unsigned x, i; 320 | 321 | x = mdb_mid2l_search( ids, id->mid ); 322 | 323 | if( x < 1 ) { 324 | /* internal error */ 325 | return -2; 326 | } 327 | 328 | if ( x <= ids[0].mid && ids[x].mid == id->mid ) { 329 | /* duplicate */ 330 | return -1; 331 | } 332 | 333 | if ( ids[0].mid >= MDB_IDL_UM_MAX ) { 334 | /* too big */ 335 | return -2; 336 | 337 | } else { 338 | /* insert id */ 339 | ids[0].mid++; 340 | for (i=(unsigned)ids[0].mid; i>x; i--) 341 | ids[i] = ids[i-1]; 342 | ids[x] = *id; 343 | } 344 | 345 | return 0; 346 | } 347 | 348 | int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ) 349 | { 350 | /* Too big? */ 351 | if (ids[0].mid >= MDB_IDL_UM_MAX) { 352 | return -2; 353 | } 354 | ids[0].mid++; 355 | ids[ids[0].mid] = *id; 356 | return 0; 357 | } 358 | 359 | /** @} */ 360 | /** @} */ 361 | -------------------------------------------------------------------------------- /src/liblmdb/midl.h: -------------------------------------------------------------------------------- 1 | /** @file midl.h 2 | * @brief LMDB ID List header file. 3 | * 4 | * This file was originally part of back-bdb but has been 5 | * modified for use in libmdb. Most of the macros defined 6 | * in this file are unused, just left over from the original. 7 | * 8 | * This file is only used internally in libmdb and its definitions 9 | * are not exposed publicly. 10 | */ 11 | /* $OpenLDAP$ */ 12 | /* This work is part of OpenLDAP Software . 13 | * 14 | * Copyright 2000-2014 The OpenLDAP Foundation. 15 | * All rights reserved. 16 | * 17 | * Redistribution and use in source and binary forms, with or without 18 | * modification, are permitted only as authorized by the OpenLDAP 19 | * Public License. 20 | * 21 | * A copy of this license is available in the file LICENSE in the 22 | * top-level directory of the distribution or, alternatively, at 23 | * . 24 | */ 25 | 26 | #ifndef _MDB_MIDL_H_ 27 | #define _MDB_MIDL_H_ 28 | 29 | #include 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | /** @defgroup internal LMDB Internals 36 | * @{ 37 | */ 38 | 39 | /** @defgroup idls ID List Management 40 | * @{ 41 | */ 42 | /** A generic unsigned ID number. These were entryIDs in back-bdb. 43 | * Preferably it should have the same size as a pointer. 44 | */ 45 | typedef size_t MDB_ID; 46 | 47 | /** An IDL is an ID List, a sorted array of IDs. The first 48 | * element of the array is a counter for how many actual 49 | * IDs are in the list. In the original back-bdb code, IDLs are 50 | * sorted in ascending order. For libmdb IDLs are sorted in 51 | * descending order. 52 | */ 53 | typedef MDB_ID *MDB_IDL; 54 | 55 | /* IDL sizes - likely should be even bigger 56 | * limiting factors: sizeof(ID), thread stack size 57 | */ 58 | #define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ 59 | #define MDB_IDL_DB_SIZE (1<. 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include "lmdb.h" 18 | 19 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 20 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 21 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 22 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 23 | 24 | int main(int argc,char * argv[]) 25 | { 26 | int i = 0, j = 0, rc; 27 | MDB_env *env; 28 | MDB_dbi dbi; 29 | MDB_val key, data; 30 | MDB_txn *txn; 31 | MDB_stat mst; 32 | MDB_cursor *cursor, *cur2; 33 | MDB_cursor_op op; 34 | int count; 35 | int *values; 36 | char sval[32] = ""; 37 | 38 | srand(time(NULL)); 39 | 40 | count = (rand()%384) + 64; 41 | values = (int *)malloc(count*sizeof(int)); 42 | 43 | for(i = 0;i -1; i-= (rand()%5)) { 85 | j++; 86 | txn=NULL; 87 | E(mdb_txn_begin(env, NULL, 0, &txn)); 88 | sprintf(sval, "%03x ", values[i]); 89 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) { 90 | j--; 91 | mdb_txn_abort(txn); 92 | } else { 93 | E(mdb_txn_commit(txn)); 94 | } 95 | } 96 | free(values); 97 | printf("Deleted %d values\n", j); 98 | 99 | E(mdb_env_stat(env, &mst)); 100 | E(mdb_txn_begin(env, NULL, 1, &txn)); 101 | E(mdb_cursor_open(txn, dbi, &cursor)); 102 | printf("Cursor next\n"); 103 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 104 | printf("key: %.*s, data: %.*s\n", 105 | (int) key.mv_size, (char *) key.mv_data, 106 | (int) data.mv_size, (char *) data.mv_data); 107 | } 108 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 109 | printf("Cursor last\n"); 110 | E(mdb_cursor_get(cursor, &key, &data, MDB_LAST)); 111 | printf("key: %.*s, data: %.*s\n", 112 | (int) key.mv_size, (char *) key.mv_data, 113 | (int) data.mv_size, (char *) data.mv_data); 114 | printf("Cursor prev\n"); 115 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 116 | printf("key: %.*s, data: %.*s\n", 117 | (int) key.mv_size, (char *) key.mv_data, 118 | (int) data.mv_size, (char *) data.mv_data); 119 | } 120 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 121 | printf("Cursor last/prev\n"); 122 | E(mdb_cursor_get(cursor, &key, &data, MDB_LAST)); 123 | printf("key: %.*s, data: %.*s\n", 124 | (int) key.mv_size, (char *) key.mv_data, 125 | (int) data.mv_size, (char *) data.mv_data); 126 | E(mdb_cursor_get(cursor, &key, &data, MDB_PREV)); 127 | printf("key: %.*s, data: %.*s\n", 128 | (int) key.mv_size, (char *) key.mv_data, 129 | (int) data.mv_size, (char *) data.mv_data); 130 | 131 | mdb_txn_abort(txn); 132 | 133 | printf("Deleting with cursor\n"); 134 | E(mdb_txn_begin(env, NULL, 0, &txn)); 135 | E(mdb_cursor_open(txn, dbi, &cur2)); 136 | for (i=0; i<50; i++) { 137 | if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT))) 138 | break; 139 | printf("key: %p %.*s, data: %p %.*s\n", 140 | key.mv_data, (int) key.mv_size, (char *) key.mv_data, 141 | data.mv_data, (int) data.mv_size, (char *) data.mv_data); 142 | E(mdb_del(txn, dbi, &key, NULL)); 143 | } 144 | 145 | printf("Restarting cursor in txn\n"); 146 | for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) { 147 | if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op))) 148 | break; 149 | printf("key: %p %.*s, data: %p %.*s\n", 150 | key.mv_data, (int) key.mv_size, (char *) key.mv_data, 151 | data.mv_data, (int) data.mv_size, (char *) data.mv_data); 152 | } 153 | mdb_cursor_close(cur2); 154 | E(mdb_txn_commit(txn)); 155 | 156 | printf("Restarting cursor outside txn\n"); 157 | E(mdb_txn_begin(env, NULL, 0, &txn)); 158 | E(mdb_cursor_open(txn, dbi, &cursor)); 159 | for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) { 160 | if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op))) 161 | break; 162 | printf("key: %p %.*s, data: %p %.*s\n", 163 | key.mv_data, (int) key.mv_size, (char *) key.mv_data, 164 | data.mv_data, (int) data.mv_size, (char *) data.mv_data); 165 | } 166 | mdb_cursor_close(cursor); 167 | mdb_close(env, dbi); 168 | 169 | mdb_txn_abort(txn); 170 | mdb_env_close(env); 171 | 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /src/liblmdb/mtest2.c: -------------------------------------------------------------------------------- 1 | /* mtest2.c - memory-mapped database tester/toy */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | 15 | /* Just like mtest.c, but using a subDB instead of the main DB */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 23 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 24 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 25 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 26 | 27 | int main(int argc,char * argv[]) 28 | { 29 | int i = 0, j = 0, rc; 30 | MDB_env *env; 31 | MDB_dbi dbi; 32 | MDB_val key, data; 33 | MDB_txn *txn; 34 | MDB_stat mst; 35 | MDB_cursor *cursor; 36 | int count; 37 | int *values; 38 | char sval[32] = ""; 39 | 40 | srand(time(NULL)); 41 | 42 | count = (rand()%384) + 64; 43 | values = (int *)malloc(count*sizeof(int)); 44 | 45 | for(i = 0;i -1; i-= (rand()%5)) { 85 | j++; 86 | txn=NULL; 87 | E(mdb_txn_begin(env, NULL, 0, &txn)); 88 | sprintf(sval, "%03x ", values[i]); 89 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) { 90 | j--; 91 | mdb_txn_abort(txn); 92 | } else { 93 | E(mdb_txn_commit(txn)); 94 | } 95 | } 96 | free(values); 97 | printf("Deleted %d values\n", j); 98 | 99 | E(mdb_env_stat(env, &mst)); 100 | E(mdb_txn_begin(env, NULL, 1, &txn)); 101 | E(mdb_cursor_open(txn, dbi, &cursor)); 102 | printf("Cursor next\n"); 103 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 104 | printf("key: %.*s, data: %.*s\n", 105 | (int) key.mv_size, (char *) key.mv_data, 106 | (int) data.mv_size, (char *) data.mv_data); 107 | } 108 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 109 | printf("Cursor prev\n"); 110 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 111 | printf("key: %.*s, data: %.*s\n", 112 | (int) key.mv_size, (char *) key.mv_data, 113 | (int) data.mv_size, (char *) data.mv_data); 114 | } 115 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 116 | mdb_cursor_close(cursor); 117 | mdb_close(env, dbi); 118 | 119 | mdb_txn_abort(txn); 120 | mdb_env_close(env); 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /src/liblmdb/mtest3.c: -------------------------------------------------------------------------------- 1 | /* mtest3.c - memory-mapped database tester/toy */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | 15 | /* Tests for sorted duplicate DBs */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 23 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 24 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 25 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 26 | 27 | int main(int argc,char * argv[]) 28 | { 29 | int i = 0, j = 0, rc; 30 | MDB_env *env; 31 | MDB_dbi dbi; 32 | MDB_val key, data; 33 | MDB_txn *txn; 34 | MDB_stat mst; 35 | MDB_cursor *cursor; 36 | int count; 37 | int *values; 38 | char sval[32]; 39 | char kval[sizeof(int)]; 40 | 41 | srand(time(NULL)); 42 | 43 | memset(sval, 0, sizeof(sval)); 44 | 45 | count = (rand()%384) + 64; 46 | values = (int *)malloc(count*sizeof(int)); 47 | 48 | for(i = 0;i -1; i-= (rand()%5)) { 90 | j++; 91 | txn=NULL; 92 | E(mdb_txn_begin(env, NULL, 0, &txn)); 93 | sprintf(kval, "%03x", values[i & ~0x0f]); 94 | sprintf(sval, "%03x %d foo bar", values[i], values[i]); 95 | key.mv_size = sizeof(int); 96 | key.mv_data = kval; 97 | data.mv_size = sizeof(sval); 98 | data.mv_data = sval; 99 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { 100 | j--; 101 | mdb_txn_abort(txn); 102 | } else { 103 | E(mdb_txn_commit(txn)); 104 | } 105 | } 106 | free(values); 107 | printf("Deleted %d values\n", j); 108 | 109 | E(mdb_env_stat(env, &mst)); 110 | E(mdb_txn_begin(env, NULL, 1, &txn)); 111 | E(mdb_cursor_open(txn, dbi, &cursor)); 112 | printf("Cursor next\n"); 113 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 114 | printf("key: %.*s, data: %.*s\n", 115 | (int) key.mv_size, (char *) key.mv_data, 116 | (int) data.mv_size, (char *) data.mv_data); 117 | } 118 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 119 | printf("Cursor prev\n"); 120 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 121 | printf("key: %.*s, data: %.*s\n", 122 | (int) key.mv_size, (char *) key.mv_data, 123 | (int) data.mv_size, (char *) data.mv_data); 124 | } 125 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 126 | mdb_cursor_close(cursor); 127 | mdb_close(env, dbi); 128 | 129 | mdb_txn_abort(txn); 130 | mdb_env_close(env); 131 | 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /src/liblmdb/mtest4.c: -------------------------------------------------------------------------------- 1 | /* mtest4.c - memory-mapped database tester/toy */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | 15 | /* Tests for sorted duplicate DBs with fixed-size keys */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 23 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 24 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 25 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 26 | 27 | int main(int argc,char * argv[]) 28 | { 29 | int i = 0, j = 0, rc; 30 | MDB_env *env; 31 | MDB_dbi dbi; 32 | MDB_val key, data; 33 | MDB_txn *txn; 34 | MDB_stat mst; 35 | MDB_cursor *cursor; 36 | int count; 37 | int *values; 38 | char sval[8]; 39 | char kval[sizeof(int)]; 40 | 41 | memset(sval, 0, sizeof(sval)); 42 | 43 | count = 510; 44 | values = (int *)malloc(count*sizeof(int)); 45 | 46 | for(i = 0;i -1; i-= (rand()%3)) { 126 | j++; 127 | txn=NULL; 128 | E(mdb_txn_begin(env, NULL, 0, &txn)); 129 | sprintf(sval, "%07x", values[i]); 130 | key.mv_size = sizeof(int); 131 | key.mv_data = kval; 132 | data.mv_size = sizeof(sval); 133 | data.mv_data = sval; 134 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { 135 | j--; 136 | mdb_txn_abort(txn); 137 | } else { 138 | E(mdb_txn_commit(txn)); 139 | } 140 | } 141 | free(values); 142 | printf("Deleted %d values\n", j); 143 | 144 | E(mdb_env_stat(env, &mst)); 145 | E(mdb_txn_begin(env, NULL, 1, &txn)); 146 | E(mdb_cursor_open(txn, dbi, &cursor)); 147 | printf("Cursor next\n"); 148 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 149 | printf("key: %.*s, data: %.*s\n", 150 | (int) key.mv_size, (char *) key.mv_data, 151 | (int) data.mv_size, (char *) data.mv_data); 152 | } 153 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 154 | printf("Cursor prev\n"); 155 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 156 | printf("key: %.*s, data: %.*s\n", 157 | (int) key.mv_size, (char *) key.mv_data, 158 | (int) data.mv_size, (char *) data.mv_data); 159 | } 160 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 161 | mdb_cursor_close(cursor); 162 | mdb_close(env, dbi); 163 | 164 | mdb_txn_abort(txn); 165 | mdb_env_close(env); 166 | 167 | return 0; 168 | } 169 | -------------------------------------------------------------------------------- /src/liblmdb/mtest5.c: -------------------------------------------------------------------------------- 1 | /* mtest5.c - memory-mapped database tester/toy */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | 15 | /* Tests for sorted duplicate DBs using cursor_put */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 23 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 24 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 25 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 26 | 27 | int main(int argc,char * argv[]) 28 | { 29 | int i = 0, j = 0, rc; 30 | MDB_env *env; 31 | MDB_dbi dbi; 32 | MDB_val key, data; 33 | MDB_txn *txn; 34 | MDB_stat mst; 35 | MDB_cursor *cursor; 36 | int count; 37 | int *values; 38 | char sval[32]; 39 | char kval[sizeof(int)]; 40 | 41 | srand(time(NULL)); 42 | 43 | memset(sval, 0, sizeof(sval)); 44 | 45 | count = (rand()%384) + 64; 46 | values = (int *)malloc(count*sizeof(int)); 47 | 48 | for(i = 0;i -1; i-= (rand()%5)) { 92 | j++; 93 | txn=NULL; 94 | E(mdb_txn_begin(env, NULL, 0, &txn)); 95 | sprintf(kval, "%03x", values[i & ~0x0f]); 96 | sprintf(sval, "%03x %d foo bar", values[i], values[i]); 97 | key.mv_size = sizeof(int); 98 | key.mv_data = kval; 99 | data.mv_size = sizeof(sval); 100 | data.mv_data = sval; 101 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { 102 | j--; 103 | mdb_txn_abort(txn); 104 | } else { 105 | E(mdb_txn_commit(txn)); 106 | } 107 | } 108 | free(values); 109 | printf("Deleted %d values\n", j); 110 | 111 | E(mdb_env_stat(env, &mst)); 112 | E(mdb_txn_begin(env, NULL, 1, &txn)); 113 | E(mdb_cursor_open(txn, dbi, &cursor)); 114 | printf("Cursor next\n"); 115 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 116 | printf("key: %.*s, data: %.*s\n", 117 | (int) key.mv_size, (char *) key.mv_data, 118 | (int) data.mv_size, (char *) data.mv_data); 119 | } 120 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 121 | printf("Cursor prev\n"); 122 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 123 | printf("key: %.*s, data: %.*s\n", 124 | (int) key.mv_size, (char *) key.mv_data, 125 | (int) data.mv_size, (char *) data.mv_data); 126 | } 127 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 128 | mdb_cursor_close(cursor); 129 | mdb_close(env, dbi); 130 | 131 | mdb_txn_abort(txn); 132 | mdb_env_close(env); 133 | 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /src/liblmdb/mtest6.c: -------------------------------------------------------------------------------- 1 | /* mtest6.c - memory-mapped database tester/toy */ 2 | /* 3 | * Copyright 2011-2014 Howard Chu, Symas Corp. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted only as authorized by the OpenLDAP 8 | * Public License. 9 | * 10 | * A copy of this license is available in the file LICENSE in the 11 | * top-level directory of the distribution or, alternatively, at 12 | * . 13 | */ 14 | 15 | /* Tests for DB splits and merges */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "lmdb.h" 21 | 22 | #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) 23 | #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) 24 | #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ 25 | "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) 26 | 27 | char dkbuf[1024]; 28 | 29 | int main(int argc,char * argv[]) 30 | { 31 | int i = 0, j = 0, rc; 32 | MDB_env *env; 33 | MDB_dbi dbi; 34 | MDB_val key, data; 35 | MDB_txn *txn; 36 | MDB_stat mst; 37 | MDB_cursor *cursor; 38 | int count; 39 | int *values; 40 | long kval; 41 | char *sval; 42 | 43 | srand(time(NULL)); 44 | 45 | E(mdb_env_create(&env)); 46 | E(mdb_env_set_mapsize(env, 10485760)); 47 | E(mdb_env_set_maxdbs(env, 4)); 48 | E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); 49 | E(mdb_txn_begin(env, NULL, 0, &txn)); 50 | E(mdb_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi)); 51 | E(mdb_cursor_open(txn, dbi, &cursor)); 52 | E(mdb_stat(txn, dbi, &mst)); 53 | 54 | sval = calloc(1, mst.ms_psize / 4); 55 | key.mv_size = sizeof(long); 56 | key.mv_data = &kval; 57 | data.mv_size = mst.ms_psize / 4 - 30; 58 | data.mv_data = sval; 59 | 60 | printf("Adding 12 values, should yield 3 splits\n"); 61 | for (i=0;i<12;i++) { 62 | kval = i*5; 63 | sprintf(sval, "%08x", kval); 64 | (void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); 65 | } 66 | printf("Adding 12 more values, should yield 3 splits\n"); 67 | for (i=0;i<12;i++) { 68 | kval = i*5+4; 69 | sprintf(sval, "%08x", kval); 70 | (void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); 71 | } 72 | printf("Adding 12 more values, should yield 3 splits\n"); 73 | for (i=0;i<12;i++) { 74 | kval = i*5+1; 75 | sprintf(sval, "%08x", kval); 76 | (void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); 77 | } 78 | E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST)); 79 | 80 | do { 81 | printf("key: %p %s, data: %p %.*s\n", 82 | key.mv_data, mdb_dkey(&key, dkbuf), 83 | data.mv_data, (int) data.mv_size, (char *) data.mv_data); 84 | } while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0); 85 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 86 | mdb_cursor_close(cursor); 87 | mdb_txn_commit(txn); 88 | 89 | #if 0 90 | j=0; 91 | 92 | for (i= count - 1; i > -1; i-= (rand()%5)) { 93 | j++; 94 | txn=NULL; 95 | E(mdb_txn_begin(env, NULL, 0, &txn)); 96 | sprintf(kval, "%03x", values[i & ~0x0f]); 97 | sprintf(sval, "%03x %d foo bar", values[i], values[i]); 98 | key.mv_size = sizeof(int); 99 | key.mv_data = kval; 100 | data.mv_size = sizeof(sval); 101 | data.mv_data = sval; 102 | if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { 103 | j--; 104 | mdb_txn_abort(txn); 105 | } else { 106 | E(mdb_txn_commit(txn)); 107 | } 108 | } 109 | free(values); 110 | printf("Deleted %d values\n", j); 111 | 112 | E(mdb_env_stat(env, &mst)); 113 | E(mdb_txn_begin(env, NULL, 1, &txn)); 114 | E(mdb_cursor_open(txn, dbi, &cursor)); 115 | printf("Cursor next\n"); 116 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 117 | printf("key: %.*s, data: %.*s\n", 118 | (int) key.mv_size, (char *) key.mv_data, 119 | (int) data.mv_size, (char *) data.mv_data); 120 | } 121 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 122 | printf("Cursor prev\n"); 123 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { 124 | printf("key: %.*s, data: %.*s\n", 125 | (int) key.mv_size, (char *) key.mv_data, 126 | (int) data.mv_size, (char *) data.mv_data); 127 | } 128 | CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); 129 | mdb_cursor_close(cursor); 130 | mdb_close(env, dbi); 131 | 132 | mdb_txn_abort(txn); 133 | #endif 134 | mdb_env_close(env); 135 | 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/liblmdb/sample-bdb.txt: -------------------------------------------------------------------------------- 1 | /* sample-bdb.txt - BerkeleyDB toy/sample 2 | * 3 | * Do a line-by-line comparison of this and sample-mdb.txt 4 | */ 5 | /* 6 | * Copyright 2012 Howard Chu, Symas Corp. 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted only as authorized by the OpenLDAP 11 | * Public License. 12 | * 13 | * A copy of this license is available in the file LICENSE in the 14 | * top-level directory of the distribution or, alternatively, at 15 | * . 16 | */ 17 | #include 18 | #include 19 | #include 20 | 21 | int main(int argc,char * argv[]) 22 | { 23 | int rc; 24 | DB_ENV *env; 25 | DB *dbi; 26 | DBT key, data; 27 | DB_TXN *txn; 28 | DBC *cursor; 29 | char sval[32], kval[32]; 30 | 31 | /* Note: Most error checking omitted for simplicity */ 32 | 33 | #define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD) 34 | rc = db_env_create(&env, 0); 35 | rc = env->open(env, "./testdb", FLAGS, 0664); 36 | rc = db_create(&dbi, env, 0); 37 | rc = env->txn_begin(env, NULL, &txn, 0); 38 | rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664); 39 | 40 | memset(&key, 0, sizeof(DBT)); 41 | memset(&data, 0, sizeof(DBT)); 42 | key.size = sizeof(int); 43 | key.data = sval; 44 | data.size = sizeof(sval); 45 | data.data = sval; 46 | 47 | sprintf(sval, "%03x %d foo bar", 32, 3141592); 48 | rc = dbi->put(dbi, txn, &key, &data, 0); 49 | rc = txn->commit(txn, 0); 50 | if (rc) { 51 | fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc)); 52 | goto leave; 53 | } 54 | rc = env->txn_begin(env, NULL, &txn, 0); 55 | rc = dbi->cursor(dbi, txn, &cursor, 0); 56 | key.flags = DB_DBT_USERMEM; 57 | key.data = kval; 58 | key.ulen = sizeof(kval); 59 | data.flags = DB_DBT_USERMEM; 60 | data.data = sval; 61 | data.ulen = sizeof(sval); 62 | while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) { 63 | printf("key: %p %.*s, data: %p %.*s\n", 64 | key.data, (int) key.size, (char *) key.data, 65 | data.data, (int) data.size, (char *) data.data); 66 | } 67 | rc = cursor->c_close(cursor); 68 | rc = txn->abort(txn); 69 | leave: 70 | rc = dbi->close(dbi, 0); 71 | rc = env->close(env, 0); 72 | return rc; 73 | } 74 | -------------------------------------------------------------------------------- /src/liblmdb/sample-mdb.txt: -------------------------------------------------------------------------------- 1 | /* sample-mdb.txt - MDB toy/sample 2 | * 3 | * Do a line-by-line comparison of this and sample-bdb.txt 4 | */ 5 | /* 6 | * Copyright 2012 Howard Chu, Symas Corp. 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted only as authorized by the OpenLDAP 11 | * Public License. 12 | * 13 | * A copy of this license is available in the file LICENSE in the 14 | * top-level directory of the distribution or, alternatively, at 15 | * . 16 | */ 17 | #include 18 | #include "lmdb.h" 19 | 20 | int main(int argc,char * argv[]) 21 | { 22 | int rc; 23 | MDB_env *env; 24 | MDB_dbi dbi; 25 | MDB_val key, data; 26 | MDB_txn *txn; 27 | MDB_cursor *cursor; 28 | char sval[32]; 29 | 30 | /* Note: Most error checking omitted for simplicity */ 31 | 32 | rc = mdb_env_create(&env); 33 | rc = mdb_env_open(env, "./testdb", 0, 0664); 34 | rc = mdb_txn_begin(env, NULL, 0, &txn); 35 | rc = mdb_open(txn, NULL, 0, &dbi); 36 | 37 | key.mv_size = sizeof(int); 38 | key.mv_data = sval; 39 | data.mv_size = sizeof(sval); 40 | data.mv_data = sval; 41 | 42 | sprintf(sval, "%03x %d foo bar", 32, 3141592); 43 | rc = mdb_put(txn, dbi, &key, &data, 0); 44 | rc = mdb_txn_commit(txn); 45 | if (rc) { 46 | fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc)); 47 | goto leave; 48 | } 49 | rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); 50 | rc = mdb_cursor_open(txn, dbi, &cursor); 51 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 52 | printf("key: %p %.*s, data: %p %.*s\n", 53 | key.mv_data, (int) key.mv_size, (char *) key.mv_data, 54 | data.mv_data, (int) data.mv_size, (char *) data.mv_data); 55 | } 56 | mdb_cursor_close(cursor); 57 | mdb_txn_abort(txn); 58 | leave: 59 | mdb_close(env, dbi); 60 | mdb_env_close(env); 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /src/liblmdb/tooltag: -------------------------------------------------------------------------------- 1 | 2 | 3 | mdb_copy_1 4 | mdb_copy - environment copy tool 5 | mdb_copy.1 6 | 7 | 8 | mdb_dump_1 9 | mdb_dump - environment export tool 10 | mdb_dump.1 11 | 12 | 13 | mdb_load_1 14 | mdb_load - environment import tool 15 | mdb_load.1 16 | 17 | 18 | mdb_stat_1 19 | mdb_stat - environment status tool 20 | mdb_stat.1 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/testLMDB.m: -------------------------------------------------------------------------------- 1 | function testLMDB 2 | %TESTLMDB Test the functionality of LMDB wrapper. 3 | 4 | addpath(fileparts(fileparts(mfilename('fullpath')))); 5 | % Using a database object. 6 | try 7 | test_readonly; 8 | test_operations; 9 | test_cursor; 10 | test_transaction; 11 | test_datatype; 12 | test_dump; 13 | catch exception 14 | disp(exception.getReport()); 15 | end 16 | if exist('_testdb', 'dir') 17 | rmdir('_testdb', 's'); 18 | end 19 | fprintf('DONE\n'); 20 | 21 | end 22 | 23 | function test_operations 24 | disp('Testing operations'); 25 | database = lmdb.DB('_testdb'); 26 | value = database.get('some-key'); 27 | assert(isempty(value)); 28 | database.put('some-key', 'foo'); 29 | database.put('another-key', 'bar'); 30 | database.put('yet-another-key', 'baz'); 31 | database.remove('yet-another-key'); 32 | value = database.get('another-key'); 33 | assert(strcmp(value, 'bar'), 'ASSERTION FAILED: value = %s', value); 34 | database.each(@(key, value) disp([key, ':', value])); 35 | foo_counter = @(key, value, accum) accum + strcmp(value, 'foo'); 36 | foo_count = database.reduce(foo_counter, 0); 37 | assert(foo_count == 1, 'Expected %d, but observed %d\n', 1, foo_count); 38 | end 39 | 40 | function test_readonly 41 | disp('Testing read-only'); 42 | database = lmdb.DB('_testdb'); 43 | clear database; 44 | database = lmdb.DB('_testdb', 'RDONLY', true); 45 | error_raised = false; 46 | try 47 | database.put('key', 'value'); 48 | catch 49 | error_raised = true; 50 | end 51 | assert(error_raised); 52 | end 53 | 54 | function test_transaction 55 | disp('Testing transaction'); 56 | database = lmdb.DB('_testdb'); 57 | % Abort test. 58 | transaction = database.begin(); 59 | for i = 1:1000 60 | transaction.put(num2str(i), 'foo'); 61 | end 62 | transaction.abort(); 63 | clear transaction; 64 | assert(isempty(database.get('1'))); 65 | % Commit test. 66 | transaction = database.begin(); 67 | for i = 1:1000 68 | transaction.put(num2str(i), 'foo'); 69 | end 70 | transaction.commit(); 71 | assert(strcmp(database.get('1'), 'foo')); 72 | clear transaction; 73 | % No transaction. 74 | database.remove('1'); 75 | assert(isempty(database.get('1'))); 76 | disp(database.stat()); 77 | clear database; % Make sure database is not destroyed before transaction. 78 | end 79 | 80 | function test_cursor 81 | disp('Testing cursor'); 82 | database = lmdb.DB('_testdb'); 83 | database.put('some-key', 'foo'); 84 | database.put('another-key', 'bar'); 85 | database.put('yet-another-key', 'baz'); 86 | cursor = database.cursor(); 87 | i = 1; 88 | while cursor.next() 89 | disp([cursor.key, ': ', cursor.value]); 90 | cursor.value = num2str(i); 91 | i = i + 1; 92 | end 93 | clear cursor; 94 | cursor = database.cursor('RDONLY', true); 95 | while cursor.next() 96 | disp([cursor.key, ': ', cursor.value]); 97 | end 98 | assert(cursor.first()); 99 | assert(cursor.last()); 100 | assert(cursor.find('some-key')); 101 | disp([cursor.key, ': ', cursor.value]); 102 | clear cursor; 103 | [key, value] = database.first(); 104 | disp([key, ': ', value]); 105 | clear database; 106 | end 107 | 108 | function test_datatype 109 | disp('Testing data type'); 110 | database = lmdb.DB('_testdb'); 111 | value = uint8(0:255); 112 | database.put('1', value); 113 | value2 = cast(database.get('1'), 'uint8'); 114 | assert(all(value == value2)); 115 | clear database; 116 | end 117 | 118 | function test_dump 119 | disp('Testing dump'); 120 | database = lmdb.DB('_testdb', 'RDONLY', true); 121 | keys = database.keys; 122 | values = database.values; 123 | clear database; 124 | end 125 | --------------------------------------------------------------------------------