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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 00ac2d717d44921d8a11dabbe516ba59203b37e2
4
- data.tar.gz: 4cc13fff824970690d1f2e81c14d3be5c91e1df8
2
+ SHA256:
3
+ metadata.gz: c02c1c99b143e331bf5fdbae711a57c4845b32ca07c4c041c9c639b96cbb8388
4
+ data.tar.gz: ce305105acf85f4161e654dc80ad045baeded4e8a8e39f45fed9f8da68f5c2ce
5
5
  SHA512:
6
- metadata.gz: c96d8fad469d56c9f9b010952ce2b1e4b94eb0bb247a6abbdd25c9d10b6b016a74a9abaa30763784e5669666e71626c318a1b011fea3ebcca4f75b0100aa5ffd
7
- data.tar.gz: eda8808e0a2844b8d80b9c27203108017e92bfe3dfe75872a336882cf8a7a6a3866b21c2e0ba330375b80d9a31928abf840abffa20a4229ac49fc6cd85c5815b
6
+ metadata.gz: 1303411d1515bf39a6406540f3a10d9e12c2bd3fea597d17a9e2b949bcf7707244381467a624edd15fada41ac65ae87721c2e42a9ec4dadb5b37e9f8db2a55b0
7
+ data.tar.gz: aeaa5861dcbde41e9fec9634f6cae749dce6a1348ab9541f90247c2cff60dcb10180701348757a00ba7fc11e93431ef97cfb3064e5face3cc50c704498ca183d
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  *.o
2
2
  *.so
3
+ *.bundle
3
4
  .#*
4
5
  *.log
5
6
  *.mdb
@@ -7,4 +8,6 @@ Makefile
7
8
  tmp/
8
9
  Gemfile.lock
9
10
  doc/
10
- .yardoc/
11
+ .yardoc/
12
+ *.gem
13
+ dtrace/
@@ -1,13 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
- - 1.9.3
5
- - 2.0.0
6
- - 2.1.0
3
+ - 2.3.8
4
+ - 2.4.9
5
+ - 2.5.7
6
+ - 2.6.3
7
+ - 2.7.0
7
8
  - ruby-head
8
9
  - rbx
9
10
  matrix:
10
11
  allow_failures:
11
12
  - rvm: ruby-head
12
- - rvm: 1.8.7
13
+ - rvm: 2.3.8
13
14
  - rvm: rbx
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.
@@ -6,3 +6,6 @@ FLAG(NOMETASYNC, nometasync)
6
6
  FLAG(WRITEMAP, writemap)
7
7
  FLAG(MAPASYNC, mapasync)
8
8
  FLAG(NOTLS, notls)
9
+ FLAG(NOLOCK, nolock)
10
+ FLAG(NORDAHEAD, nordahead)
11
+ FLAG(NOMEMINIT, nomeminit)
@@ -13,3 +13,9 @@ ERROR(TLS_FULL)
13
13
  ERROR(TXN_FULL)
14
14
  ERROR(CURSOR_FULL)
15
15
  ERROR(PAGE_FULL)
16
+ ERROR(MAP_RESIZED)
17
+ ERROR(INCOMPATIBLE)
18
+ ERROR(BAD_RSLOT)
19
+ ERROR(BAD_TXN)
20
+ ERROR(BAD_VALSIZE)
21
+ ERROR(BAD_DBI)
@@ -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
- rb_warn("Memory leak - Garbage collecting active transaction");
42
- // mdb_txn_abort(transaction->txn);
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 transaction_env
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
- mdb_txn_abort(txn);
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)), INT2NUM((size_t)info.me_##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, self, "stat", 0, 0, MDB_RDONLY);
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
- rb_scan_args(argc, argv, "2:", &vkey, &vval, &option_hash);
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 database_env
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
- int ret = mdb_cursor_get(cursor->cur, &key, &value, MDB_NEXT);
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), rb_str_new(value.mv_data, value.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 *txn = mdb_cursor_txn(cursor->cur);
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
- * @param key The key to which the cursor should be positioned
1136
- * @return [Array] The [key, value] pair to which the cursor now points.
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 self, VALUE vkey) {
1139
- CURSOR(self, cursor);
1140
- MDB_val key, value;
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
- key.mv_size = RSTRING_LEN(vkey);
1143
- key.mv_data = StringValuePtr(vkey);
1251
+ rb_scan_args(argc, argv, "11", &vkey, &vval);
1144
1252
 
1145
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_KEY));
1146
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
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
- check(mdb_cursor_get(cursor->cur, &key, &value, MDB_SET_RANGE));
1163
- return rb_assoc_new(rb_str_new(key.mv_data, key.mv_size), rb_str_new(value.mv_data, value.mv_size));
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, 0);
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);
@@ -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 self, VALUE vkey);
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);
@@ -11,9 +11,12 @@ module LMDB
11
11
  # puts "at #{key}: #{value}"
12
12
  # end
13
13
  def each
14
- cursor do |c|
15
- while i = c.next
16
- yield(i)
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(key, value)
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
@@ -1,3 +1,3 @@
1
1
  module LMDB
2
- VERSION = '0.4.8'
2
+ VERSION = '0.5.3'.freeze
3
3
  end
@@ -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 = ">= 1.9.3"
23
- s.add_development_dependency 'rake', "~> 10.0"
24
- s.add_development_dependency 'rake-compiler', '<=0.8.2'
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
@@ -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(Fixnum)
12
- LMDB::LIB_VERSION_MINOR.should be_instance_of(Fixnum)
13
- LMDB::LIB_VERSION_PATCH.should be_instance_of(Fixnum)
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, :nosync => true, :mode => 0777, :maxreaders => 777, :mapsize => 111111, :maxdbs => 666)
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(Fixnum)
56
- stat[:depth].should be_instance_of(Fixnum)
57
- stat[:branch_pages].should be_instance_of(Fixnum)
58
- stat[:leaf_pages].should be_instance_of(Fixnum)
59
- stat[:overflow_pages].should be_instance_of(Fixnum)
60
- stat[:entries].should be_instance_of(Fixnum)
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(Fixnum)
66
- info[:mapsize].should be_instance_of(Fixnum)
67
- info[:last_pgno].should be_instance_of(Fixnum)
68
- info[:last_txnid].should be_instance_of(Fixnum)
69
- info[:maxreaders].should be_instance_of(Fixnum)
70
- info[:numreaders].should be_instance_of(Fixnum)
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
- db1 = env.database('db1', :create => true)
189
- db2 = env.database('db2', :create => true)
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', :create => true)
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.8
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: 2014-11-02 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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: 0.8.2
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: 0.8.2
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: 1.9.3
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
- rubyforge_project:
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