lmdb 0.4.8 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +4 -1
- data/.travis.yml +6 -5
- data/CHANGES +42 -0
- data/ext/lmdb_ext/env_flags.h +3 -0
- data/ext/lmdb_ext/errors.h +6 -0
- data/ext/lmdb_ext/lmdb_ext.c +177 -31
- data/ext/lmdb_ext/lmdb_ext.h +6 -2
- data/lib/lmdb/database.rb +117 -4
- data/lib/lmdb/version.rb +1 -1
- data/lmdb.gemspec +3 -3
- data/spec/lmdb_spec.rb +92 -20
- metadata +10 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c02c1c99b143e331bf5fdbae711a57c4845b32ca07c4c041c9c639b96cbb8388
|
4
|
+
data.tar.gz: ce305105acf85f4161e654dc80ad045baeded4e8a8e39f45fed9f8da68f5c2ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1303411d1515bf39a6406540f3a10d9e12c2bd3fea597d17a9e2b949bcf7707244381467a624edd15fada41ac65ae87721c2e42a9ec4dadb5b37e9f8db2a55b0
|
7
|
+
data.tar.gz: aeaa5861dcbde41e9fec9634f6cae749dce6a1348ab9541f90247c2cff60dcb10180701348757a00ba7fc11e93431ef97cfb3064e5face3cc50c704498ca183d
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGES
CHANGED
@@ -1,3 +1,45 @@
|
|
1
|
+
0.5.1
|
2
|
+
|
3
|
+
* Move read-only operations to read-only transactions (djt)
|
4
|
+
|
5
|
+
0.5.0
|
6
|
+
|
7
|
+
* Add jumping to key-value pairs via MDB_GET_BOTH (djt)
|
8
|
+
* Add a number of handy predicates and shortcuts (see docs; djt)
|
9
|
+
|
10
|
+
0.4.8
|
11
|
+
|
12
|
+
* Fix a bug in Database#delete (#38) and add tests.
|
13
|
+
|
14
|
+
0.4.7
|
15
|
+
|
16
|
+
* Fix bug in nested transations (#34).
|
17
|
+
|
18
|
+
0.4.6
|
19
|
+
|
20
|
+
* Import lmdb 0.9.14 source.
|
21
|
+
* Handle MDB_MAP_RESIZED during transaction.
|
22
|
+
* Expose mdb_env_set_mapsize() as Environment#mapsize=.
|
23
|
+
* Cleanup and bug detection in environment_free.
|
24
|
+
* Add accessors:
|
25
|
+
* Transaction#env
|
26
|
+
* Database#env
|
27
|
+
* Cursor#database
|
28
|
+
* Add Cursor#next_range for iterating up to a specified key.
|
29
|
+
|
30
|
+
0.4.5
|
31
|
+
|
32
|
+
* Expand file paths in #copy and #new.
|
33
|
+
* Fix bug: mark cursor list in transaction_mark.
|
34
|
+
|
35
|
+
0.4.4
|
36
|
+
|
37
|
+
* Fix gemspec: permit ruby version >= 2.
|
38
|
+
|
39
|
+
0.4.3
|
40
|
+
|
41
|
+
* Restore 1.9.3 compatibility.
|
42
|
+
|
1
43
|
0.4.2
|
2
44
|
|
3
45
|
* Fix #11, #12, #14.
|
data/ext/lmdb_ext/env_flags.h
CHANGED
data/ext/lmdb_ext/errors.h
CHANGED
data/ext/lmdb_ext/lmdb_ext.c
CHANGED
@@ -38,8 +38,11 @@ static void check(int code) {
|
|
38
38
|
|
39
39
|
static void transaction_free(Transaction* transaction) {
|
40
40
|
if (transaction->txn) {
|
41
|
-
|
42
|
-
|
41
|
+
//int id = (int)mdb_txn_id(transaction->txn);
|
42
|
+
//rb_warn(sprintf("Memory leak: Garbage collecting active transaction %d", id));
|
43
|
+
rb_warn("Memory leak: Garbage collecting active transaction");
|
44
|
+
// transaction_abort(transaction);
|
45
|
+
// mdb_txn_abort(transaction->txn);
|
43
46
|
}
|
44
47
|
free(transaction);
|
45
48
|
}
|
@@ -108,7 +111,7 @@ static VALUE transaction_abort(VALUE self) {
|
|
108
111
|
}
|
109
112
|
|
110
113
|
/**
|
111
|
-
* @overload
|
114
|
+
* @overload env
|
112
115
|
* @return [Environment] the environment in which this transaction is running.
|
113
116
|
* @example
|
114
117
|
* env.transaction do |t|
|
@@ -121,6 +124,18 @@ static VALUE transaction_env(VALUE self) {
|
|
121
124
|
return transaction->env;
|
122
125
|
}
|
123
126
|
|
127
|
+
/**
|
128
|
+
* @overload readonly?
|
129
|
+
* @note This predicate is considered *unstable*; do not get used to it.
|
130
|
+
* @return [false,true] whether the transaction is read-only.
|
131
|
+
*/
|
132
|
+
static VALUE transaction_is_readonly(VALUE self) {
|
133
|
+
TRANSACTION(self, transaction);
|
134
|
+
//MDB_txn* txn = transaction->txn;
|
135
|
+
return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
|
136
|
+
}
|
137
|
+
|
138
|
+
|
124
139
|
static void transaction_finish(VALUE self, int commit) {
|
125
140
|
TRANSACTION(self, transaction);
|
126
141
|
|
@@ -231,9 +246,9 @@ static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flag
|
|
231
246
|
if (txn_args.stop || !txn) {
|
232
247
|
// !txn is when rb_thread_call_without_gvl2
|
233
248
|
// returns before calling txn_begin
|
234
|
-
if (txn)
|
235
|
-
|
236
|
-
|
249
|
+
if (txn) mdb_txn_abort(txn);
|
250
|
+
|
251
|
+
//rb_warn("got here lol");
|
237
252
|
rb_thread_check_ints();
|
238
253
|
goto retry; // in what cases do we get here?
|
239
254
|
}
|
@@ -246,6 +261,7 @@ static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flag
|
|
246
261
|
transaction->parent = environment_active_txn(venv);
|
247
262
|
transaction->env = venv;
|
248
263
|
transaction->txn = txn;
|
264
|
+
transaction->flags = flags;
|
249
265
|
transaction->thread = rb_thread_current();
|
250
266
|
transaction->cursors = rb_ary_new();
|
251
267
|
environment_set_active_txn(venv, transaction->thread, vtxn);
|
@@ -254,6 +270,7 @@ static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flag
|
|
254
270
|
VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
|
255
271
|
|
256
272
|
if (exception) {
|
273
|
+
//rb_warn("lol got exception");
|
257
274
|
if (vtxn == environment_active_txn(venv))
|
258
275
|
transaction_abort(vtxn);
|
259
276
|
rb_jump_tag(exception);
|
@@ -319,6 +336,16 @@ static VALUE stat2hash(const MDB_stat* stat) {
|
|
319
336
|
return ret;
|
320
337
|
}
|
321
338
|
|
339
|
+
static VALUE flags2hash(int flags) {
|
340
|
+
VALUE ret = rb_hash_new();
|
341
|
+
|
342
|
+
#define FLAG(const, name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), (flags & MDB_##const) == 0 ? Qfalse : Qtrue);
|
343
|
+
#include "dbi_flags.h"
|
344
|
+
#undef FLAG
|
345
|
+
|
346
|
+
return ret;
|
347
|
+
}
|
348
|
+
|
322
349
|
/**
|
323
350
|
* @overload stat
|
324
351
|
* Return useful statistics about an environment.
|
@@ -356,7 +383,7 @@ static VALUE environment_info(VALUE self) {
|
|
356
383
|
|
357
384
|
VALUE ret = rb_hash_new();
|
358
385
|
|
359
|
-
#define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)),
|
386
|
+
#define INFO_SET(name) rb_hash_aset(ret, ID2SYM(rb_intern(#name)), SIZET2NUM((size_t)info.me_##name));
|
360
387
|
INFO_SET(mapaddr);
|
361
388
|
INFO_SET(mapsize);
|
362
389
|
INFO_SET(last_pgno);
|
@@ -470,7 +497,13 @@ static int environment_options(VALUE key, VALUE value, EnvironmentOptions* optio
|
|
470
497
|
*/
|
471
498
|
static VALUE environment_new(int argc, VALUE *argv, VALUE klass) {
|
472
499
|
VALUE path, option_hash;
|
500
|
+
|
501
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
502
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
503
|
+
argc, argv, "1:", &path, &option_hash);
|
504
|
+
#else
|
473
505
|
rb_scan_args(argc, argv, "1:", &path, &option_hash);
|
506
|
+
#endif
|
474
507
|
|
475
508
|
EnvironmentOptions options = {
|
476
509
|
.flags = MDB_NOTLS,
|
@@ -745,7 +778,13 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
|
|
745
778
|
return call_with_transaction(self, self, "database", argc, argv, 0);
|
746
779
|
|
747
780
|
VALUE name, option_hash;
|
781
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
782
|
+
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS,
|
783
|
+
argc, argv, "01:", &name, &option_hash);
|
784
|
+
#else
|
748
785
|
rb_scan_args(argc, argv, "01:", &name, &option_hash);
|
786
|
+
#endif
|
787
|
+
|
749
788
|
|
750
789
|
int flags = 0;
|
751
790
|
if (!NIL_P(option_hash))
|
@@ -776,13 +815,63 @@ static VALUE environment_database(int argc, VALUE *argv, VALUE self) {
|
|
776
815
|
static VALUE database_stat(VALUE self) {
|
777
816
|
DATABASE(self, database);
|
778
817
|
if (!active_txn(database->env))
|
779
|
-
return call_with_transaction(database->env,
|
818
|
+
return call_with_transaction(database->env,
|
819
|
+
self, "stat", 0, 0, MDB_RDONLY);
|
780
820
|
|
781
821
|
MDB_stat stat;
|
782
822
|
check(mdb_stat(need_txn(database->env), database->dbi, &stat));
|
783
823
|
return stat2hash(&stat);
|
784
824
|
}
|
785
825
|
|
826
|
+
/**
|
827
|
+
* @overload flags
|
828
|
+
* Return the flags used to open the database.
|
829
|
+
* @return [Hash] The flags.
|
830
|
+
*/
|
831
|
+
static VALUE database_get_flags(VALUE self) {
|
832
|
+
DATABASE(self, database);
|
833
|
+
if (!active_txn(database->env))
|
834
|
+
return call_with_transaction(database->env,
|
835
|
+
self, "flags", 0, 0, MDB_RDONLY);
|
836
|
+
unsigned int flags;
|
837
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
838
|
+
return flags2hash(flags);
|
839
|
+
}
|
840
|
+
|
841
|
+
/* XXX these two could probably also be macro'd, or maybe not i dunno */
|
842
|
+
|
843
|
+
/**
|
844
|
+
* @overload dupsort?
|
845
|
+
* Returns whether the database is in +:dupsort+ mode.
|
846
|
+
* @return [true, false]
|
847
|
+
*/
|
848
|
+
static VALUE database_is_dupsort(VALUE self) {
|
849
|
+
DATABASE(self, database);
|
850
|
+
if (!active_txn(database->env))
|
851
|
+
return call_with_transaction(database->env, self,
|
852
|
+
"dupsort?", 0, 0, MDB_RDONLY);
|
853
|
+
unsigned int flags;
|
854
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
855
|
+
|
856
|
+
return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
|
857
|
+
}
|
858
|
+
|
859
|
+
/**
|
860
|
+
* @overload dupfixed?
|
861
|
+
* Returns whether the database is in +:dupfixed+ mode.
|
862
|
+
* @return [true, false]
|
863
|
+
*/
|
864
|
+
static VALUE database_is_dupfixed(VALUE self) {
|
865
|
+
DATABASE(self, database);
|
866
|
+
if (!active_txn(database->env))
|
867
|
+
return call_with_transaction(database->env, self,
|
868
|
+
"dupfixed?", 0, 0, MDB_RDONLY);
|
869
|
+
unsigned int flags;
|
870
|
+
check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
|
871
|
+
|
872
|
+
return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
|
873
|
+
}
|
874
|
+
|
786
875
|
/**
|
787
876
|
* @overload drop
|
788
877
|
* Remove a database from the environment.
|
@@ -875,8 +964,13 @@ static VALUE database_put(int argc, VALUE *argv, VALUE self) {
|
|
875
964
|
if (!active_txn(database->env))
|
876
965
|
return call_with_transaction(database->env, self, "put", argc, argv, 0);
|
877
966
|
|
878
|
-
VALUE vkey, vval, option_hash;
|
879
|
-
|
967
|
+
VALUE vkey, vval, option_hash = Qnil;
|
968
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
969
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
970
|
+
argc, argv, "20:", &vkey, &vval, &option_hash);
|
971
|
+
#else
|
972
|
+
rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
|
973
|
+
#endif
|
880
974
|
|
881
975
|
int flags = 0;
|
882
976
|
if (!NIL_P(option_hash))
|
@@ -1025,7 +1119,7 @@ static VALUE database_cursor(VALUE self) {
|
|
1025
1119
|
}
|
1026
1120
|
|
1027
1121
|
/**
|
1028
|
-
* @overload
|
1122
|
+
* @overload env
|
1029
1123
|
* @return [Environment] the environment to which this database belongs.
|
1030
1124
|
*/
|
1031
1125
|
static VALUE database_env(VALUE self) {
|
@@ -1082,21 +1176,30 @@ static VALUE cursor_prev(VALUE self) {
|
|
1082
1176
|
}
|
1083
1177
|
|
1084
1178
|
/**
|
1085
|
-
* @overload next
|
1179
|
+
* @overload next nodup = nil
|
1086
1180
|
* Position the cursor to the next record in the database, and
|
1087
1181
|
* return its value.
|
1182
|
+
* @param nodup [true, false] If true, skip over duplicate records.
|
1088
1183
|
* @return [Array,nil] The [key, value] pair for the next record, or
|
1089
1184
|
* nil if no next record.
|
1090
1185
|
*/
|
1091
|
-
static VALUE cursor_next(VALUE self) {
|
1186
|
+
static VALUE cursor_next(int argc, VALUE* argv, VALUE self) {
|
1092
1187
|
CURSOR(self, cursor);
|
1188
|
+
VALUE nodup;
|
1093
1189
|
MDB_val key, value;
|
1190
|
+
MDB_cursor_op op = MDB_NEXT;
|
1094
1191
|
|
1095
|
-
|
1192
|
+
rb_scan_args(argc, argv, "01", &nodup);
|
1193
|
+
|
1194
|
+
if (RTEST(nodup))
|
1195
|
+
op = MDB_NEXT_NODUP;
|
1196
|
+
|
1197
|
+
int ret = mdb_cursor_get(cursor->cur, &key, &value, op);
|
1096
1198
|
if (ret == MDB_NOTFOUND)
|
1097
1199
|
return Qnil;
|
1098
1200
|
check(ret);
|
1099
|
-
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1201
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1202
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1100
1203
|
}
|
1101
1204
|
|
1102
1205
|
/**
|
@@ -1104,6 +1207,7 @@ static VALUE cursor_next(VALUE self) {
|
|
1104
1207
|
* Position the cursor to the next record in the database, and
|
1105
1208
|
* return its value if the record's key is less than or equal to
|
1106
1209
|
* the specified key, or nil otherwise.
|
1210
|
+
* @param key [#to_s] The key to serve as the upper bound
|
1107
1211
|
* @return [Array,nil] The [key, value] pair for the next record, or
|
1108
1212
|
* nil if no next record or the next record is out of the range.
|
1109
1213
|
*/
|
@@ -1119,7 +1223,7 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
|
|
1119
1223
|
ub_key.mv_size = RSTRING_LEN(upper_bound_key);
|
1120
1224
|
ub_key.mv_data = StringValuePtr(upper_bound_key);
|
1121
1225
|
|
1122
|
-
MDB_txn
|
1226
|
+
MDB_txn* txn = mdb_cursor_txn(cursor->cur);
|
1123
1227
|
MDB_dbi dbi = mdb_cursor_dbi(cursor->cur);
|
1124
1228
|
|
1125
1229
|
if (mdb_cmp(txn, dbi, &key, &ub_key) <= 0) {
|
@@ -1130,20 +1234,40 @@ static VALUE cursor_next_range(VALUE self, VALUE upper_bound_key) {
|
|
1130
1234
|
}
|
1131
1235
|
|
1132
1236
|
/**
|
1133
|
-
* @overload set(key)
|
1134
|
-
* Set the cursor to a specified key
|
1135
|
-
*
|
1136
|
-
* @
|
1237
|
+
* @overload set(key, value = nil)
|
1238
|
+
* Set the cursor to a specified key, optionally at the specified
|
1239
|
+
* value if the database was opened with +:dupsort+.
|
1240
|
+
* @param key [#to_s] The key to which the cursor should be positioned
|
1241
|
+
* @param value [nil, #to_s] The optional value (+:dupsort+ only)
|
1242
|
+
* @return [Array] The +[key, value]+ pair to which the cursor now points.
|
1137
1243
|
*/
|
1138
|
-
static VALUE cursor_set(VALUE
|
1139
|
-
|
1140
|
-
|
1244
|
+
static VALUE cursor_set(int argc, VALUE* argv, VALUE self) {
|
1245
|
+
CURSOR(self, cursor);
|
1246
|
+
VALUE vkey, vval;
|
1247
|
+
MDB_val key, value;
|
1248
|
+
MDB_cursor_op op = MDB_SET_KEY;
|
1249
|
+
int ret;
|
1141
1250
|
|
1142
|
-
|
1143
|
-
key.mv_data = StringValuePtr(vkey);
|
1251
|
+
rb_scan_args(argc, argv, "11", &vkey, &vval);
|
1144
1252
|
|
1145
|
-
|
1146
|
-
|
1253
|
+
key.mv_size = RSTRING_LEN(vkey);
|
1254
|
+
key.mv_data = StringValuePtr(vkey);
|
1255
|
+
|
1256
|
+
if (!NIL_P(vval)) {
|
1257
|
+
op = MDB_GET_BOTH;
|
1258
|
+
value.mv_size = RSTRING_LEN(vval);
|
1259
|
+
value.mv_data = StringValuePtr(vval);
|
1260
|
+
}
|
1261
|
+
|
1262
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, op);
|
1263
|
+
|
1264
|
+
if (!NIL_P(vval) && ret == MDB_NOTFOUND)
|
1265
|
+
return Qnil;
|
1266
|
+
|
1267
|
+
check(ret);
|
1268
|
+
|
1269
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1270
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1147
1271
|
}
|
1148
1272
|
|
1149
1273
|
/**
|
@@ -1155,12 +1279,20 @@ static VALUE cursor_set(VALUE self, VALUE vkey) {
|
|
1155
1279
|
static VALUE cursor_set_range(VALUE self, VALUE vkey) {
|
1156
1280
|
CURSOR(self, cursor);
|
1157
1281
|
MDB_val key, value;
|
1282
|
+
int ret;
|
1158
1283
|
|
1159
1284
|
key.mv_size = RSTRING_LEN(vkey);
|
1160
1285
|
key.mv_data = StringValuePtr(vkey);
|
1161
1286
|
|
1162
|
-
|
1163
|
-
|
1287
|
+
ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE);
|
1288
|
+
|
1289
|
+
/* not sure why we were letting this throw an exception */
|
1290
|
+
if (ret == MDB_NOTFOUND) return Qnil;
|
1291
|
+
|
1292
|
+
check(ret);
|
1293
|
+
|
1294
|
+
return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size),
|
1295
|
+
rb_str_new(value.mv_data, value.mv_size));
|
1164
1296
|
}
|
1165
1297
|
|
1166
1298
|
/**
|
@@ -1221,7 +1353,12 @@ static VALUE cursor_put(int argc, VALUE* argv, VALUE self) {
|
|
1221
1353
|
CURSOR(self, cursor);
|
1222
1354
|
|
1223
1355
|
VALUE vkey, vval, option_hash;
|
1356
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
1357
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
1358
|
+
argc, argv, "2:", &vkey, &vval, &option_hash);
|
1359
|
+
#else
|
1224
1360
|
rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
|
1361
|
+
#endif
|
1225
1362
|
|
1226
1363
|
int flags = 0;
|
1227
1364
|
if (!NIL_P(option_hash))
|
@@ -1258,7 +1395,12 @@ static VALUE cursor_delete(int argc, VALUE *argv, VALUE self) {
|
|
1258
1395
|
CURSOR(self, cursor);
|
1259
1396
|
|
1260
1397
|
VALUE option_hash;
|
1398
|
+
#ifdef RB_SCAN_ARGS_KEYWORDS
|
1399
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
|
1400
|
+
argc, argv, ":", &option_hash);
|
1401
|
+
#else
|
1261
1402
|
rb_scan_args(argc, argv, ":", &option_hash);
|
1403
|
+
#endif
|
1262
1404
|
|
1263
1405
|
int flags = 0;
|
1264
1406
|
if (!NIL_P(option_hash))
|
@@ -1388,6 +1530,9 @@ void Init_lmdb_ext() {
|
|
1388
1530
|
cDatabase = rb_define_class_under(mLMDB, "Database", rb_cObject);
|
1389
1531
|
rb_undef_method(rb_singleton_class(cDatabase), "new");
|
1390
1532
|
rb_define_method(cDatabase, "stat", database_stat, 0);
|
1533
|
+
rb_define_method(cDatabase, "flags", database_get_flags, 0);
|
1534
|
+
rb_define_method(cDatabase, "dupsort?", database_is_dupsort, 0);
|
1535
|
+
rb_define_method(cDatabase, "dupfixed?", database_is_dupfixed, 0);
|
1391
1536
|
rb_define_method(cDatabase, "drop", database_drop, 0);
|
1392
1537
|
rb_define_method(cDatabase, "clear", database_clear, 0);
|
1393
1538
|
rb_define_method(cDatabase, "get", database_get, 1);
|
@@ -1458,6 +1603,7 @@ void Init_lmdb_ext() {
|
|
1458
1603
|
rb_define_method(cTransaction, "commit", transaction_commit, 0);
|
1459
1604
|
rb_define_method(cTransaction, "abort", transaction_abort, 0);
|
1460
1605
|
rb_define_method(cTransaction, "env", transaction_env, 0);
|
1606
|
+
rb_define_method(cTransaction, "readonly?", transaction_is_readonly, 0);
|
1461
1607
|
|
1462
1608
|
/**
|
1463
1609
|
* Document-class: LMDB::Cursor
|
@@ -1491,10 +1637,10 @@ void Init_lmdb_ext() {
|
|
1491
1637
|
rb_define_method(cCursor, "get", cursor_get, 0);
|
1492
1638
|
rb_define_method(cCursor, "first", cursor_first, 0);
|
1493
1639
|
rb_define_method(cCursor, "last", cursor_last, 0);
|
1494
|
-
rb_define_method(cCursor, "next", cursor_next,
|
1640
|
+
rb_define_method(cCursor, "next", cursor_next, -1);
|
1495
1641
|
rb_define_method(cCursor, "next_range", cursor_next_range, 1);
|
1496
1642
|
rb_define_method(cCursor, "prev", cursor_prev, 0);
|
1497
|
-
rb_define_method(cCursor, "set", cursor_set, 1);
|
1643
|
+
rb_define_method(cCursor, "set", cursor_set, -1);
|
1498
1644
|
rb_define_method(cCursor, "set_range", cursor_set_range, 1);
|
1499
1645
|
rb_define_method(cCursor, "put", cursor_put, -1);
|
1500
1646
|
rb_define_method(cCursor, "count", cursor_count, 0);
|
data/ext/lmdb_ext/lmdb_ext.h
CHANGED
@@ -53,6 +53,7 @@ typedef struct {
|
|
53
53
|
VALUE thread;
|
54
54
|
VALUE cursors;
|
55
55
|
MDB_txn* txn;
|
56
|
+
unsigned int flags;
|
56
57
|
} Transaction;
|
57
58
|
|
58
59
|
typedef struct {
|
@@ -116,10 +117,10 @@ static void cursor_free(Cursor* cursor);
|
|
116
117
|
static VALUE cursor_get(VALUE self);
|
117
118
|
static VALUE cursor_last(VALUE self);
|
118
119
|
static void cursor_mark(Cursor* cursor);
|
119
|
-
static VALUE cursor_next(VALUE self);
|
120
|
+
static VALUE cursor_next(int argc, VALUE* argv, VALUE self);
|
120
121
|
static VALUE cursor_prev(VALUE self);
|
121
122
|
static VALUE cursor_put(int argc, VALUE* argv, VALUE self);
|
122
|
-
static VALUE cursor_set(VALUE
|
123
|
+
static VALUE cursor_set(int argc, VALUE* argv, VALUE self);
|
123
124
|
static VALUE cursor_set_range(VALUE self, VALUE vkey);
|
124
125
|
static VALUE database_clear(VALUE self);
|
125
126
|
static VALUE database_cursor(VALUE self);
|
@@ -129,6 +130,9 @@ static VALUE database_get(VALUE self, VALUE vkey);
|
|
129
130
|
static void database_mark(Database* database);
|
130
131
|
static VALUE database_put(int argc, VALUE *argv, VALUE self);
|
131
132
|
static VALUE database_stat(VALUE self);
|
133
|
+
static VALUE database_get_flags(VALUE self);
|
134
|
+
static VALUE database_is_dupsort(VALUE self);
|
135
|
+
static VALUE database_is_dupfixed(VALUE self);
|
132
136
|
static VALUE environment_active_txn(VALUE self);
|
133
137
|
static VALUE environment_change_flags(int argc, VALUE* argv, VALUE self, int set);
|
134
138
|
static void environment_check(Environment* environment);
|
data/lib/lmdb/database.rb
CHANGED
@@ -11,9 +11,12 @@ module LMDB
|
|
11
11
|
# puts "at #{key}: #{value}"
|
12
12
|
# end
|
13
13
|
def each
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# maybe_txn true do
|
15
|
+
env.transaction do
|
16
|
+
cursor do |c|
|
17
|
+
while i = c.next
|
18
|
+
yield(i)
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -37,13 +40,123 @@ module LMDB
|
|
37
40
|
# db['b'] = 1234 #=> 1234
|
38
41
|
# db['a'] #=> 'b'
|
39
42
|
def []=(key, value)
|
40
|
-
put
|
43
|
+
put key, value
|
41
44
|
value
|
42
45
|
end
|
43
46
|
|
47
|
+
# Get the keys as an array.
|
48
|
+
# @return [Array] of keys.
|
49
|
+
def keys
|
50
|
+
each_key.to_a
|
51
|
+
end
|
52
|
+
|
53
|
+
# Iterate over each key in the database, skipping over duplicate records.
|
54
|
+
#
|
55
|
+
# @yield key [String] the next key in the database.
|
56
|
+
# @return [Enumerator] in lieu of a block.
|
57
|
+
def each_key(&block)
|
58
|
+
return enum_for :each_key unless block_given?
|
59
|
+
# maybe_txn true do
|
60
|
+
env.transaction do
|
61
|
+
cursor do |c|
|
62
|
+
while (rec = c.next true)
|
63
|
+
yield rec.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Iterate over the duplicate values of a given key, using an
|
70
|
+
# implicit cursor. Works whether +:dupsort+ is set or not.
|
71
|
+
#
|
72
|
+
# @param key [#to_s] The key in question.
|
73
|
+
# @yield value [String] the next value associated with the key.
|
74
|
+
# @return [Enumerator] in lieu of a block.
|
75
|
+
def each_value(key, &block)
|
76
|
+
return enum_for :each_value, key unless block_given?
|
77
|
+
|
78
|
+
value = get(key) or return
|
79
|
+
unless dupsort?
|
80
|
+
yield value
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
#maybe_txn true do
|
85
|
+
env.transaction do
|
86
|
+
cursor do |c|
|
87
|
+
method = :set
|
88
|
+
while rec = c.send(method, key)
|
89
|
+
method = :next_range
|
90
|
+
yield rec[1]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return the cardinality (number of duplicates) of a given
|
97
|
+
# key. Works whether +:dupsort+ is set or not.
|
98
|
+
# @param key [#to_s] The key in question.
|
99
|
+
# @return [Integer] The number of entries under the key.
|
100
|
+
def cardinality(key)
|
101
|
+
ret = 0
|
102
|
+
maybe_txn true do
|
103
|
+
# env.transaction do
|
104
|
+
if get key
|
105
|
+
if dupsort?
|
106
|
+
cursor do |c|
|
107
|
+
c.set key
|
108
|
+
ret = c.count
|
109
|
+
end
|
110
|
+
else
|
111
|
+
ret = 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
ret
|
116
|
+
end
|
117
|
+
|
118
|
+
# Test if the database has a given key (or, if opened in
|
119
|
+
# +:dupsort+, value)
|
120
|
+
def has?(key, value = nil)
|
121
|
+
v = get(key) or return false
|
122
|
+
return true if value.nil? or value.to_s == v
|
123
|
+
return false unless dupsort?
|
124
|
+
|
125
|
+
ret = false
|
126
|
+
# read-only txn was having trouble being nested inside a read-write
|
127
|
+
#maybe_txn true do
|
128
|
+
env.transaction do
|
129
|
+
cursor { |c| ret = !!c.set(key, value) }
|
130
|
+
end
|
131
|
+
ret
|
132
|
+
end
|
133
|
+
|
134
|
+
# Delete the key (and optional value pair) if it exists; do not
|
135
|
+
# complain about missing keys.
|
136
|
+
# @param key [#to_s] The key.
|
137
|
+
# @param value [#to_s] The optional value.
|
138
|
+
def delete?(key, value = nil)
|
139
|
+
delete key, value if has? key, value
|
140
|
+
end
|
141
|
+
|
44
142
|
# @return the number of records in this database
|
45
143
|
def size
|
46
144
|
stat[:entries]
|
47
145
|
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# having trouble with read-only transactions embedded in
|
150
|
+
# read-write for some reason; can't pin it down to test it yet so
|
151
|
+
# going to do this (djt; 2020-02-10)
|
152
|
+
def maybe_txn(readonly, &block)
|
153
|
+
if t = env.active_txn
|
154
|
+
yield t
|
155
|
+
else
|
156
|
+
env.transaction !!readonly do |t|
|
157
|
+
yield t
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
48
161
|
end
|
49
162
|
end
|
data/lib/lmdb/version.rb
CHANGED
data/lmdb.gemspec
CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
20
20
|
s.require_paths = ['lib']
|
21
21
|
|
22
|
-
s.required_ruby_version = ">=
|
23
|
-
s.add_development_dependency 'rake', "~>
|
24
|
-
s.add_development_dependency 'rake-compiler', '
|
22
|
+
s.required_ruby_version = ">= 2.4"
|
23
|
+
s.add_development_dependency 'rake', "~> 13.0"
|
24
|
+
s.add_development_dependency 'rake-compiler', '~> 1.1'
|
25
25
|
s.add_development_dependency 'rspec', "~> 3.0"
|
26
26
|
end
|
data/spec/lmdb_spec.rb
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
require 'helper'
|
3
3
|
|
4
4
|
describe LMDB do
|
5
|
-
let(:env) { LMDB.new(path) }
|
5
|
+
let(:env) { LMDB.new(path, mapsize: 2**20) }
|
6
6
|
after { env.close rescue nil }
|
7
7
|
|
8
8
|
let(:db) { env.database }
|
9
9
|
|
10
10
|
it 'has version constants' do
|
11
|
-
LMDB::LIB_VERSION_MAJOR.should be_instance_of(
|
12
|
-
LMDB::LIB_VERSION_MINOR.should be_instance_of(
|
13
|
-
LMDB::LIB_VERSION_PATCH.should be_instance_of(
|
11
|
+
LMDB::LIB_VERSION_MAJOR.should be_instance_of(Integer)
|
12
|
+
LMDB::LIB_VERSION_MINOR.should be_instance_of(Integer)
|
13
|
+
LMDB::LIB_VERSION_PATCH.should be_instance_of(Integer)
|
14
14
|
LMDB::LIB_VERSION.should be_instance_of(String)
|
15
15
|
LMDB::VERSION.should be_instance_of(String)
|
16
16
|
end
|
@@ -37,7 +37,8 @@ describe LMDB do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'accepts options' do
|
40
|
-
env = LMDB::Environment.new(path, :
|
40
|
+
env = LMDB::Environment.new(path, nosync: true, mode: 0777,
|
41
|
+
maxreaders: 777, mapsize: 111111, maxdbs: 666)
|
41
42
|
env.should be_instance_of(described_class)
|
42
43
|
env.info[:maxreaders].should == 777
|
43
44
|
env.info[:mapsize].should == 111111
|
@@ -52,22 +53,22 @@ describe LMDB do
|
|
52
53
|
|
53
54
|
it 'should return stat' do
|
54
55
|
stat = env.stat
|
55
|
-
stat[:psize].should be_instance_of(
|
56
|
-
stat[:depth].should be_instance_of(
|
57
|
-
stat[:branch_pages].should be_instance_of(
|
58
|
-
stat[:leaf_pages].should be_instance_of(
|
59
|
-
stat[:overflow_pages].should be_instance_of(
|
60
|
-
stat[:entries].should be_instance_of(
|
56
|
+
stat[:psize].should be_instance_of(Integer)
|
57
|
+
stat[:depth].should be_instance_of(Integer)
|
58
|
+
stat[:branch_pages].should be_instance_of(Integer)
|
59
|
+
stat[:leaf_pages].should be_instance_of(Integer)
|
60
|
+
stat[:overflow_pages].should be_instance_of(Integer)
|
61
|
+
stat[:entries].should be_instance_of(Integer)
|
61
62
|
end
|
62
63
|
|
63
64
|
it 'should return info' do
|
64
65
|
info = env.info
|
65
|
-
info[:mapaddr].should be_instance_of(
|
66
|
-
info[:mapsize].should be_instance_of(
|
67
|
-
info[:last_pgno].should be_instance_of(
|
68
|
-
info[:last_txnid].should be_instance_of(
|
69
|
-
info[:maxreaders].should be_instance_of(
|
70
|
-
info[:numreaders].should be_instance_of(
|
66
|
+
info[:mapaddr].should be_instance_of(Integer)
|
67
|
+
info[:mapsize].should be_instance_of(Integer)
|
68
|
+
info[:last_pgno].should be_instance_of(Integer)
|
69
|
+
info[:last_txnid].should be_instance_of(Integer)
|
70
|
+
info[:maxreaders].should be_instance_of(Integer)
|
71
|
+
info[:numreaders].should be_instance_of(Integer)
|
71
72
|
end
|
72
73
|
|
73
74
|
it 'should set mapsize' do
|
@@ -183,10 +184,18 @@ describe LMDB do
|
|
183
184
|
describe LMDB::Database do
|
184
185
|
subject { db }
|
185
186
|
|
187
|
+
it 'should return flags' do
|
188
|
+
subject.flags.should be_instance_of(Hash)
|
189
|
+
subject.dupsort?.should == false
|
190
|
+
subject.dupfixed?.should == false
|
191
|
+
end
|
192
|
+
|
186
193
|
it 'should support named databases' do
|
187
194
|
main = env.database
|
188
|
-
|
189
|
-
|
195
|
+
# funnily it complains in 2.7 unless i do this
|
196
|
+
dbopts = { create: true }
|
197
|
+
db1 = env.database 'db1', create: true # actually no it doesn't wtf
|
198
|
+
db2 = env.database 'db2', **dbopts
|
190
199
|
|
191
200
|
main['key'] = '1'
|
192
201
|
db1['key'] = '2'
|
@@ -201,6 +210,10 @@ describe LMDB do
|
|
201
210
|
subject.get('cat').should be_nil
|
202
211
|
subject.put('cat', 'garfield').should be_nil
|
203
212
|
subject.get('cat').should == 'garfield'
|
213
|
+
|
214
|
+
# check for key-value pairs on non-dupsort database
|
215
|
+
subject.has?('cat', 'garfield').should == true
|
216
|
+
subject.has?('cat', 'heathcliff').should == false
|
204
217
|
end
|
205
218
|
|
206
219
|
it 'should delete by key' do
|
@@ -214,6 +227,9 @@ describe LMDB do
|
|
214
227
|
subject.put('cat', 'garfield')
|
215
228
|
subject.delete('cat', 'garfield').should be_nil
|
216
229
|
proc { subject.delete('cat', 'garfield') }.should raise_error(LMDB::Error::NOTFOUND)
|
230
|
+
|
231
|
+
# soft delete
|
232
|
+
subject.delete?('cat', 'heathcliff').should be_nil
|
217
233
|
end
|
218
234
|
|
219
235
|
it 'stores key/values in same transaction' do
|
@@ -273,10 +289,16 @@ describe LMDB do
|
|
273
289
|
|
274
290
|
it 'should get environment' do
|
275
291
|
main = env.database
|
276
|
-
db1 = env.database('db1', :
|
292
|
+
db1 = env.database('db1', create: true)
|
277
293
|
main.env.should == env
|
278
294
|
db1.env.should == env
|
279
295
|
end
|
296
|
+
|
297
|
+
it 'should iterate over/list keys' do
|
298
|
+
db['k1'] = 'v1'
|
299
|
+
db['k2'] = 'v2'
|
300
|
+
db.keys.sort.should == %w[k1 k2]
|
301
|
+
end
|
280
302
|
end
|
281
303
|
|
282
304
|
describe LMDB::Cursor do
|
@@ -331,6 +353,49 @@ describe LMDB do
|
|
331
353
|
end
|
332
354
|
end
|
333
355
|
|
356
|
+
it 'should set to a key-value pair when db is dupsort' do
|
357
|
+
dupdb = env.database 'dupsort', create: true, dupsort: true
|
358
|
+
|
359
|
+
# check flag while we're at it
|
360
|
+
dupdb.flags[:dupsort].should == true
|
361
|
+
dupdb.dupsort?.should == true
|
362
|
+
dupdb.dupfixed?.should == false
|
363
|
+
|
364
|
+
# add the no-op keyword to trigger a complaint from ruby 2.7
|
365
|
+
dupdb.put 'key1', 'value1', nodupdata: false
|
366
|
+
dupdb.put 'key1', 'value2'
|
367
|
+
dupdb.put 'key2', 'value3'
|
368
|
+
dupdb.cursor do |c|
|
369
|
+
c.set('key1', 'value2').should == ['key1', 'value2']
|
370
|
+
c.set('key1', 'value1').should == ['key1', 'value1']
|
371
|
+
c.set('key1', 'value3').should == nil
|
372
|
+
end
|
373
|
+
|
374
|
+
# this is basically an extended test of `cursor.set key, val`
|
375
|
+
dupdb.has?('key1', 'value1').should == true
|
376
|
+
dupdb.has?('key1', 'value2').should == true
|
377
|
+
dupdb.has?('key1', 'value0').should == false
|
378
|
+
|
379
|
+
# match the contents of key1
|
380
|
+
dupdb.each_value('key1').to_a.sort.should == ['value1', 'value2']
|
381
|
+
|
382
|
+
# we should have two entries for key1
|
383
|
+
dupdb.cardinality('key1').should == 2
|
384
|
+
|
385
|
+
dupdb.each_key.to_a.sort.should == ['key1', 'key2']
|
386
|
+
|
387
|
+
# XXX move this or whatever
|
388
|
+
env.transaction do |t|
|
389
|
+
dupdb.put 'key1', 'value1' unless dupdb.has? 'key1', 'value1'
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'should complain setting a key-value pair without dupsort' do
|
394
|
+
db.cursor do |c|
|
395
|
+
proc { c.set('key1', 'value1') }.should raise_error(LMDB::Error)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
334
399
|
it 'should raise without block or txn' do
|
335
400
|
proc { db.cursor.next }.should raise_error(LMDB::Error)
|
336
401
|
end
|
@@ -346,5 +411,12 @@ describe LMDB do
|
|
346
411
|
env.transaction { c = db.cursor; db2 = c.database }
|
347
412
|
db2.should == db
|
348
413
|
end
|
414
|
+
|
415
|
+
it 'should nest a read-only txn in a read-write' do
|
416
|
+
env.transaction do |t|
|
417
|
+
# has? opens a read-only transaction
|
418
|
+
db.put 'hurr', 'durr' unless db.has? 'hurr', 'durr'
|
419
|
+
end
|
420
|
+
end
|
349
421
|
end
|
350
422
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lmdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Mendler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '13.0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '13.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake-compiler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '1.1'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '1.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,15 +103,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
103
|
requirements:
|
104
104
|
- - ">="
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version:
|
106
|
+
version: '2.4'
|
107
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
112
|
requirements: []
|
113
|
-
|
114
|
-
rubygems_version: 2.4.2
|
113
|
+
rubygems_version: 3.1.2
|
115
114
|
signing_key:
|
116
115
|
specification_version: 4
|
117
116
|
summary: Ruby bindings to Lightning MDB
|