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 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