zlmdb 25.12.2__cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl

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.
Files changed (76) hide show
  1. zlmdb/__init__.py +416 -0
  2. zlmdb/_database.py +990 -0
  3. zlmdb/_errors.py +31 -0
  4. zlmdb/_flatc/__init__.py +105 -0
  5. zlmdb/_lmdb_vendor/__init__.py +37 -0
  6. zlmdb/_lmdb_vendor/__main__.py +25 -0
  7. zlmdb/_lmdb_vendor/_config.py +10 -0
  8. zlmdb/_lmdb_vendor/_lmdb_cffi.cpython-314t-aarch64-linux-gnu.so +0 -0
  9. zlmdb/_lmdb_vendor/cffi.py +2606 -0
  10. zlmdb/_lmdb_vendor/tool.py +670 -0
  11. zlmdb/_meta.py +27 -0
  12. zlmdb/_pmap.py +1667 -0
  13. zlmdb/_schema.py +137 -0
  14. zlmdb/_transaction.py +181 -0
  15. zlmdb/_types.py +1596 -0
  16. zlmdb/_version.py +27 -0
  17. zlmdb/cli.py +41 -0
  18. zlmdb/flatbuffers/__init__.py +60 -0
  19. zlmdb/flatbuffers/_git_version.py +24 -0
  20. zlmdb/flatbuffers/_version.py +17 -0
  21. zlmdb/flatbuffers/builder.py +824 -0
  22. zlmdb/flatbuffers/compat.py +89 -0
  23. zlmdb/flatbuffers/encode.py +43 -0
  24. zlmdb/flatbuffers/flexbuffers.py +1570 -0
  25. zlmdb/flatbuffers/number_types.py +182 -0
  26. zlmdb/flatbuffers/packer.py +42 -0
  27. zlmdb/flatbuffers/reflection/AdvancedFeatures.py +10 -0
  28. zlmdb/flatbuffers/reflection/BaseType.py +25 -0
  29. zlmdb/flatbuffers/reflection/Enum.py +252 -0
  30. zlmdb/flatbuffers/reflection/EnumVal.py +144 -0
  31. zlmdb/flatbuffers/reflection/Field.py +325 -0
  32. zlmdb/flatbuffers/reflection/KeyValue.py +84 -0
  33. zlmdb/flatbuffers/reflection/Object.py +260 -0
  34. zlmdb/flatbuffers/reflection/RPCCall.py +195 -0
  35. zlmdb/flatbuffers/reflection/Schema.py +301 -0
  36. zlmdb/flatbuffers/reflection/SchemaFile.py +112 -0
  37. zlmdb/flatbuffers/reflection/Service.py +213 -0
  38. zlmdb/flatbuffers/reflection/Type.py +148 -0
  39. zlmdb/flatbuffers/reflection/__init__.py +0 -0
  40. zlmdb/flatbuffers/reflection.fbs +156 -0
  41. zlmdb/flatbuffers/table.py +129 -0
  42. zlmdb/flatbuffers/util.py +47 -0
  43. zlmdb/py.typed +0 -0
  44. zlmdb/tests/conftest.py +9 -0
  45. zlmdb/tests/lmdb/__init__.py +0 -0
  46. zlmdb/tests/lmdb/address_book.py +287 -0
  47. zlmdb/tests/lmdb/crash_test.py +339 -0
  48. zlmdb/tests/lmdb/cursor_test.py +333 -0
  49. zlmdb/tests/lmdb/env_test.py +919 -0
  50. zlmdb/tests/lmdb/getmulti_test.py +92 -0
  51. zlmdb/tests/lmdb/iteration_test.py +258 -0
  52. zlmdb/tests/lmdb/package_test.py +70 -0
  53. zlmdb/tests/lmdb/test_lmdb.py +188 -0
  54. zlmdb/tests/lmdb/testlib.py +185 -0
  55. zlmdb/tests/lmdb/tool_test.py +60 -0
  56. zlmdb/tests/lmdb/txn_test.py +575 -0
  57. zlmdb/tests/orm/MNodeLog.py +853 -0
  58. zlmdb/tests/orm/__init__.py +0 -0
  59. zlmdb/tests/orm/_schema_fbs.py +215 -0
  60. zlmdb/tests/orm/_schema_mnode_log.py +1202 -0
  61. zlmdb/tests/orm/_schema_py2.py +250 -0
  62. zlmdb/tests/orm/_schema_py3.py +307 -0
  63. zlmdb/tests/orm/_test_flatbuffers.py +144 -0
  64. zlmdb/tests/orm/_test_serialization.py +144 -0
  65. zlmdb/tests/orm/test_basic.py +217 -0
  66. zlmdb/tests/orm/test_etcd.py +275 -0
  67. zlmdb/tests/orm/test_pmap_indexes.py +466 -0
  68. zlmdb/tests/orm/test_pmap_types.py +90 -0
  69. zlmdb/tests/orm/test_pmaps.py +295 -0
  70. zlmdb/tests/orm/test_select.py +619 -0
  71. zlmdb-25.12.2.dist-info/METADATA +280 -0
  72. zlmdb-25.12.2.dist-info/RECORD +76 -0
  73. zlmdb-25.12.2.dist-info/WHEEL +7 -0
  74. zlmdb-25.12.2.dist-info/entry_points.txt +3 -0
  75. zlmdb-25.12.2.dist-info/licenses/LICENSE +167 -0
  76. zlmdb-25.12.2.dist-info/licenses/NOTICE +41 -0
@@ -0,0 +1,287 @@
1
+ # Copyright 2013-2025 The py-lmdb authors, all rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted only as authorized by the OpenLDAP
5
+ # Public License.
6
+ #
7
+ # A copy of this license is available in the file LICENSE in the
8
+ # top-level directory of the distribution or, alternatively, at
9
+ # <http://www.OpenLDAP.org/license.html>.
10
+ #
11
+ # OpenLDAP is a registered trademark of the OpenLDAP Foundation.
12
+ #
13
+ # Individual files and/or contributed packages may be copyright by
14
+ # other parties and/or subject to additional restrictions.
15
+ #
16
+ # This work also contains materials derived from public sources.
17
+ #
18
+ # Additional information about OpenLDAP can be obtained at
19
+ # <http://www.openldap.org/>.
20
+
21
+ """
22
+ Test low-level LMDB API (py-lmdb compatibility)
23
+
24
+ This test is based on the py-lmdb address-book.py example:
25
+ https://github.com/jnwatson/py-lmdb/blob/master/examples/address-book.py
26
+
27
+ It demonstrates that zlmdb provides a fully compatible py-lmdb API
28
+ for direct LMDB access.
29
+ """
30
+
31
+ import tempfile
32
+ import shutil
33
+ import os
34
+
35
+ import zlmdb.lmdb as lmdb
36
+ import pytest
37
+
38
+
39
+ @pytest.fixture
40
+ def tmpdir():
41
+ """Create a temporary directory for test database"""
42
+ path = tempfile.mkdtemp()
43
+ yield path
44
+ shutil.rmtree(path)
45
+
46
+
47
+ def test_address_book_example(tmpdir):
48
+ """
49
+ Test the complete py-lmdb address-book example workflow.
50
+
51
+ This verifies:
52
+ - Environment creation with multiple databases
53
+ - Subdatabase creation
54
+ - Write transactions
55
+ - Read transactions
56
+ - Cursor iteration (sorted keys)
57
+ - Update operations
58
+ - Delete operations
59
+ - Drop database operations
60
+ """
61
+ dbpath = os.path.join(tmpdir, "address-book.lmdb")
62
+
63
+ # Open (and create if necessary) our database environment. Must specify
64
+ # max_dbs=... since we're opening subdbs.
65
+ env = lmdb.open(dbpath, max_dbs=10)
66
+
67
+ # Now create subdbs for home and business addresses.
68
+ home_db = env.open_db(b"home")
69
+ business_db = env.open_db(b"business")
70
+
71
+ # Add some telephone numbers to each DB:
72
+ with env.begin(write=True) as txn:
73
+ txn.put(b"mum", b"012345678", db=home_db)
74
+ txn.put(b"dad", b"011232211", db=home_db)
75
+ txn.put(b"dentist", b"044415121", db=home_db)
76
+ txn.put(b"hospital", b"078126321", db=home_db)
77
+
78
+ txn.put(b"vendor", b"0917465628", db=business_db)
79
+ txn.put(b"customer", b"0553211232", db=business_db)
80
+ txn.put(b"coworker", b"0147652935", db=business_db)
81
+ txn.put(b"boss", b"0123151232", db=business_db)
82
+ txn.put(b"manager", b"0644810485", db=business_db)
83
+
84
+ # Verify: Iterate each DB to show the keys are sorted
85
+ with env.begin() as txn:
86
+ # Home database - check sorted order
87
+ home_entries = list(txn.cursor(db=home_db))
88
+ assert home_entries == [
89
+ (b"dad", b"011232211"),
90
+ (b"dentist", b"044415121"),
91
+ (b"hospital", b"078126321"),
92
+ (b"mum", b"012345678"),
93
+ ]
94
+
95
+ # Business database - check sorted order
96
+ business_entries = list(txn.cursor(db=business_db))
97
+ assert business_entries == [
98
+ (b"boss", b"0123151232"),
99
+ (b"coworker", b"0147652935"),
100
+ (b"customer", b"0553211232"),
101
+ (b"manager", b"0644810485"),
102
+ (b"vendor", b"0917465628"),
103
+ ]
104
+
105
+ # Now let's update some phone numbers. We can specify the default subdb when
106
+ # starting the transaction, rather than pass it in every time:
107
+ with env.begin(write=True, db=home_db) as txn:
108
+ # Update dentist number
109
+ txn.put(b"dentist", b"099991231")
110
+
111
+ # Delete hospital number
112
+ txn.delete(b"hospital")
113
+
114
+ # Verify home DB state
115
+ home_entries = list(txn.cursor())
116
+ assert home_entries == [
117
+ (b"dad", b"011232211"),
118
+ (b"dentist", b"099991231"), # Updated
119
+ (b"mum", b"012345678"),
120
+ ]
121
+ # Note: hospital should be deleted
122
+ assert b"hospital" not in [key for key, _ in home_entries]
123
+
124
+ # Now let's look up a number in the business DB
125
+ with env.begin(db=business_db) as txn:
126
+ boss_number = txn.get(b"boss")
127
+ assert boss_number == b"0123151232"
128
+
129
+ # We got fired, time to delete all keys from the business DB.
130
+ with env.begin(write=True) as txn:
131
+ txn.drop(business_db, delete=False)
132
+
133
+ # Add number for recruiter to business DB
134
+ txn.put(b"recruiter", b"04123125324", db=business_db)
135
+
136
+ # Verify business DB is now only the recruiter
137
+ business_entries = list(txn.cursor(db=business_db))
138
+ assert business_entries == [
139
+ (b"recruiter", b"04123125324"),
140
+ ]
141
+
142
+ # Clean up
143
+ env.close()
144
+
145
+
146
+ def test_lmdb_basic_operations(tmpdir):
147
+ """
148
+ Test basic LMDB operations through py-lmdb compatible API.
149
+
150
+ This is a simpler test focusing on core CRUD operations.
151
+ """
152
+ dbpath = os.path.join(tmpdir, "test.lmdb")
153
+
154
+ # Create environment and database
155
+ env = lmdb.open(dbpath)
156
+
157
+ # Write data
158
+ with env.begin(write=True) as txn:
159
+ txn.put(b"key1", b"value1")
160
+ txn.put(b"key2", b"value2")
161
+ txn.put(b"key3", b"value3")
162
+
163
+ # Read data
164
+ with env.begin() as txn:
165
+ assert txn.get(b"key1") == b"value1"
166
+ assert txn.get(b"key2") == b"value2"
167
+ assert txn.get(b"key3") == b"value3"
168
+ assert txn.get(b"nonexistent") is None
169
+
170
+ # Update data
171
+ with env.begin(write=True) as txn:
172
+ txn.put(b"key2", b"new_value2")
173
+
174
+ # Verify update
175
+ with env.begin() as txn:
176
+ assert txn.get(b"key2") == b"new_value2"
177
+
178
+ # Delete data
179
+ with env.begin(write=True) as txn:
180
+ assert txn.delete(b"key1") is True
181
+ assert txn.delete(b"nonexistent") is False
182
+
183
+ # Verify deletion
184
+ with env.begin() as txn:
185
+ assert txn.get(b"key1") is None
186
+ assert txn.get(b"key2") == b"new_value2"
187
+ assert txn.get(b"key3") == b"value3"
188
+
189
+ env.close()
190
+
191
+
192
+ def test_lmdb_cursor_operations(tmpdir):
193
+ """
194
+ Test cursor operations for iteration and positioning.
195
+ """
196
+ dbpath = os.path.join(tmpdir, "cursor-test.lmdb")
197
+ env = lmdb.open(dbpath)
198
+
199
+ # Insert data with predictable sorting
200
+ with env.begin(write=True) as txn:
201
+ for i in range(10):
202
+ key = f"key{i:02d}".encode()
203
+ value = f"value{i}".encode()
204
+ txn.put(key, value)
205
+
206
+ # Test cursor iteration (should be sorted)
207
+ with env.begin() as txn:
208
+ cursor = txn.cursor()
209
+ entries = list(cursor)
210
+
211
+ # Verify sorted order
212
+ assert len(entries) == 10
213
+ assert entries[0] == (b"key00", b"value0")
214
+ assert entries[9] == (b"key09", b"value9")
215
+
216
+ # Verify all entries are in order
217
+ for i, (key, value) in enumerate(entries):
218
+ expected_key = f"key{i:02d}".encode()
219
+ expected_value = f"value{i}".encode()
220
+ assert key == expected_key
221
+ assert value == expected_value
222
+
223
+ # Test cursor positioning
224
+ with env.begin() as txn:
225
+ cursor = txn.cursor()
226
+
227
+ # Move to first
228
+ assert cursor.first() is True
229
+ assert cursor.key() == b"key00"
230
+
231
+ # Move to last
232
+ assert cursor.last() is True
233
+ assert cursor.key() == b"key09"
234
+
235
+ # Move to specific key
236
+ assert cursor.set_key(b"key05") is True
237
+ assert cursor.key() == b"key05"
238
+ assert cursor.value() == b"value5"
239
+
240
+ # Move next
241
+ assert cursor.next() is True
242
+ assert cursor.key() == b"key06"
243
+
244
+ # Move prev
245
+ assert cursor.prev() is True
246
+ assert cursor.key() == b"key05"
247
+
248
+ env.close()
249
+
250
+
251
+ def test_lmdb_environment_info(tmpdir):
252
+ """
253
+ Test environment info and statistics.
254
+ """
255
+ dbpath = os.path.join(tmpdir, "info-test.lmdb")
256
+ env = lmdb.open(dbpath, map_size=10 * 1024 * 1024) # 10MB
257
+
258
+ # Get environment info
259
+ info = env.info()
260
+ assert info["map_size"] == 10 * 1024 * 1024
261
+ assert info["last_pgno"] >= 0
262
+ assert info["last_txnid"] >= 0
263
+ assert info["max_readers"] > 0
264
+ assert info["num_readers"] >= 0
265
+
266
+ # Get environment stats
267
+ stats = env.stat()
268
+ assert stats["psize"] > 0
269
+ assert stats["depth"] >= 0
270
+ assert stats["entries"] >= 0
271
+
272
+ env.close()
273
+
274
+
275
+ if __name__ == "__main__":
276
+ # Allow running as a script for quick testing
277
+ import sys
278
+
279
+ tmpdir = tempfile.mkdtemp()
280
+ try:
281
+ test_address_book_example(tmpdir)
282
+ test_lmdb_basic_operations(tmpdir)
283
+ test_lmdb_cursor_operations(tmpdir)
284
+ test_lmdb_environment_info(tmpdir)
285
+ print("✓ All tests passed!")
286
+ finally:
287
+ shutil.rmtree(tmpdir)
@@ -0,0 +1,339 @@
1
+ #
2
+ # Copyright 2013-2021 The py-lmdb authors, all rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted only as authorized by the OpenLDAP
6
+ # Public License.
7
+ #
8
+ # A copy of this license is available in the file LICENSE in the
9
+ # top-level directory of the distribution or, alternatively, at
10
+ # <http://www.OpenLDAP.org/license.html>.
11
+ #
12
+ # OpenLDAP is a registered trademark of the OpenLDAP Foundation.
13
+ #
14
+ # Individual files and/or contributed packages may be copyright by
15
+ # other parties and/or subject to additional restrictions.
16
+ #
17
+ # This work also contains materials derived from public sources.
18
+ #
19
+ # Additional information about OpenLDAP can be obtained at
20
+ # <http://www.openldap.org/>.
21
+ #
22
+
23
+ # This is not a test suite! More like a collection of triggers for previously
24
+ # observed crashes. Want to contribute to py-lmdb? Please write a test suite!
25
+ #
26
+ # what happens when empty keys/ values passed to various funcs
27
+ # incorrect types
28
+ # try to break cpython arg parsing - too many/few/incorrect args
29
+ # Various efforts to cause Python-level leaks.
30
+ #
31
+
32
+ from __future__ import absolute_import
33
+ from __future__ import with_statement
34
+
35
+ import sys
36
+ import itertools
37
+ import random
38
+ import unittest
39
+ import multiprocessing
40
+
41
+ import zlmdb.lmdb as lmdb
42
+ from . import testlib
43
+
44
+ from .testlib import B
45
+ from .testlib import byte_chr as O
46
+
47
+
48
+ class CrashTest(unittest.TestCase):
49
+ def tearDown(self):
50
+ testlib.cleanup()
51
+
52
+ # Various efforts to cause segfaults.
53
+
54
+ def setUp(self):
55
+ self.path, self.env = testlib.temp_env()
56
+ with self.env.begin(write=True) as txn:
57
+ txn.put(B("dave"), B(""))
58
+ txn.put(B("dave2"), B(""))
59
+
60
+ def testOldCrash(self):
61
+ txn = self.env.begin()
62
+ dir(iter(txn.cursor()))
63
+
64
+ def testCloseWithTxn(self):
65
+ txn = self.env.begin(write=True)
66
+ self.env.close()
67
+ self.assertRaises(Exception, (lambda: list(txn.cursor())))
68
+
69
+ def testDoubleClose(self):
70
+ self.env.close()
71
+ self.env.close()
72
+
73
+ def testTxnCloseActiveIter(self):
74
+ with self.env.begin() as txn:
75
+ it = txn.cursor().iternext()
76
+ self.assertRaises(Exception, (lambda: list(it)))
77
+
78
+ def testDbCloseActiveIter(self):
79
+ db = self.env.open_db(key=B("dave3"))
80
+ with self.env.begin(write=True) as txn:
81
+ txn.put(B("a"), B("b"), db=db)
82
+ it = txn.cursor(db=db).iternext()
83
+ self.assertRaises(Exception, (lambda: list(it)))
84
+
85
+
86
+ class IteratorTest(unittest.TestCase):
87
+ def tearDown(self):
88
+ testlib.cleanup()
89
+
90
+ def setUp(self):
91
+ self.path, self.env = testlib.temp_env()
92
+ self.txn = self.env.begin(write=True)
93
+ self.c = self.txn.cursor()
94
+
95
+ def testEmpty(self):
96
+ self.assertEqual([], list(self.c))
97
+ self.assertEqual([], list(self.c.iternext()))
98
+ self.assertEqual([], list(self.c.iterprev()))
99
+
100
+ def testFilled(self):
101
+ testlib.putData(self.txn)
102
+ self.assertEqual(testlib.ITEMS, list(self.c))
103
+ self.assertEqual(testlib.ITEMS, list(self.c))
104
+ self.assertEqual(testlib.ITEMS, list(self.c.iternext()))
105
+ self.assertEqual(testlib.ITEMS[::-1], list(self.txn.cursor().iterprev()))
106
+ self.assertEqual(testlib.ITEMS[::-1], list(self.c.iterprev()))
107
+ self.assertEqual(testlib.ITEMS, list(self.c))
108
+
109
+ def testFilledSkipForward(self):
110
+ testlib.putData(self.txn)
111
+ self.c.set_range(B("b"))
112
+ self.assertEqual(testlib.ITEMS[1:], list(self.c))
113
+
114
+ def testFilledSkipReverse(self):
115
+ testlib.putData(self.txn)
116
+ self.c.set_range(B("b"))
117
+ self.assertEqual(testlib.REV_ITEMS[-2:], list(self.c.iterprev()))
118
+
119
+ def testFilledSkipEof(self):
120
+ testlib.putData(self.txn)
121
+ self.assertEqual(False, self.c.set_range(B("z")))
122
+ self.assertEqual(testlib.REV_ITEMS, list(self.c.iterprev()))
123
+
124
+
125
+ class BigReverseTest(unittest.TestCase):
126
+ def tearDown(self):
127
+ testlib.cleanup()
128
+
129
+ # Test for issue with MDB_LAST+MDB_PREV skipping chunks of database.
130
+ def test_big_reverse(self):
131
+ path, env = testlib.temp_env()
132
+ txn = env.begin(write=True)
133
+ keys = [B("%05d" % i) for i in range(0xFFFF)]
134
+ for k in keys:
135
+ txn.put(k, k, append=True)
136
+ assert list(txn.cursor().iterprev(values=False)) == list(reversed(keys))
137
+
138
+
139
+ class MultiCursorDeleteTest(unittest.TestCase):
140
+ def tearDown(self):
141
+ testlib.cleanup()
142
+
143
+ def setUp(self):
144
+ self.path, self.env = testlib.temp_env()
145
+
146
+ def test1(self):
147
+ """Ensure MDB_NEXT is ignored on `c1' when it was previously positioned
148
+ on the key that `c2' just deleted."""
149
+ txn = self.env.begin(write=True)
150
+ cur = txn.cursor()
151
+ while cur.first():
152
+ cur.delete()
153
+
154
+ for i in range(1, 10):
155
+ cur.put(O(ord("a") + i) * i, B(""))
156
+
157
+ c1 = txn.cursor()
158
+ c1f = c1.iternext(values=False)
159
+ while next(c1f) != B("ddd"):
160
+ pass
161
+ c2 = txn.cursor()
162
+ assert c2.set_key(B("ddd"))
163
+ c2.delete()
164
+ assert next(c1f) == B("eeee")
165
+
166
+ def test_monster(self):
167
+ # Generate predictable sequence of sizes.
168
+ rand = random.Random()
169
+ rand.seed(0)
170
+
171
+ txn = self.env.begin(write=True)
172
+ keys = []
173
+ for i in range(20000):
174
+ key = B("%06x" % i)
175
+ val = B("x" * rand.randint(76, 350))
176
+ assert txn.put(key, val)
177
+ keys.append(key)
178
+
179
+ deleted = 0
180
+ for key in txn.cursor().iternext(values=False):
181
+ assert txn.delete(key), key
182
+ deleted += 1
183
+
184
+ assert deleted == len(keys), deleted
185
+
186
+
187
+ class TxnFullTest(unittest.TestCase):
188
+ def tearDown(self):
189
+ testlib.cleanup()
190
+
191
+ def test_17bf75b12eb94d9903cd62329048b146d5313bad(self):
192
+ """
193
+ me_txn0 previously cached MDB_TXN_ERROR permanently. Fixed by
194
+ 17bf75b12eb94d9903cd62329048b146d5313bad.
195
+ """
196
+ path, env = testlib.temp_env(map_size=4096 * 9, sync=False, max_spare_txns=0)
197
+ for i in itertools.count():
198
+ try:
199
+ with env.begin(write=True) as txn:
200
+ txn.put(B(str(i)), B(str(i)))
201
+ except lmdb.MapFullError:
202
+ break
203
+
204
+ # Should not crash with MDB_BAD_TXN:
205
+ with env.begin(write=True) as txn:
206
+ txn.delete(B("1"))
207
+
208
+
209
+ class EmptyIterTest(unittest.TestCase):
210
+ def tearDown(self):
211
+ testlib.cleanup()
212
+
213
+ def test_python3_iternext_segfault(self):
214
+ # https://github.com/dw/py-lmdb/issues/105
215
+ _, env = testlib.temp_env()
216
+ txn = env.begin()
217
+ cur = txn.cursor()
218
+ ite = cur.iternext()
219
+ nex = getattr(ite, "next", getattr(ite, "__next__", None))
220
+ assert nex is not None
221
+ self.assertRaises(StopIteration, nex)
222
+
223
+
224
+ class MultiputTest(unittest.TestCase):
225
+ def tearDown(self):
226
+ testlib.cleanup()
227
+
228
+ def test_multiput_segfault(self):
229
+ # http://github.com/jnwatson/py-lmdb/issues/173
230
+ _, env = testlib.temp_env()
231
+ db = env.open_db(b"foo", dupsort=True)
232
+ txn = env.begin(db=db, write=True)
233
+ txn.put(b"a", b"\x00\x00\x00\x00\x00\x00\x00\x00")
234
+ txn.put(b"a", b"\x05")
235
+ txn.put(b"a", b"\t")
236
+ txn.put(b"a", b"\r")
237
+ txn.put(b"a", b"\x11")
238
+ txn.put(b"a", b"\x15")
239
+ txn.put(b"a", b"\x19")
240
+ txn.put(b"a", b"\x1d")
241
+ txn.put(b"a", b"!")
242
+ txn.put(b"a", b"%")
243
+ txn.put(b"a", b")")
244
+ txn.put(b"a", b"-")
245
+ txn.put(b"a", b"1")
246
+ txn.put(b"a", b"5")
247
+ txn.commit()
248
+
249
+
250
+ class InvalidArgTest(unittest.TestCase):
251
+ def tearDown(self):
252
+ testlib.cleanup()
253
+
254
+ def test_duplicate_arg(self):
255
+ # https://github.com/jnwatson/py-lmdb/issues/203
256
+ _, env = testlib.temp_env()
257
+ txn = env.begin(write=True)
258
+ c = txn.cursor()
259
+ self.assertRaises(TypeError, c.get, b"a", key=True)
260
+
261
+
262
+ class BadCursorTest(unittest.TestCase):
263
+ def tearDown(self):
264
+ testlib.cleanup()
265
+
266
+ def test_cursor_open_failure(self):
267
+ """
268
+ Test the error path for when mdb_cursor_open fails
269
+
270
+ Note:
271
+ this only would crash if cpython is built with Py_TRACE_REFS
272
+ """
273
+ # https://github.com/jnwatson/py-lmdb/issues/216
274
+ path, env = testlib.temp_env()
275
+ db = env.open_db(b"db", dupsort=True)
276
+ env.close()
277
+ del env
278
+
279
+ env = lmdb.open(path, readonly=True, max_dbs=4)
280
+ txn1 = env.begin(write=False)
281
+ db = env.open_db(b"db", dupsort=True, txn=txn1)
282
+ txn2 = env.begin(write=False)
283
+ self.assertRaises(lmdb.InvalidParameterError, txn2.cursor, db=db)
284
+
285
+
286
+ MINDBSIZE = 64 * 1024 * 2 # certain ppcle Linux distros have a 64K page size
287
+
288
+ if sys.version_info[:2] >= (3, 4):
289
+
290
+ class MapResizeTest(unittest.TestCase):
291
+ def tearDown(self):
292
+ testlib.cleanup()
293
+
294
+ @staticmethod
295
+ def do_resize(path):
296
+ """
297
+ Increase map size and fill up database, making sure that the root page is no longer
298
+ accessible in the main process.
299
+ """
300
+ with lmdb.open(path, max_dbs=10, create=False, map_size=MINDBSIZE) as env:
301
+ env.open_db(b"foo")
302
+ env.set_mapsize(MINDBSIZE * 2)
303
+ count = 0
304
+ try:
305
+ # Figure out how many keyvals we can enter before we run out of space
306
+ with env.begin(write=True) as txn:
307
+ while True:
308
+ datum = count.to_bytes(4, "little")
309
+ txn.put(datum, b"0")
310
+ count += 1
311
+
312
+ except lmdb.MapFullError:
313
+ # Now put (and commit) just short of that
314
+ with env.begin(write=True) as txn:
315
+ for i in range(count - 100):
316
+ datum = i.to_bytes(4, "little")
317
+ txn.put(datum, b"0")
318
+ else:
319
+ assert 0
320
+
321
+ def test_opendb_resize(self):
322
+ """
323
+ Test that we correctly handle a MDB_MAP_RESIZED in env.open_db.
324
+
325
+ Would seg fault in cffi implementation
326
+ """
327
+ mpctx = multiprocessing.get_context("spawn")
328
+ path, env = testlib.temp_env(max_dbs=10, map_size=MINDBSIZE)
329
+ env.close()
330
+ env = lmdb.open(path, max_dbs=10, map_size=MINDBSIZE, readonly=True)
331
+ proc = mpctx.Process(target=self.do_resize, args=(path,))
332
+ proc.start()
333
+ proc.join(5)
334
+ assert proc.exitcode is not None
335
+ self.assertRaises(lmdb.MapResizedError, env.open_db, b"foo")
336
+
337
+
338
+ if __name__ == "__main__":
339
+ unittest.main()