solentware-base 5.2.2__tar.gz → 5.2.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.2.3}/PKG-INFO +4 -19
  2. {solentware_base-5.2.2 → solentware_base-5.2.3}/pyproject.toml +4 -4
  3. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_database.py +4 -0
  4. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_databasedu.py +1 -1
  5. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_db.py +5 -2
  6. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_db_tkinter.py +5 -2
  7. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_dbdu.py +21 -8
  8. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_dbdu_tkinter.py +21 -8
  9. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_dpt.py +6 -1
  10. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_lmdb.py +1 -1
  11. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_lmdbdu.py +21 -8
  12. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_nosql.py +31 -3
  13. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_nosqldu.py +1 -1
  14. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_sqlite.py +103 -63
  15. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_sqlitedu.py +1 -1
  16. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/merge.py +6 -3
  17. {solentware_base-5.2.2 → solentware_base-5.2.3/solentware_base.egg-info}/PKG-INFO +4 -19
  18. {solentware_base-5.2.2 → solentware_base-5.2.3}/LICENCE +0 -0
  19. {solentware_base-5.2.2 → solentware_base-5.2.3}/MANIFEST.in +0 -0
  20. {solentware_base-5.2.2 → solentware_base-5.2.3}/README +0 -0
  21. {solentware_base-5.2.2 → solentware_base-5.2.3}/setup.cfg +0 -0
  22. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/__init__.py +0 -0
  23. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/apsw_database.py +0 -0
  24. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/apswdu_database.py +0 -0
  25. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/berkeleydb_database.py +0 -0
  26. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/berkeleydbdu_database.py +0 -0
  27. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/bsddb3_database.py +0 -0
  28. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/bsddb3du_database.py +0 -0
  29. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/__init__.py +0 -0
  30. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/_bytebit.py +0 -0
  31. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/bytebit.py +0 -0
  32. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/constants.py +0 -0
  33. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/cursor.py +0 -0
  34. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/filespec.py +0 -0
  35. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/find.py +0 -0
  36. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/findvalues.py +0 -0
  37. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/record.py +0 -0
  38. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/recordset.py +0 -0
  39. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/recordsetbasecursor.py +0 -0
  40. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/recordsetcursor.py +0 -0
  41. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/segmentsize.py +0 -0
  42. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/tree.py +0 -0
  43. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/where.py +0 -0
  44. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/core/wherevalues.py +0 -0
  45. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/db_tcl.py +0 -0
  46. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/db_tkinter_database.py +0 -0
  47. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/db_tkinterdu_database.py +0 -0
  48. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/dpt_database.py +0 -0
  49. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/dptdu_database.py +0 -0
  50. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/dptfastload_database.py +0 -0
  51. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/gnu_database.py +0 -0
  52. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/gnu_module.py +0 -0
  53. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/gnudu_database.py +0 -0
  54. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/lmdb_database.py +0 -0
  55. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/lmdbdu_database.py +0 -0
  56. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/modulequery.py +0 -0
  57. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/ndbm_database.py +0 -0
  58. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/ndbm_module.py +0 -0
  59. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/ndbmdu_database.py +0 -0
  60. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/sqlite3_database.py +0 -0
  61. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/sqlite3du_database.py +0 -0
  62. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/__init__.py +0 -0
  63. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/base_3_to_4.py +0 -0
  64. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/base_3_to_4_db.py +0 -0
  65. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/base_3_to_4_sqlite.py +0 -0
  66. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/berkeleydb_du_splice_fix.py +0 -0
  67. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/compare_dpt_tape_files.py +0 -0
  68. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/create_database.py +0 -0
  69. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/sqlite_table_name_upgrade.py +0 -0
  70. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/traverse_dpt_tape_files.py +0 -0
  71. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/tools/ui_base_3_to_4.py +0 -0
  72. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/unqlite_database.py +0 -0
  73. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/unqlitedu_database.py +0 -0
  74. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/vedis_database.py +0 -0
  75. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base/vedisdu_database.py +0 -0
  76. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base.egg-info/SOURCES.txt +0 -0
  77. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base.egg-info/dependency_links.txt +0 -0
  78. {solentware_base-5.2.2 → solentware_base-5.2.3}/solentware_base.egg-info/top_level.txt +0 -0
  79. {solentware_base-5.2.2 → solentware_base-5.2.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.2.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.2.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
 
@@ -63,7 +63,7 @@ _openbsd_platform = sys.platform.startswith("openbsd")
63
63
  del sys
64
64
 
65
65
 
66
- class DatabaseError(Exception):
66
+ class DatabaseError(_database.DatabaseError):
67
67
  """Exception for Database class."""
68
68
 
69
69
 
@@ -549,7 +549,10 @@ class Database(_database.Database):
549
549
  for field in specification[SECONDARY]:
550
550
  secondary = SUBFILE_DELIMITER.join((file, field))
551
551
  if secondary in self.table:
552
- self.table[secondary].close()
552
+ try:
553
+ self.table[secondary].close()
554
+ except AttributeError:
555
+ pass
553
556
  self.table[secondary] = None
554
557
  for k, dbo in self.table.items():
555
558
  if dbo is not None:
@@ -62,7 +62,7 @@ _openbsd_platform = sys.platform.startswith("openbsd")
62
62
  del sys
63
63
 
64
64
 
65
- class DatabaseError(Exception):
65
+ class DatabaseError(_database.DatabaseError):
66
66
  """Exception for Database class."""
67
67
 
68
68
 
@@ -713,7 +713,10 @@ class Database(_database.Database):
713
713
  # careless about deleting closed secondary database DB
714
714
  # objects in deferred update mode. Exceptions occur
715
715
  # here if that is allowed to happen with the Tcl API.
716
- tcl_tk_call((self.table[secondary], "close"))
716
+ try:
717
+ tcl_tk_call((self.table[secondary], "close"))
718
+ except AttributeError:
719
+ pass
717
720
  self.table[secondary] = None
718
721
  for k, dbo in self.table.items():
719
722
  if dbo is not None:
@@ -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()
@@ -38,7 +38,7 @@ from .recordset import (
38
38
  )
39
39
 
40
40
 
41
- class DatabaseError(Exception):
41
+ class DatabaseError(_database.DatabaseError):
42
42
  """Exception for Database class."""
43
43
 
44
44
 
@@ -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
@@ -59,7 +59,7 @@ from .recordset import (
59
59
  )
60
60
 
61
61
 
62
- class DatabaseError(Exception):
62
+ class DatabaseError(_database.DatabaseError):
63
63
  """Exception for Database class."""
64
64
 
65
65
 
@@ -293,6 +293,12 @@ class Database(_database.Database):
293
293
  (self.table[CONTROL_FILE], FIELD_REGISTER_KEY.decode())
294
294
  )
295
295
 
296
+ # These two moved from the try clauses just below to keep the tests
297
+ # with memory-only databases passing. Implication is memory-only
298
+ # databases behave differently when adding fields, and the tests no
299
+ # longer truly test the code.
300
+ rsk = None
301
+ rssbk = None
296
302
  # The ___control table should be present already if the file exists.
297
303
  if self.database_file is not None:
298
304
  dbenv = dbclass(self.database_file)
@@ -300,11 +306,11 @@ class Database(_database.Database):
300
306
  if specification_key in dbenv:
301
307
  rsk = dbenv[specification_key]
302
308
  else:
303
- rsk = None
309
+ pass
304
310
  if segment_size_bytes_key in dbenv:
305
311
  rssbk = dbenv[segment_size_bytes_key]
306
312
  else:
307
- rssbk = None
313
+ pass
308
314
  if rsk is not None and rssbk is not None:
309
315
  spec_from_db = literal_eval(rsk.decode())
310
316
  if self._use_specification_items is not None:
@@ -383,6 +389,28 @@ class Database(_database.Database):
383
389
  dbenv = dbclass()
384
390
  dbenv.disable_autocommit()
385
391
 
392
+ if rsk:
393
+ if len(table_register) != len(self.specification) + 1:
394
+ raise DatabaseError(
395
+ "Specification and table register sizes inconsistent"
396
+ )
397
+ if set(self.specification.keys()).difference(table_register):
398
+ raise DatabaseError(
399
+ "Specification and table register content inconsistent"
400
+ )
401
+ for speckey in self.specification:
402
+ if len(self.specification[speckey][SECONDARY]) > len(
403
+ field_register[speckey]
404
+ ):
405
+ raise DatabaseError(
406
+ "Specification and field register size inconsistent"
407
+ )
408
+ if set(self.specification[speckey][SECONDARY]).difference(
409
+ set(field_register[speckey])
410
+ ):
411
+ raise DatabaseError(
412
+ "Specification and field register content inconsistent"
413
+ )
386
414
  self.set_segment_size()
387
415
  self.dbenv = dbenv
388
416
  if files is None:
@@ -26,7 +26,7 @@ from . import _databasedu
26
26
  from .recordset import RecordsetSegmentList
27
27
 
28
28
 
29
- class DatabaseError(Exception):
29
+ class DatabaseError(_databasedu.DatabaseduError):
30
30
  """Exception for Database class."""
31
31
 
32
32
 
@@ -47,7 +47,7 @@ from .recordset import (
47
47
  )
48
48
 
49
49
 
50
- class DatabaseError(Exception):
50
+ class DatabaseError(_database.DatabaseError):
51
51
  """Exception for Database class."""
52
52
 
53
53
 
@@ -161,6 +161,12 @@ class Database(_database.Database):
161
161
  if not os.path.isdir(self.home_directory):
162
162
  raise
163
163
 
164
+ # These two moved from the try clauses just below to keep the tests
165
+ # with memory-only databases passing. Implication is memory-only
166
+ # databases behave differently when adding fields, and the tests no
167
+ # longer truly test the code.
168
+ rsk = None
169
+ rssbk = None
164
170
  # The ___control table should be present already if the file exists.
165
171
  if self.database_file is not None:
166
172
  dbenv = dbe.Connection(self.database_file)
@@ -180,12 +186,12 @@ class Database(_database.Database):
180
186
  cursor.execute(statement, (SPECIFICATION_KEY,))
181
187
  rsk = cursor.fetchall()
182
188
  except Exception:
183
- rsk = None
189
+ pass
184
190
  try:
185
191
  cursor.execute(statement, (SEGMENT_SIZE_BYTES_KEY,))
186
192
  rssbk = cursor.fetchall()
187
193
  except Exception:
188
- rssbk = None
194
+ pass
189
195
  if rsk is not None and rssbk is not None:
190
196
  spec_from_db = literal_eval(rsk[0][0])
191
197
  if self._use_specification_items is not None:
@@ -230,91 +236,98 @@ class Database(_database.Database):
230
236
  self.start_transaction()
231
237
  cursor = self.dbenv.cursor()
232
238
  self.table[CONTROL_FILE] = CONTROL_FILE
233
- statement = " ".join(
234
- (
235
- create_table,
236
- CONTROL_FILE,
237
- "(",
238
- CONTROL_FILE,
239
- ",",
240
- SQLITE_VALUE_COLUMN,
241
- ",",
242
- "primary key",
243
- "(",
244
- CONTROL_FILE,
245
- ",",
246
- SQLITE_VALUE_COLUMN,
247
- ") )",
248
- )
249
- )
250
- cursor.execute(statement)
251
- for file, specification in self.specification.items():
252
- if file not in files:
253
- continue
254
- fields = specification[SECONDARY]
255
- self.table[file] = file
239
+ if rsk is None:
256
240
  statement = " ".join(
257
241
  (
258
242
  create_table,
259
- self.table[file],
243
+ CONTROL_FILE,
260
244
  "(",
261
- file,
262
- db_key,
245
+ CONTROL_FILE,
246
+ ",",
263
247
  SQLITE_VALUE_COLUMN,
264
- ")",
265
- )
266
- )
267
- cursor.execute(statement)
268
- self.ebm_control[file] = ExistenceBitmapControl(file, self)
269
- segmentfile = SUBFILE_DELIMITER.join((file, SEGMENT_SUFFIX))
270
- self.segment_table[file] = segmentfile
271
- statement = " ".join(
272
- (
273
- create_table,
274
- segmentfile,
248
+ ",",
249
+ "primary key",
275
250
  "(",
276
- SQLITE_RECORDS_COLUMN,
277
- ")",
251
+ CONTROL_FILE,
252
+ ",",
253
+ SQLITE_VALUE_COLUMN,
254
+ ") )",
278
255
  )
279
256
  )
280
257
  cursor.execute(statement)
281
- for field in fields:
282
- secondary = SUBFILE_DELIMITER.join((file, field))
283
- self.table[secondary] = secondary
258
+ for file, specification in self.specification.items():
259
+ if file not in files:
260
+ continue
261
+ fields = specification[SECONDARY]
262
+ self.table[file] = file
263
+ if rsk is None:
284
264
  statement = " ".join(
285
265
  (
286
266
  create_table,
287
- secondary,
267
+ self.table[file],
288
268
  "(",
289
- field,
290
- ",",
291
- SQLITE_SEGMENT_COLUMN,
292
- ",",
293
- SQLITE_COUNT_COLUMN,
294
- ",",
295
269
  file,
270
+ db_key,
271
+ SQLITE_VALUE_COLUMN,
296
272
  ")",
297
273
  )
298
274
  )
299
275
  cursor.execute(statement)
300
- indexname = "".join(
301
- (INDEXPREFIX, SUBFILE_DELIMITER.join((file, field)))
302
- )
303
- self.index[secondary] = indexname
276
+ self.ebm_control[file] = ExistenceBitmapControl(file, self)
277
+ segmentfile = SUBFILE_DELIMITER.join((file, SEGMENT_SUFFIX))
278
+ self.segment_table[file] = segmentfile
279
+ if rsk is None:
304
280
  statement = " ".join(
305
281
  (
306
- db_create_index,
307
- indexname,
308
- "on",
309
- secondary,
282
+ create_table,
283
+ segmentfile,
310
284
  "(",
311
- field,
312
- ",",
313
- SQLITE_SEGMENT_COLUMN,
285
+ SQLITE_RECORDS_COLUMN,
314
286
  ")",
315
287
  )
316
288
  )
317
289
  cursor.execute(statement)
290
+ for field in fields:
291
+ secondary = SUBFILE_DELIMITER.join((file, field))
292
+ self.table[secondary] = secondary
293
+ if rsk is None:
294
+ statement = " ".join(
295
+ (
296
+ create_table,
297
+ secondary,
298
+ "(",
299
+ field,
300
+ ",",
301
+ SQLITE_SEGMENT_COLUMN,
302
+ ",",
303
+ SQLITE_COUNT_COLUMN,
304
+ ",",
305
+ file,
306
+ ")",
307
+ )
308
+ )
309
+ cursor.execute(statement)
310
+ else:
311
+ self._raise_if_no_object("table", secondary)
312
+ indexname = "".join(
313
+ (INDEXPREFIX, SUBFILE_DELIMITER.join((file, field)))
314
+ )
315
+ self.index[secondary] = indexname
316
+ if rsk is None:
317
+ statement = " ".join(
318
+ (
319
+ db_create_index,
320
+ indexname,
321
+ "on",
322
+ secondary,
323
+ "(",
324
+ field,
325
+ ",",
326
+ SQLITE_SEGMENT_COLUMN,
327
+ ")",
328
+ )
329
+ )
330
+ cursor.execute(statement)
318
331
  if self.database_file is not None:
319
332
  if rsk is None and rssbk is None:
320
333
  statement = " ".join(
@@ -338,6 +351,33 @@ class Database(_database.Database):
338
351
  )
339
352
  self.commit()
340
353
 
354
+ def _raise_if_no_object(self, type_, name):
355
+ """Raise DatabaseError if object of type_ and name does not exist."""
356
+ statement = " ".join(
357
+ (
358
+ "select name from sqlite_master where type",
359
+ "== ?",
360
+ "and name",
361
+ "== ?",
362
+ )
363
+ )
364
+ cursor = self.dbenv.cursor()
365
+ try:
366
+ if cursor.execute(statement, (type_, name)).fetchone() is None:
367
+ raise DatabaseError(
368
+ "".join(
369
+ (
370
+ "Database does not have '",
371
+ type_,
372
+ "' named '",
373
+ name,
374
+ "'",
375
+ )
376
+ )
377
+ )
378
+ finally:
379
+ cursor.close()
380
+
341
381
  def close_database_contexts(self, files=None):
342
382
  """Close files in database.
343
383
 
@@ -27,7 +27,7 @@ from .recordset import (
27
27
  )
28
28
 
29
29
 
30
- class DatabaseError(Exception):
30
+ class DatabaseError(_databasedu.DatabaseduError):
31
31
  """Exception for Database class."""
32
32
 
33
33
 
@@ -110,9 +110,12 @@ class SortIndiciesToSequentialFiles:
110
110
  reference.clear()
111
111
  count = (self.segment + 1) * SegmentSize.db_segment_size
112
112
  self.segment = segment
113
- for index, reference in self.indicies.items():
114
- for item in value[index]:
115
- reference.setdefault(item, []).append(key)
113
+ indicies = self.indicies
114
+ for index, values in value.items():
115
+ reference = indicies.get(index)
116
+ if reference is not None:
117
+ for item in values:
118
+ reference.setdefault(item, []).append(key)
116
119
  return count
117
120
 
118
121
  def write_segment_to_sequential_file(self, index, reference):
@@ -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.2.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
File without changes
File without changes
File without changes