solentware-base 5.2.2__tar.gz → 5.3__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.
Files changed (79) hide show
  1. {solentware_base-5.2.2/solentware_base.egg-info → solentware_base-5.3}/PKG-INFO +4 -19
  2. {solentware_base-5.2.2 → solentware_base-5.3}/pyproject.toml +4 -4
  3. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_database.py +4 -0
  4. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_databasedu.py +13 -1
  5. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_db.py +28 -2
  6. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_db_tkinter.py +39 -2
  7. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_dbdu.py +21 -8
  8. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_dbdu_tkinter.py +21 -8
  9. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_dpt.py +6 -1
  10. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_lmdb.py +23 -1
  11. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_lmdbdu.py +21 -8
  12. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_nosql.py +31 -3
  13. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_nosqldu.py +1 -1
  14. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_sqlite.py +155 -65
  15. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_sqlitedu.py +1 -1
  16. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/constants.py +1 -0
  17. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/merge.py +8 -5
  18. {solentware_base-5.2.2 → solentware_base-5.3/solentware_base.egg-info}/PKG-INFO +4 -19
  19. {solentware_base-5.2.2 → solentware_base-5.3}/LICENCE +0 -0
  20. {solentware_base-5.2.2 → solentware_base-5.3}/MANIFEST.in +0 -0
  21. {solentware_base-5.2.2 → solentware_base-5.3}/README +0 -0
  22. {solentware_base-5.2.2 → solentware_base-5.3}/setup.cfg +0 -0
  23. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/__init__.py +0 -0
  24. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/apsw_database.py +0 -0
  25. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/apswdu_database.py +0 -0
  26. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/berkeleydb_database.py +0 -0
  27. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/berkeleydbdu_database.py +0 -0
  28. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/bsddb3_database.py +0 -0
  29. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/bsddb3du_database.py +0 -0
  30. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/__init__.py +0 -0
  31. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/_bytebit.py +0 -0
  32. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/bytebit.py +0 -0
  33. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/cursor.py +0 -0
  34. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/filespec.py +0 -0
  35. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/find.py +0 -0
  36. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/findvalues.py +0 -0
  37. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/record.py +0 -0
  38. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/recordset.py +0 -0
  39. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/recordsetbasecursor.py +0 -0
  40. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/recordsetcursor.py +0 -0
  41. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/segmentsize.py +0 -0
  42. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/tree.py +0 -0
  43. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/where.py +0 -0
  44. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/core/wherevalues.py +0 -0
  45. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/db_tcl.py +0 -0
  46. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/db_tkinter_database.py +0 -0
  47. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/db_tkinterdu_database.py +0 -0
  48. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/dpt_database.py +0 -0
  49. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/dptdu_database.py +0 -0
  50. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/dptfastload_database.py +0 -0
  51. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/gnu_database.py +0 -0
  52. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/gnu_module.py +0 -0
  53. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/gnudu_database.py +0 -0
  54. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/lmdb_database.py +0 -0
  55. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/lmdbdu_database.py +0 -0
  56. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/modulequery.py +0 -0
  57. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/ndbm_database.py +0 -0
  58. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/ndbm_module.py +0 -0
  59. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/ndbmdu_database.py +0 -0
  60. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/sqlite3_database.py +0 -0
  61. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/sqlite3du_database.py +0 -0
  62. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/__init__.py +0 -0
  63. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/base_3_to_4.py +0 -0
  64. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/base_3_to_4_db.py +0 -0
  65. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/base_3_to_4_sqlite.py +0 -0
  66. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/berkeleydb_du_splice_fix.py +0 -0
  67. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/compare_dpt_tape_files.py +0 -0
  68. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/create_database.py +0 -0
  69. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/sqlite_table_name_upgrade.py +0 -0
  70. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/traverse_dpt_tape_files.py +0 -0
  71. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/tools/ui_base_3_to_4.py +0 -0
  72. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/unqlite_database.py +0 -0
  73. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/unqlitedu_database.py +0 -0
  74. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/vedis_database.py +0 -0
  75. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base/vedisdu_database.py +0 -0
  76. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base.egg-info/SOURCES.txt +0 -0
  77. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base.egg-info/dependency_links.txt +0 -0
  78. {solentware_base-5.2.2 → solentware_base-5.3}/solentware_base.egg-info/top_level.txt +0 -0
  79. {solentware_base-5.2.2 → solentware_base-5.3}/tox.ini +0 -0
@@ -1,28 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: solentware-base
3
- Version: 5.2.2
3
+ Version: 5.3
4
4
  Summary: Bitmapped record number databases.
5
5
  Author-email: Roger Marsh <roger.marsh@solentware.co.uk>
6
- License: Licence for solentware_base package
7
- Copyright (c) 2009, Roger Marsh
8
- All rights reserved.
9
-
10
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
11
-
12
-
13
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
14
-
15
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
16
-
17
- Neither the name of the owner nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
18
-
19
-
20
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21
-
6
+ License-Expression: BSD-3-Clause
22
7
  Project-URL: Homepage, http://www.solentware.co.uk
23
8
  Project-URL: Repository, https://github.com/RogerMarsh/solentware-base.git
24
9
  Project-URL: Bug Tracker, https://github.com/RogerMarsh/solentware-base/issues
25
- Classifier: License :: OSI Approved :: BSD License
26
10
  Classifier: Programming Language :: Python :: 3.9
27
11
  Classifier: Programming Language :: Python :: 3.10
28
12
  Classifier: Programming Language :: Python :: 3.11
@@ -32,6 +16,7 @@ Classifier: Topic :: Database
32
16
  Classifier: Intended Audience :: Developers
33
17
  Classifier: Development Status :: 3 - Alpha
34
18
  License-File: LICENCE
19
+ Dynamic: license-file
35
20
 
36
21
  =========================================================================
37
22
  A bitmapped record number database interface for various database engines
@@ -1,6 +1,6 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools>=61.0",
3
+ "setuptools>=77.0.3",
4
4
  "wheel"
5
5
  ]
6
6
  build-backend = "setuptools.build_meta"
@@ -10,13 +10,12 @@ line-length = 79
10
10
 
11
11
  [project]
12
12
  name = "solentware-base"
13
- version = "5.2.2"
13
+ version = "5.3"
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
- "License :: OSI Approved :: BSD License",
20
19
  "Programming Language :: Python :: 3.9",
21
20
  "Programming Language :: Python :: 3.10",
22
21
  "Programming Language :: Python :: 3.11",
@@ -26,7 +25,8 @@ classifiers = [
26
25
  "Intended Audience :: Developers",
27
26
  "Development Status :: 3 - Alpha",
28
27
  ]
29
- license = {file = "LICENCE"}
28
+ license = "BSD-3-Clause"
29
+ license-files = ["LICENCE"]
30
30
  readme = "README"
31
31
 
32
32
  [project.urls]
@@ -24,6 +24,10 @@ from .recordset import (
24
24
  )
25
25
 
26
26
 
27
+ class DatabaseError(Exception):
28
+ """Exception for Database class."""
29
+
30
+
27
31
  class DataSourceCursorError(Exception):
28
32
  """Exception for database-specific DataSourceCursor setting."""
29
33
 
@@ -16,7 +16,7 @@ from .bytebit import Bitarray
16
16
  from . import merge
17
17
 
18
18
 
19
- class DatabaseduError(Exception):
19
+ class DatabaseduError(_database.DatabaseError):
20
20
  """Exception for Database class."""
21
21
 
22
22
 
@@ -270,3 +270,15 @@ class Database(_database.Database):
270
270
  merger = merge.Merge(index_directory)
271
271
  for item in merger.sorter():
272
272
  yield item
273
+
274
+ def get_merge_import_sort_area(self):
275
+ """Return database directory.
276
+
277
+ The directory containing database is used for sorting by default,
278
+ subclasses should override as required.
279
+
280
+ There is little point in doing so unless the sort area is then on
281
+ a different drive or mount point.
282
+
283
+ """
284
+ return self.home_directory
@@ -34,6 +34,7 @@ from .constants import (
34
34
  CONTROL_FILE,
35
35
  DEFAULT_SEGMENT_SIZE_BYTES,
36
36
  SPECIFICATION_KEY,
37
+ APPLICATION_CONTROL_KEY,
37
38
  SEGMENT_SIZE_BYTES_KEY,
38
39
  SEGMENT_HEADER_LENGTH,
39
40
  FIELDS,
@@ -63,7 +64,7 @@ _openbsd_platform = sys.platform.startswith("openbsd")
63
64
  del sys
64
65
 
65
66
 
66
- class DatabaseError(Exception):
67
+ class DatabaseError(_database.DatabaseError):
67
68
  """Exception for Database class."""
68
69
 
69
70
 
@@ -496,6 +497,9 @@ class Database(_database.Database):
496
497
  repr(self.segment_size_bytes).encode(),
497
498
  txn=self.dbtxn,
498
499
  )
500
+ self.table[CONTROL_FILE].put(
501
+ APPLICATION_CONTROL_KEY, repr({}).encode(), txn=self.dbtxn
502
+ )
499
503
  self.commit()
500
504
  self._dbe = dbe
501
505
 
@@ -549,7 +553,10 @@ class Database(_database.Database):
549
553
  for field in specification[SECONDARY]:
550
554
  secondary = SUBFILE_DELIMITER.join((file, field))
551
555
  if secondary in self.table:
552
- self.table[secondary].close()
556
+ try:
557
+ self.table[secondary].close()
558
+ except AttributeError:
559
+ pass
553
560
  self.table[secondary] = None
554
561
  for k, dbo in self.table.items():
555
562
  if dbo is not None:
@@ -1760,6 +1767,25 @@ class Database(_database.Database):
1760
1767
  return super()._generate_database_file_name(name)
1761
1768
  return os.path.join(self.home_directory, name)
1762
1769
 
1770
+ def get_application_control(self):
1771
+ """Return dict of application control items."""
1772
+ value = self.table[CONTROL_FILE].get(
1773
+ APPLICATION_CONTROL_KEY,
1774
+ txn=self.dbtxn,
1775
+ )
1776
+ if value is not None:
1777
+ return literal_eval(value.decode())
1778
+ return {}
1779
+
1780
+ def set_application_control(self, appcontrol):
1781
+ """Set dict of application control items."""
1782
+ self.table[CONTROL_FILE].delete(
1783
+ APPLICATION_CONTROL_KEY, txn=self.dbtxn
1784
+ )
1785
+ self.table[CONTROL_FILE].put(
1786
+ APPLICATION_CONTROL_KEY, repr(appcontrol).encode(), txn=self.dbtxn
1787
+ )
1788
+
1763
1789
 
1764
1790
  class Cursor(_cursor.Cursor):
1765
1791
  """Define a cursor on the underlying database engine dbset.
@@ -33,6 +33,7 @@ from .constants import (
33
33
  CONTROL_FILE,
34
34
  DEFAULT_SEGMENT_SIZE_BYTES,
35
35
  SPECIFICATION_KEY,
36
+ APPLICATION_CONTROL_KEY,
36
37
  SEGMENT_SIZE_BYTES_KEY,
37
38
  SEGMENT_HEADER_LENGTH,
38
39
  FIELDS,
@@ -62,7 +63,7 @@ _openbsd_platform = sys.platform.startswith("openbsd")
62
63
  del sys
63
64
 
64
65
 
65
- class DatabaseError(Exception):
66
+ class DatabaseError(_database.DatabaseError):
66
67
  """Exception for Database class."""
67
68
 
68
69
 
@@ -638,6 +639,11 @@ class Database(_database.Database):
638
639
  ]
639
640
  )
640
641
  tcl_tk_call(tuple(command))
642
+ command = [self.table[CONTROL_FILE], "put"]
643
+ if self.dbtxn:
644
+ command.extend(["-txn", self.dbtxn])
645
+ command.extend([APPLICATION_CONTROL_KEY, repr({}).encode()])
646
+ tcl_tk_call(tuple(command))
641
647
  self.commit()
642
648
 
643
649
  def _run_db_archive(self):
@@ -713,7 +719,10 @@ class Database(_database.Database):
713
719
  # careless about deleting closed secondary database DB
714
720
  # objects in deferred update mode. Exceptions occur
715
721
  # here if that is allowed to happen with the Tcl API.
716
- tcl_tk_call((self.table[secondary], "close"))
722
+ try:
723
+ tcl_tk_call((self.table[secondary], "close"))
724
+ except AttributeError:
725
+ pass
717
726
  self.table[secondary] = None
718
727
  for k, dbo in self.table.items():
719
728
  if dbo is not None:
@@ -2194,6 +2203,34 @@ class Database(_database.Database):
2194
2203
  return super()._generate_database_file_name(name)
2195
2204
  return os.path.join(self.home_directory, name)
2196
2205
 
2206
+ def get_application_control(self):
2207
+ """Return dict of application control items."""
2208
+ command = [self.table[CONTROL_FILE], "get"]
2209
+ if self.dbtxn:
2210
+ command.extend(["-txn", self.dbtxn])
2211
+ command.extend(
2212
+ [
2213
+ APPLICATION_CONTROL_KEY,
2214
+ ]
2215
+ )
2216
+ value = tcl_tk_call(tuple(command))
2217
+ if value is not None:
2218
+ return literal_eval(value.decode())
2219
+ return {}
2220
+
2221
+ def set_application_control(self, appcontrol):
2222
+ """Set dict of application control items."""
2223
+ command = [self.table[CONTROL_FILE], "delete"]
2224
+ if self.dbtxn:
2225
+ command.extend(["-txn", self.dbtxn])
2226
+ command.extend([APPLICATION_CONTROL_KEY])
2227
+ tcl_tk_call(tuple(command))
2228
+ command = [self.table[CONTROL_FILE], "put"]
2229
+ if self.dbtxn:
2230
+ command.extend(["-txn", self.dbtxn])
2231
+ command.extend([APPLICATION_CONTROL_KEY, repr(appcontrol).encode()])
2232
+ tcl_tk_call(tuple(command))
2233
+
2197
2234
 
2198
2235
  class Cursor(_cursor.Cursor):
2199
2236
  """Define a cursor on the underlying database engine dbset.
@@ -21,7 +21,7 @@ from .recordset import (
21
21
  from . import _databasedu
22
22
 
23
23
 
24
- class DatabaseError(Exception):
24
+ class DatabaseError(_databasedu.DatabaseduError):
25
25
  """Exception for Database class."""
26
26
 
27
27
 
@@ -481,7 +481,7 @@ class Database(_databasedu.Database):
481
481
  assert len(record) == 2
482
482
  value, segment = record
483
483
  if len(segment) == 6:
484
- count = b"x00\x01"
484
+ count = b"\x00\x01"
485
485
  reference = segment[4:]
486
486
  else:
487
487
  count = segment[4:6]
@@ -529,17 +529,23 @@ class Database(_databasedu.Database):
529
529
  self.prev_key = item[0]
530
530
  item_type = item.pop(2)
531
531
  if item_type == EXISTING_SEGMENT_REFERENCE:
532
- self.cursor.put(item[0], b"".join(item[1:]), keylast)
533
532
  assert len(item) == 4
533
+ if item[-2] == b"\x00\x01":
534
+ item.pop(-2)
535
+ self.cursor.put(item[0], b"".join(item[1:]), keylast)
536
+ length = len(b"".join(item[1:]))
537
+ assert length == 10 or length == 6
534
538
  return
535
539
  if int.from_bytes(item[-2], byteorder="big") > 1:
536
540
  item[-1] = segment_table.append(
537
541
  item[-1], txn=self.database.dbtxn
538
542
  ).to_bytes(4, byteorder="big")
539
543
  assert len(item) == 4
544
+ assert len(b"".join(item[1:])) == 10
540
545
  else:
541
546
  item.pop(2)
542
547
  assert len(item) == 3
548
+ assert len(b"".join(item[1:])) == 6
543
549
  self.cursor.put(item[0], b"".join(item[1:]), keylast)
544
550
  assert item_type == NEW_SEGMENT_CONTENT
545
551
  return
@@ -547,22 +553,22 @@ class Database(_databasedu.Database):
547
553
  assert item[2] == NEW_SEGMENT_CONTENT
548
554
  del item[2]
549
555
  high = read_high_item_in_index(self.cursor)
550
- existing_segment = make_segment_from_high(
556
+ new_segment = make_segment_from_item(item)
557
+ new_segment |= make_segment_from_high(
551
558
  high, self.database.dbtxn
552
559
  )
553
- new_segment = make_segment_from_item(item)
554
- new_segment |= existing_segment
555
560
  new_segment.normalize()
556
561
  item[-2] = (
557
562
  self.database.encode_number_for_sequential_file_dump(
558
563
  new_segment.count_records(), 2
559
564
  )
560
565
  )
561
- if int.from_bytes(high[-2], byteorder="big") == 1:
566
+ if high[2] == b"\x00\x01":
562
567
  item[-1] = segment_table.append(
563
568
  new_segment.tobytes(), txn=self.database.dbtxn
564
569
  ).to_bytes(4, byteorder="big")
565
570
  self.cursor.delete()
571
+ assert len(b"".join(item[1:])) == 10
566
572
  self.cursor.put(item[0], b"".join(item[1:]), keylast)
567
573
  else:
568
574
  segment_table.put(
@@ -572,14 +578,19 @@ class Database(_databasedu.Database):
572
578
  )
573
579
  self.cursor.delete()
574
580
  item[-1] = high[-1]
581
+ assert len(b"".join(item[1:])) == 10
575
582
  self.cursor.put(item[0], b"".join(item[1:]), keylast)
576
583
  assert len(item) == 4
577
584
  return
578
585
  item_type = item.pop(2)
579
586
  if item_type == EXISTING_SEGMENT_REFERENCE:
587
+ assert len(item) == 4
580
588
  self.prev_key = item[0]
589
+ if item[-2] == b"\x00\x01":
590
+ item.pop(-2)
581
591
  self.cursor.put(item[0], b"".join(item[1:]), keylast)
582
- assert len(item) == 4
592
+ length = len(b"".join(item[1:]))
593
+ assert length == 10 or length == 6
583
594
  return
584
595
  if int.from_bytes(item[-2], byteorder="big") > 1:
585
596
  item[-1] = segment_table.append(
@@ -587,9 +598,11 @@ class Database(_databasedu.Database):
587
598
  txn=self.database.dbtxn,
588
599
  ).to_bytes(4, byteorder="big")
589
600
  assert len(item) == 4
601
+ assert len(b"".join(item[1:])) == 10
590
602
  else:
591
603
  item.pop(2)
592
604
  assert len(item) == 3
605
+ assert len(b"".join(item[1:])) == 6
593
606
  self.cursor.put(item[0], b"".join(item[1:]), keylast)
594
607
  assert item_type == NEW_SEGMENT_CONTENT
595
608
  return
@@ -22,7 +22,7 @@ from .recordset import (
22
22
  from . import _databasedu
23
23
 
24
24
 
25
- class DatabaseError(Exception):
25
+ class DatabaseError(_databasedu.DatabaseduError):
26
26
  """Exception for Database class."""
27
27
 
28
28
 
@@ -576,7 +576,7 @@ class Database(_databasedu.Database):
576
576
  assert len(record) == 1
577
577
  value, segment = record[0]
578
578
  if len(segment) == 6:
579
- count = b"x00\x01"
579
+ count = b"\x00\x01"
580
580
  reference = segment[4:]
581
581
  else:
582
582
  count = segment[4:6]
@@ -664,22 +664,28 @@ class Database(_databasedu.Database):
664
664
  self.prev_key = item[0]
665
665
  item_type = item.pop(2)
666
666
  if item_type == EXISTING_SEGMENT_REFERENCE:
667
+ assert len(item) == 4
668
+ if item[-2] == b"\x00\x01":
669
+ item.pop(-2)
667
670
  tcl_tk_call(
668
671
  tuple(
669
672
  self.write_item_to_index
670
673
  + [item[0], b"".join(item[1:])]
671
674
  )
672
675
  )
673
- assert len(item) == 4
676
+ length = len(b"".join(item[1:]))
677
+ assert length == 10 or length == 6
674
678
  return
675
679
  if int.from_bytes(item[-2], byteorder="big") > 1:
676
680
  item[-1] = tcl_tk_call(
677
681
  tuple(self.write_segment_value + [item[-1]])
678
682
  ).to_bytes(4, byteorder="big")
679
683
  assert len(item) == 4
684
+ assert len(b"".join(item[1:])) == 10
680
685
  else:
681
686
  item.pop(2)
682
687
  assert len(item) == 3
688
+ assert len(b"".join(item[1:])) == 6
683
689
  tcl_tk_call(
684
690
  tuple(
685
691
  self.write_item_to_index
@@ -694,22 +700,22 @@ class Database(_databasedu.Database):
694
700
  high = read_high_item_in_index(
695
701
  self.get_last_record_in_index
696
702
  )
697
- existing_segment = make_segment_from_high(
703
+ new_segment = make_segment_from_item(item)
704
+ new_segment |= make_segment_from_high(
698
705
  high, self.get_segment_value
699
706
  )
700
- new_segment = make_segment_from_item(item)
701
- new_segment |= existing_segment
702
707
  new_segment.normalize()
703
708
  item[-2] = (
704
709
  self.database.encode_number_for_sequential_file_dump(
705
710
  new_segment.count_records(), 2
706
711
  )
707
712
  )
708
- if int.from_bytes(high[-2], byteorder="big") == 1:
713
+ if high[2] == b"\x00\x01":
709
714
  item[-1] = tcl_tk_call(
710
715
  tuple(self.write_segment_value + [item[-1]])
711
716
  ).to_bytes(4, byteorder="big")
712
717
  tcl_tk_call(tuple(self.delete_index_item))
718
+ assert len(b"".join(item[1:])) == 10
713
719
  tcl_tk_call(
714
720
  tuple(
715
721
  self.write_item_to_index
@@ -728,6 +734,7 @@ class Database(_databasedu.Database):
728
734
  )
729
735
  tcl_tk_call(tuple(self.delete_index_item))
730
736
  item[-1] = high[-1]
737
+ assert len(b"".join(item[1:])) == 10
731
738
  tcl_tk_call(
732
739
  tuple(
733
740
  self.write_item_to_index
@@ -738,23 +745,29 @@ class Database(_databasedu.Database):
738
745
  return
739
746
  item_type = item.pop(2)
740
747
  if item_type == EXISTING_SEGMENT_REFERENCE:
748
+ assert len(item) == 4
741
749
  self.prev_key = item[0]
750
+ if item[-2] == b"\x00\x01":
751
+ item.pop(-2)
742
752
  tcl_tk_call(
743
753
  tuple(
744
754
  self.write_item_to_index
745
755
  + [item[0], b"".join(item[1:])]
746
756
  )
747
757
  )
748
- assert len(item) == 4
758
+ length = len(b"".join(item[1:]))
759
+ assert length == 10 or length == 6
749
760
  return
750
761
  if int.from_bytes(item[-2], byteorder="big") > 1:
751
762
  item[-1] = tcl_tk_call(
752
763
  tuple(self.write_segment_value + [item[-1]])
753
764
  ).to_bytes(4, byteorder="big")
754
765
  assert len(item) == 4
766
+ assert len(b"".join(item[1:])) == 10
755
767
  else:
756
768
  item.pop(2)
757
769
  assert len(item) == 3
770
+ assert len(b"".join(item[1:])) == 6
758
771
  tcl_tk_call(
759
772
  tuple(
760
773
  self.write_item_to_index
@@ -63,7 +63,7 @@ FILE_PARAMETER_LIST = (
63
63
  SegmentSize.db_segment_size_bytes = TABLE_B_SIZE
64
64
 
65
65
 
66
- class DatabaseError(Exception):
66
+ class DatabaseError(_database.DatabaseError):
67
67
  """Raise when an exceptional case is encountered in Database class."""
68
68
 
69
69
 
@@ -1292,6 +1292,11 @@ class DPTFile:
1292
1292
  # Open the file for normal use.
1293
1293
  self.open_existing_file(dbenv)
1294
1294
 
1295
+ # Check requested fields exist.
1296
+ # A RuntimeError is raised by GetFieldAtts() if field does not exist.
1297
+ for field in self.fields:
1298
+ self.opencontext.GetFieldAtts(field)
1299
+
1295
1300
  # Permanent instances for efficient file updates.
1296
1301
  self.fieldvalue = dptapi.APIFieldValue()
1297
1302
  self._putrecordcopy = dptapi.APIStoreRecordTemplate()
@@ -18,6 +18,7 @@ from .constants import (
18
18
  DESIGN_FILE,
19
19
  DEFAULT_SEGMENT_SIZE_BYTES,
20
20
  SPECIFICATION_KEY,
21
+ APPLICATION_CONTROL_KEY,
21
22
  SEGMENT_SIZE_BYTES_KEY,
22
23
  SEGMENT_HEADER_LENGTH,
23
24
  DEFAULT_MAP_SIZE,
@@ -38,7 +39,7 @@ from .recordset import (
38
39
  )
39
40
 
40
41
 
41
- class DatabaseError(Exception):
42
+ class DatabaseError(_database.DatabaseError):
42
43
  """Exception for Database class."""
43
44
 
44
45
 
@@ -343,6 +344,7 @@ class Database(_database.Database):
343
344
  SEGMENT_SIZE_BYTES_KEY,
344
345
  repr(self.segment_size_bytes).encode(),
345
346
  )
347
+ cursor.put(APPLICATION_CONTROL_KEY, repr({}).encode())
346
348
  cursor.close()
347
349
  self.table[DESIGN_FILE].close_datastore()
348
350
  self.open_database_contexts()
@@ -387,6 +389,8 @@ class Database(_database.Database):
387
389
  self._real_segment_size_bytes = False
388
390
  if segment_size != self.segment_size_bytes:
389
391
  self._real_segment_size_bytes = segment_size
392
+ self.dbenv.close()
393
+ self.dbenv = None
390
394
  raise self.SegmentSizeError(
391
395
  " ".join(
392
396
  (
@@ -1847,6 +1851,24 @@ class Database(_database.Database):
1847
1851
  ) // DEFAULT_MAP_PAGES
1848
1852
  self.dbenv.set_mapsize(self.map_blocks * DEFAULT_MAP_SIZE)
1849
1853
 
1854
+ def get_application_control(self):
1855
+ """Return dict of application control items."""
1856
+ value = self.dbtxn.transaction.get(
1857
+ APPLICATION_CONTROL_KEY,
1858
+ db=self.table[DESIGN_FILE].datastore,
1859
+ )
1860
+ if value is not None:
1861
+ return literal_eval(value.decode())
1862
+ return {}
1863
+
1864
+ def set_application_control(self, appcontrol):
1865
+ """Set dict of application control items."""
1866
+ self.dbtxn.transaction.put(
1867
+ APPLICATION_CONTROL_KEY,
1868
+ repr(appcontrol).encode(),
1869
+ db=self.table[DESIGN_FILE].datastore,
1870
+ )
1871
+
1850
1872
 
1851
1873
  class _DBtxn:
1852
1874
  """Provide a Symas LMMD read-only or read-write transaction instance.
@@ -19,7 +19,7 @@ from .recordset import (
19
19
  from . import _databasedu
20
20
 
21
21
 
22
- class DatabaseError(Exception):
22
+ class DatabaseError(_databasedu.DatabaseduError):
23
23
  """Exception for Database class."""
24
24
 
25
25
 
@@ -530,7 +530,7 @@ class Database(_databasedu.Database):
530
530
  assert len(record) == 2
531
531
  value, segment = record
532
532
  if len(segment) == 6:
533
- count = b"x00\x01"
533
+ count = b"\x00\x01"
534
534
  reference = segment[4:]
535
535
  else:
536
536
  count = segment[4:6]
@@ -587,17 +587,23 @@ class Database(_databasedu.Database):
587
587
  self.prev_key = item[0]
588
588
  item_type = item.pop(2)
589
589
  if item_type == EXISTING_SEGMENT_REFERENCE:
590
- self.cursor.put(item[0], b"".join(item[1:]))
591
590
  assert len(item) == 4
591
+ if item[-2] == b"\x00\x01":
592
+ item.pop(-2)
593
+ self.cursor.put(item[0], b"".join(item[1:]))
594
+ length = len(b"".join(item[1:]))
595
+ assert length == 10 or length == 6
592
596
  return
593
597
  if int.from_bytes(item[-2], byteorder="big") > 1:
594
598
  item[-1] = write_segment_value(
595
599
  item[-1], self.segment_cursor
596
600
  )
597
601
  assert len(item) == 4
602
+ assert len(b"".join(item[1:])) == 10
598
603
  else:
599
604
  item.pop(2)
600
605
  assert len(item) == 3
606
+ assert len(b"".join(item[1:])) == 6
601
607
  self.cursor.put(item[0], b"".join(item[1:]))
602
608
  assert item_type == NEW_SEGMENT_CONTENT
603
609
  return
@@ -605,22 +611,22 @@ class Database(_databasedu.Database):
605
611
  assert item[2] == NEW_SEGMENT_CONTENT
606
612
  del item[2]
607
613
  high = read_high_item_in_index(self.cursor)
608
- existing_segment = make_segment_from_high(
614
+ new_segment = make_segment_from_item(item)
615
+ new_segment |= make_segment_from_high(
609
616
  high, self.database.dbtxn.transaction
610
617
  )
611
- new_segment = make_segment_from_item(item)
612
- new_segment |= existing_segment
613
618
  new_segment.normalize()
614
619
  item[-2] = (
615
620
  self.database.encode_number_for_sequential_file_dump(
616
621
  new_segment.count_records(), 2
617
622
  )
618
623
  )
619
- if int.from_bytes(item[-2], byteorder="big") == 1:
624
+ if high[2] == b"\x00\x01":
620
625
  item[-1] = write_segment_value(
621
626
  new_segment.tobytes(), self.segment_cursor
622
627
  )
623
628
  self.cursor.delete()
629
+ assert len(b"".join(item[1:])) == 10
624
630
  self.cursor.put(item[0], b"".join(item[1:]))
625
631
  else:
626
632
  self.database.dbtxn.transaction.put(
@@ -630,23 +636,30 @@ class Database(_databasedu.Database):
630
636
  )
631
637
  self.cursor.delete()
632
638
  item[-1] = high[-1]
639
+ assert len(b"".join(item[1:])) == 10
633
640
  self.cursor.put(item[0], b"".join(item[1:]))
634
641
  assert len(item) == 4
635
642
  return
636
643
  item_type = item.pop(2)
637
644
  if item_type == EXISTING_SEGMENT_REFERENCE:
645
+ assert len(item) == 4
638
646
  self.prev_key = item[0]
647
+ if item[-2] == b"\x00\x01":
648
+ item.pop(-2)
639
649
  self.cursor.put(item[0], b"".join(item[1:]))
640
- assert len(item) == 4
650
+ length = len(b"".join(item[1:]))
651
+ assert length == 10 or length == 6
641
652
  return
642
653
  if int.from_bytes(item[-2], byteorder="big") > 1:
643
654
  item[-1] = write_segment_value(
644
655
  item[-1], self.segment_cursor
645
656
  )
646
657
  assert len(item) == 4
658
+ assert len(b"".join(item[1:])) == 10
647
659
  else:
648
660
  item.pop(2)
649
661
  assert len(item) == 3
662
+ assert len(b"".join(item[1:])) == 6
650
663
  self.cursor.put(item[0], b"".join(item[1:]))
651
664
  assert item_type == NEW_SEGMENT_CONTENT
652
665
  return