lmdb 0.4.8 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|