solentware-base 5.1__tar.gz → 5.2__tar.gz
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.
- {solentware-base-5.1/solentware_base.egg-info → solentware-base-5.2}/PKG-INFO +2 -3
- {solentware-base-5.1 → solentware-base-5.2}/README +1 -1
- {solentware-base-5.1 → solentware-base-5.2}/pyproject.toml +1 -2
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_database.py +71 -6
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_databasedu.py +105 -26
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_db.py +53 -51
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_db_tkinter.py +56 -64
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_dbdu.py +217 -31
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_dbdu_tkinter.py +305 -25
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_dpt.py +349 -170
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_lmdb.py +48 -48
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_lmdbdu.py +228 -24
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_nosql.py +46 -36
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_nosqldu.py +6 -11
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_sqlite.py +46 -46
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_sqlitedu.py +350 -25
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/bytebit.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/constants.py +7 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/cursor.py +17 -7
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/filespec.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/find.py +9 -4
- solentware-base-5.2/solentware_base/core/merge.py +166 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/recordset.py +251 -350
- solentware-base-5.2/solentware_base/core/recordsetbasecursor.py +120 -0
- solentware-base-5.2/solentware_base/core/recordsetcursor.py +167 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/tree.py +1 -4
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/where.py +0 -4
- solentware-base-5.2/solentware_base/dptdu_database.py +234 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/gnudu_database.py +0 -3
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/modulequery.py +0 -5
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/ndbmdu_database.py +0 -3
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/base_3_to_4.py +4 -12
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/base_3_to_4_db.py +4 -14
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/base_3_to_4_sqlite.py +4 -12
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/berkeleydb_du_splice_fix.py +3 -4
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/compare_dpt_tape_files.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/create_database.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/sqlite_table_name_upgrade.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/traverse_dpt_tape_files.py +0 -1
- {solentware-base-5.1 → solentware-base-5.2/solentware_base.egg-info}/PKG-INFO +2 -3
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base.egg-info/SOURCES.txt +3 -2
- solentware-base-5.1/solentware_base/core/archivedu.py +0 -226
- solentware-base-5.1/solentware_base/core/archivedudpt.py +0 -117
- solentware-base-5.1/solentware_base/dptdu_database.py +0 -118
- {solentware-base-5.1 → solentware-base-5.2}/LICENCE +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/MANIFEST.in +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/setup.cfg +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/__init__.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/apsw_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/apswdu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/berkeleydb_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/berkeleydbdu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/bsddb3_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/bsddb3du_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/__init__.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/_bytebit.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/findvalues.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/record.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/segmentsize.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/core/wherevalues.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/db_tcl.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/db_tkinter_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/db_tkinterdu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/dpt_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/dptfastload_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/gnu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/gnu_module.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/lmdb_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/lmdbdu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/ndbm_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/ndbm_module.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/sqlite3_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/sqlite3du_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/__init__.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/tools/ui_base_3_to_4.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/unqlite_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/unqlitedu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/vedis_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base/vedisdu_database.py +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base.egg-info/dependency_links.txt +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/solentware_base.egg-info/top_level.txt +0 -0
- {solentware-base-5.1 → solentware-base-5.2}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: solentware-base
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2
|
|
4
4
|
Summary: Bitmapped record number databases.
|
|
5
5
|
Author-email: Roger Marsh <roger.marsh@solentware.co.uk>
|
|
6
6
|
License: Licence for solentware_base package
|
|
@@ -23,7 +23,6 @@ Project-URL: Homepage, http://www.solentware.co.uk
|
|
|
23
23
|
Project-URL: Repository, https://github.com/RogerMarsh/solentware-base.git
|
|
24
24
|
Project-URL: Bug Tracker, https://github.com/RogerMarsh/solentware-base/issues
|
|
25
25
|
Classifier: License :: OSI Approved :: BSD License
|
|
26
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
27
26
|
Classifier: Programming Language :: Python :: 3.8
|
|
28
27
|
Classifier: Programming Language :: Python :: 3.9
|
|
29
28
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -48,7 +47,7 @@ A record number database is seen as a set of fixed size segments, and an existen
|
|
|
48
47
|
|
|
49
48
|
Where an index value refers to many records in a segment these references are encoded as a bitmap or a list of record numbers, in a single <index value> : <bitmap or list> record, rather than as many <index value> : <record number> records. When 'many' is small a list is favoured to save the space needed for large numbers of unset bits.
|
|
50
49
|
|
|
51
|
-
The interfaces provided by the apsw, berkeleydb, bsddb3, dptdb, and sqlite3, Python modules are supported.
|
|
50
|
+
The interfaces provided by the apsw, berkeleydb, bsddb3, dptdb, lmdb, and sqlite3, Python modules are supported.
|
|
52
51
|
|
|
53
52
|
The interfaces provided by the dbm.gnu, dbm.ndbm, unqlite, and vedis, Python modules are available for experiment.
|
|
54
53
|
|
|
@@ -12,7 +12,7 @@ A record number database is seen as a set of fixed size segments, and an existen
|
|
|
12
12
|
|
|
13
13
|
Where an index value refers to many records in a segment these references are encoded as a bitmap or a list of record numbers, in a single <index value> : <bitmap or list> record, rather than as many <index value> : <record number> records. When 'many' is small a list is favoured to save the space needed for large numbers of unset bits.
|
|
14
14
|
|
|
15
|
-
The interfaces provided by the apsw, berkeleydb, bsddb3, dptdb, and sqlite3, Python modules are supported.
|
|
15
|
+
The interfaces provided by the apsw, berkeleydb, bsddb3, dptdb, lmdb, and sqlite3, Python modules are supported.
|
|
16
16
|
|
|
17
17
|
The interfaces provided by the dbm.gnu, dbm.ndbm, unqlite, and vedis, Python modules are available for experiment.
|
|
18
18
|
|
|
@@ -10,14 +10,13 @@ line-length = 79
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "solentware-base"
|
|
13
|
-
version = "5.
|
|
13
|
+
version = "5.2"
|
|
14
14
|
authors = [
|
|
15
15
|
{ name="Roger Marsh", email="roger.marsh@solentware.co.uk" },
|
|
16
16
|
]
|
|
17
17
|
description = "Bitmapped record number databases."
|
|
18
18
|
classifiers = [
|
|
19
19
|
"License :: OSI Approved :: BSD License",
|
|
20
|
-
"Programming Language :: Python :: 3.7",
|
|
21
20
|
"Programming Language :: Python :: 3.8",
|
|
22
21
|
"Programming Language :: Python :: 3.9",
|
|
23
22
|
"Programming Language :: Python :: 3.10",
|
|
@@ -24,6 +24,10 @@ from .recordset import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class DataSourceCursorError(Exception):
|
|
28
|
+
"""Exception for database-specific DataSourceCursor setting."""
|
|
29
|
+
|
|
30
|
+
|
|
27
31
|
# The Database class was added to the ignored-classes list of pylint.conf for
|
|
28
32
|
# solentware_base at a time when all the no-member errors (E1101) referred
|
|
29
33
|
# to it or one of it's subclasses with the same name in another module.
|
|
@@ -37,9 +41,7 @@ class Database:
|
|
|
37
41
|
Property file_per_database returns False because all 'key:value' sets,
|
|
38
42
|
and the associated inverted list indicies, are held in one file by
|
|
39
43
|
default. Berkeley DB is the known example where it is reasonable to
|
|
40
|
-
return True: override the class attribute,
|
|
41
|
-
delete_archive() methods in .archivedu.Archivedu class, if this case
|
|
42
|
-
is wanted.
|
|
44
|
+
return True: override the class attribute, if this case is wanted.
|
|
43
45
|
|
|
44
46
|
"""
|
|
45
47
|
|
|
@@ -133,7 +135,6 @@ class Database:
|
|
|
133
135
|
self.delete(dbset, oldkey, instance.srvalue)
|
|
134
136
|
key = self.put(dbset, newkey, instance.newrecord.srvalue)
|
|
135
137
|
if key is not None:
|
|
136
|
-
|
|
137
138
|
# put was append to record number database and
|
|
138
139
|
# returned the new primary key. Adjust record key
|
|
139
140
|
# for secondary updates.
|
|
@@ -223,7 +224,6 @@ class Database:
|
|
|
223
224
|
putkey = instance.key.pack()
|
|
224
225
|
instance.set_packed_value_and_indexes()
|
|
225
226
|
if putkey is None:
|
|
226
|
-
|
|
227
227
|
# reuse record number if possible
|
|
228
228
|
putkey = self.get_lowest_freed_record_number(dbset)
|
|
229
229
|
if putkey is not None:
|
|
@@ -231,7 +231,6 @@ class Database:
|
|
|
231
231
|
|
|
232
232
|
key = self.put(dbset, putkey, instance.srvalue)
|
|
233
233
|
if key is not None:
|
|
234
|
-
|
|
235
234
|
# put was append to record number database and
|
|
236
235
|
# returned the new primary key. Adjust record key
|
|
237
236
|
# for secondary updates.
|
|
@@ -379,6 +378,72 @@ class Database:
|
|
|
379
378
|
"""Do nothing, present for compatibility with DPT."""
|
|
380
379
|
del kwargs
|
|
381
380
|
|
|
381
|
+
def get_database_table_sizes(self, **kwargs):
|
|
382
|
+
"""Return empty dict, present for compatibility with DPT."""
|
|
383
|
+
del kwargs
|
|
384
|
+
return {}
|
|
385
|
+
|
|
386
|
+
def close_datasourcecursor_recordset(self, datasourcecursor):
|
|
387
|
+
"""Do nothing. Override if necessary.
|
|
388
|
+
|
|
389
|
+
Present for compatibility with DPT database engine.
|
|
390
|
+
"""
|
|
391
|
+
del datasourcecursor
|
|
392
|
+
|
|
393
|
+
def set_datasourcecursor_recordset(self, datasourcecursor, recordset):
|
|
394
|
+
"""Validate and set recordset as datasourcecursor's recordset.
|
|
395
|
+
|
|
396
|
+
The recordset and datasourcecursor must be associated with the same
|
|
397
|
+
database identity and be the identity of self.
|
|
398
|
+
|
|
399
|
+
This method exists because in DPT these checks are implied when the
|
|
400
|
+
recordset and cursor are created, and both objects are details of
|
|
401
|
+
the implementation.
|
|
402
|
+
"""
|
|
403
|
+
if datasourcecursor.dbhome is not self:
|
|
404
|
+
raise DataSourceCursorError("DataSource is not for this database")
|
|
405
|
+
if datasourcecursor.recordset:
|
|
406
|
+
if (
|
|
407
|
+
datasourcecursor.recordset.dbidentity
|
|
408
|
+
== recordset.recordset.dbidentity
|
|
409
|
+
):
|
|
410
|
+
datasourcecursor.recordset.recordset.close()
|
|
411
|
+
datasourcecursor.recordset = recordset
|
|
412
|
+
else:
|
|
413
|
+
raise DataSourceCursorError(
|
|
414
|
+
"New and existing Recordsets are for different databases"
|
|
415
|
+
)
|
|
416
|
+
elif datasourcecursor.dbidentity == recordset.recordset.dbidentity:
|
|
417
|
+
datasourcecursor.recordset = recordset
|
|
418
|
+
else:
|
|
419
|
+
raise DataSourceCursorError(
|
|
420
|
+
"New Recordset and DataSource are for different databases"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def get_datasourcecursor_recordset_cursor(self, dsc):
|
|
424
|
+
"""Create and return cursor on dsc's recordset.
|
|
425
|
+
|
|
426
|
+
dsc not datasourcecursor to shorten argument name.
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
if dsc.dbhome is not self:
|
|
430
|
+
raise DataSourceCursorError("DataSource is not for this database")
|
|
431
|
+
if dsc.recordset:
|
|
432
|
+
if dsc.dbidentity == dsc.recordset.recordset.dbidentity:
|
|
433
|
+
cursor = dsc.recordset.dbhome.create_recordset_cursor(
|
|
434
|
+
dsc.recordset.recordset
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
raise DataSourceCursorError(
|
|
438
|
+
"Recordset and DataSource are for different databases"
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
dsc.recordset = dsc.dbhome.recordlist_nil(dsc.dbset)
|
|
442
|
+
cursor = dsc.recordset.dbhome.create_recordset_cursor(
|
|
443
|
+
dsc.recordset.recordset
|
|
444
|
+
)
|
|
445
|
+
return cursor
|
|
446
|
+
|
|
382
447
|
|
|
383
448
|
class ExistenceBitmapControl:
|
|
384
449
|
"""Base class for managing existence bitmap of file in database.
|
|
@@ -13,6 +13,7 @@ from . import _database
|
|
|
13
13
|
from .segmentsize import SegmentSize
|
|
14
14
|
from .constants import SECONDARY
|
|
15
15
|
from .bytebit import Bitarray
|
|
16
|
+
from . import merge
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class DatabaseduError(Exception):
|
|
@@ -22,25 +23,6 @@ class DatabaseduError(Exception):
|
|
|
22
23
|
class Database(_database.Database):
|
|
23
24
|
"""Provide deferred update versions of the record update methods."""
|
|
24
25
|
|
|
25
|
-
# Deferred updates are done inside transactions if possible, despite
|
|
26
|
-
# it being slower, so backup should not be necessary in most cases.
|
|
27
|
-
# Where deferred update within transactions is not available, or is
|
|
28
|
-
# wanted perhaps, a subclass of _databasedu.Database should override
|
|
29
|
-
# this attribute to 'True'.
|
|
30
|
-
# This and it's property, and methods archive and delete_archive are
|
|
31
|
-
# duplicated in the dptdu_database.Database hierarchy: enough for a
|
|
32
|
-
# shared superclass for default backup stuff.
|
|
33
|
-
_take_backup_before_deferred_update = False
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def take_backup_before_deferred_update(self):
|
|
37
|
-
"""Return True if temporary backups should protect deferred update.
|
|
38
|
-
|
|
39
|
-
It is expected the archive and delete_archive methods will do this.
|
|
40
|
-
|
|
41
|
-
"""
|
|
42
|
-
return self._take_backup_before_deferred_update
|
|
43
|
-
|
|
44
26
|
def put_instance(self, dbset, instance):
|
|
45
27
|
"""Put new instance on database dbset.
|
|
46
28
|
|
|
@@ -66,7 +48,7 @@ class Database(_database.Database):
|
|
|
66
48
|
instance.srkey = self.encode_record_number(putkey)
|
|
67
49
|
srindex = instance.srindex
|
|
68
50
|
segment, record_number = divmod(putkey, SegmentSize.db_segment_size)
|
|
69
|
-
self.
|
|
51
|
+
self._defer_add_record_to_ebm(dbset, segment, record_number)
|
|
70
52
|
pcb = instance.putcallbacks
|
|
71
53
|
for secondary in srindex:
|
|
72
54
|
if secondary not in self.specification[dbset][SECONDARY]:
|
|
@@ -74,12 +56,12 @@ class Database(_database.Database):
|
|
|
74
56
|
pcb[secondary](instance, srindex[secondary])
|
|
75
57
|
continue
|
|
76
58
|
for j in srindex[secondary]:
|
|
77
|
-
self.
|
|
59
|
+
self._defer_add_record_to_field_value(
|
|
78
60
|
dbset, secondary, j, segment, record_number
|
|
79
61
|
)
|
|
80
62
|
|
|
81
63
|
if record_number in self.deferred_update_points:
|
|
82
|
-
self.
|
|
64
|
+
self._write_existence_bit_map(dbset, segment)
|
|
83
65
|
for secondary in self.specification[dbset][SECONDARY]:
|
|
84
66
|
self.sort_and_write(dbset, secondary, segment)
|
|
85
67
|
if record_number == max(self.deferred_update_points):
|
|
@@ -88,7 +70,45 @@ class Database(_database.Database):
|
|
|
88
70
|
self.first_chunk[dbset] = False
|
|
89
71
|
self.high_segment[dbset] = segment
|
|
90
72
|
|
|
91
|
-
def
|
|
73
|
+
def index_instance(self, dbset, instance):
|
|
74
|
+
"""Apply instance index values on database dbset.
|
|
75
|
+
|
|
76
|
+
This method assumes all primary databases are integer primary key,
|
|
77
|
+
and there is enough memory to do a segment at a time.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
putkey = instance.key.pack()
|
|
81
|
+
if putkey is None:
|
|
82
|
+
# Apply index values without record number is not allowed.
|
|
83
|
+
raise DatabaseduError(
|
|
84
|
+
"Cannot apply index values without a record number"
|
|
85
|
+
)
|
|
86
|
+
instance.set_packed_value_and_indexes()
|
|
87
|
+
instance.srkey = self.encode_record_number(putkey)
|
|
88
|
+
srindex = instance.srindex
|
|
89
|
+
segment, record_number = divmod(putkey, SegmentSize.db_segment_size)
|
|
90
|
+
pcb = instance.putcallbacks
|
|
91
|
+
for secondary in srindex:
|
|
92
|
+
if secondary not in self.specification[dbset][SECONDARY]:
|
|
93
|
+
if secondary in pcb:
|
|
94
|
+
pcb[secondary](instance, srindex[secondary])
|
|
95
|
+
continue
|
|
96
|
+
for j in srindex[secondary]:
|
|
97
|
+
self._defer_add_record_to_field_value(
|
|
98
|
+
dbset, secondary, j, segment, record_number
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if record_number in self.deferred_update_points:
|
|
102
|
+
for secondary in srindex:
|
|
103
|
+
assert secondary in self.specification[dbset][SECONDARY]
|
|
104
|
+
self.sort_and_write(dbset, secondary, segment)
|
|
105
|
+
if record_number == max(self.deferred_update_points):
|
|
106
|
+
self.first_chunk[dbset] = True
|
|
107
|
+
elif record_number == min(self.deferred_update_points):
|
|
108
|
+
self.first_chunk[dbset] = False
|
|
109
|
+
self.high_segment[dbset] = segment
|
|
110
|
+
|
|
111
|
+
def _defer_add_record_to_ebm(self, file, segment, record_number):
|
|
92
112
|
"""Add bit to existence bit map for new record and defer update."""
|
|
93
113
|
assert file in self.specification
|
|
94
114
|
try:
|
|
@@ -110,7 +130,7 @@ class Database(_database.Database):
|
|
|
110
130
|
self.existence_bit_maps[file] = {}
|
|
111
131
|
self.existence_bit_maps[file][segment] = ebm
|
|
112
132
|
|
|
113
|
-
def
|
|
133
|
+
def _defer_add_record_to_field_value(
|
|
114
134
|
self, file, field, key, segment, record_number
|
|
115
135
|
):
|
|
116
136
|
"""Add record_number to cached segment for key."""
|
|
@@ -126,7 +146,6 @@ class Database(_database.Database):
|
|
|
126
146
|
if values is None:
|
|
127
147
|
value_segments[key] = [record_number]
|
|
128
148
|
elif isinstance(values, list):
|
|
129
|
-
|
|
130
149
|
# A (value, record_number) can be given many times.
|
|
131
150
|
# Ensure a record_number appears in the list once only.
|
|
132
151
|
if values[-1] != record_number:
|
|
@@ -159,7 +178,6 @@ class Database(_database.Database):
|
|
|
159
178
|
for k in segvalues:
|
|
160
179
|
value = segvalues[k]
|
|
161
180
|
if isinstance(value, list):
|
|
162
|
-
|
|
163
181
|
# A single record is presented as an integer: the
|
|
164
182
|
# database engine will decide the transformation.
|
|
165
183
|
if len(value) == 1:
|
|
@@ -191,3 +209,64 @@ class Database(_database.Database):
|
|
|
191
209
|
Actions are specific to a database engine.
|
|
192
210
|
|
|
193
211
|
"""
|
|
212
|
+
|
|
213
|
+
def encode_number_for_sequential_file_dump(self, number, bytes_):
|
|
214
|
+
"""Return big-endian encoding of number in bytes_ bytes."""
|
|
215
|
+
return number.to_bytes(bytes_, byteorder="big")
|
|
216
|
+
|
|
217
|
+
def encode_segment_for_sequential_file_dump(self, record_numbers):
|
|
218
|
+
"""Return encoding of record numbers appropriate to record count."""
|
|
219
|
+
if len(record_numbers) > SegmentSize.db_upper_conversion_limit:
|
|
220
|
+
seg = SegmentSize.empty_bitarray.copy()
|
|
221
|
+
for bit in record_numbers:
|
|
222
|
+
seg[bit] = True
|
|
223
|
+
return seg.tobytes()
|
|
224
|
+
int_to_bytes = self._int_to_bytes
|
|
225
|
+
return b"".join([int_to_bytes[n] for n in record_numbers])
|
|
226
|
+
|
|
227
|
+
def set_int_to_bytes_lookup(self, lookup=True):
|
|
228
|
+
"""Create lookup to convert integers to bytestring big-endian.
|
|
229
|
+
|
|
230
|
+
Set lookup table to None if bool(lookup) is False.
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
if lookup:
|
|
234
|
+
self._int_to_bytes = [
|
|
235
|
+
n.to_bytes(2, byteorder="big")
|
|
236
|
+
for n in range(SegmentSize.db_segment_size)
|
|
237
|
+
]
|
|
238
|
+
else:
|
|
239
|
+
self._int_to_bytes = None
|
|
240
|
+
|
|
241
|
+
def merge_import(self, index_directory, file, field, commit_limit):
|
|
242
|
+
"""Yield count of sorted items written to an index at intervals.
|
|
243
|
+
|
|
244
|
+
Sorted items from files in index_directory are written to index
|
|
245
|
+
field in file, and the count of items added is yielded every
|
|
246
|
+
commit_limit items.
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
writer = self.merge_writer(file, field)
|
|
250
|
+
merger = merge.Merge(index_directory)
|
|
251
|
+
commit_count = None
|
|
252
|
+
for commit_count, item in enumerate(merger.sorter()):
|
|
253
|
+
if not commit_count % commit_limit:
|
|
254
|
+
if commit_count:
|
|
255
|
+
writer.close_cursor()
|
|
256
|
+
self.commit()
|
|
257
|
+
self.deferred_update_housekeeping()
|
|
258
|
+
yield commit_count
|
|
259
|
+
self.start_transaction()
|
|
260
|
+
writer.make_new_cursor()
|
|
261
|
+
writer.write(item)
|
|
262
|
+
writer.close_cursor()
|
|
263
|
+
if commit_count is not None:
|
|
264
|
+
self.commit()
|
|
265
|
+
self.deferred_update_housekeeping()
|
|
266
|
+
self.start_transaction()
|
|
267
|
+
|
|
268
|
+
def next_sorted_item(self, index_directory):
|
|
269
|
+
"""Yield sorted items from files in index_directory."""
|
|
270
|
+
merger = merge.Merge(index_directory)
|
|
271
|
+
for item in merger.sorter():
|
|
272
|
+
yield item
|