zlmdb 25.10.1__cp314-cp314-win_amd64.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.
Potentially problematic release.
This version of zlmdb might be problematic. Click here for more details.
- flatbuffers/__init__.py +19 -0
- flatbuffers/_version.py +17 -0
- flatbuffers/builder.py +776 -0
- flatbuffers/compat.py +86 -0
- flatbuffers/encode.py +42 -0
- flatbuffers/flexbuffers.py +1527 -0
- flatbuffers/number_types.py +181 -0
- flatbuffers/packer.py +42 -0
- flatbuffers/reflection/AdvancedFeatures.py +10 -0
- flatbuffers/reflection/BaseType.py +24 -0
- flatbuffers/reflection/Enum.py +169 -0
- flatbuffers/reflection/EnumVal.py +96 -0
- flatbuffers/reflection/Field.py +208 -0
- flatbuffers/reflection/KeyValue.py +56 -0
- flatbuffers/reflection/Object.py +175 -0
- flatbuffers/reflection/RPCCall.py +131 -0
- flatbuffers/reflection/Schema.py +206 -0
- flatbuffers/reflection/SchemaFile.py +77 -0
- flatbuffers/reflection/Service.py +145 -0
- flatbuffers/reflection/Type.py +98 -0
- flatbuffers/reflection/__init__.py +0 -0
- flatbuffers/table.py +129 -0
- flatbuffers/util.py +43 -0
- zlmdb/__init__.py +312 -0
- zlmdb/_database.py +990 -0
- zlmdb/_errors.py +31 -0
- zlmdb/_meta.py +27 -0
- zlmdb/_pmap.py +1667 -0
- zlmdb/_schema.py +137 -0
- zlmdb/_transaction.py +181 -0
- zlmdb/_types.py +1596 -0
- zlmdb/_version.py +27 -0
- zlmdb/cli.py +41 -0
- zlmdb/flatbuffers/__init__.py +5 -0
- zlmdb/flatbuffers/reflection/AdvancedFeatures.py +10 -0
- zlmdb/flatbuffers/reflection/BaseType.py +25 -0
- zlmdb/flatbuffers/reflection/Enum.py +252 -0
- zlmdb/flatbuffers/reflection/EnumVal.py +144 -0
- zlmdb/flatbuffers/reflection/Field.py +325 -0
- zlmdb/flatbuffers/reflection/KeyValue.py +84 -0
- zlmdb/flatbuffers/reflection/Object.py +260 -0
- zlmdb/flatbuffers/reflection/RPCCall.py +195 -0
- zlmdb/flatbuffers/reflection/Schema.py +301 -0
- zlmdb/flatbuffers/reflection/SchemaFile.py +112 -0
- zlmdb/flatbuffers/reflection/Service.py +213 -0
- zlmdb/flatbuffers/reflection/Type.py +148 -0
- zlmdb/flatbuffers/reflection/__init__.py +0 -0
- zlmdb/flatbuffers/reflection.fbs +152 -0
- zlmdb/lmdb/__init__.py +37 -0
- zlmdb/lmdb/__main__.py +25 -0
- zlmdb/lmdb/_config.py +10 -0
- zlmdb/lmdb/cffi.py +2606 -0
- zlmdb/lmdb/tool.py +670 -0
- zlmdb/tests/lmdb/__init__.py +0 -0
- zlmdb/tests/lmdb/address_book.py +287 -0
- zlmdb/tests/lmdb/crash_test.py +339 -0
- zlmdb/tests/lmdb/cursor_test.py +333 -0
- zlmdb/tests/lmdb/env_test.py +919 -0
- zlmdb/tests/lmdb/getmulti_test.py +92 -0
- zlmdb/tests/lmdb/iteration_test.py +258 -0
- zlmdb/tests/lmdb/package_test.py +70 -0
- zlmdb/tests/lmdb/test_lmdb.py +188 -0
- zlmdb/tests/lmdb/testlib.py +185 -0
- zlmdb/tests/lmdb/tool_test.py +60 -0
- zlmdb/tests/lmdb/txn_test.py +575 -0
- zlmdb/tests/orm/MNodeLog.py +853 -0
- zlmdb/tests/orm/__init__.py +0 -0
- zlmdb/tests/orm/_schema_fbs.py +215 -0
- zlmdb/tests/orm/_schema_mnode_log.py +1201 -0
- zlmdb/tests/orm/_schema_py2.py +250 -0
- zlmdb/tests/orm/_schema_py3.py +307 -0
- zlmdb/tests/orm/_test_flatbuffers.py +144 -0
- zlmdb/tests/orm/_test_serialization.py +144 -0
- zlmdb/tests/orm/test_basic.py +217 -0
- zlmdb/tests/orm/test_etcd.py +275 -0
- zlmdb/tests/orm/test_pmap_indexes.py +466 -0
- zlmdb/tests/orm/test_pmap_types.py +90 -0
- zlmdb/tests/orm/test_pmaps.py +295 -0
- zlmdb/tests/orm/test_select.py +619 -0
- zlmdb-25.10.1.dist-info/METADATA +264 -0
- zlmdb-25.10.1.dist-info/RECORD +86 -0
- zlmdb-25.10.1.dist-info/WHEEL +5 -0
- zlmdb-25.10.1.dist-info/entry_points.txt +2 -0
- zlmdb-25.10.1.dist-info/licenses/LICENSE +137 -0
- zlmdb-25.10.1.dist-info/licenses/NOTICE +41 -0
- zlmdb-25.10.1.dist-info/top_level.txt +2 -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()
|