solentware-base 5.2__tar.gz → 5.2.1__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/solentware_base.egg-info → solentware_base-5.2.1}/PKG-INFO +1 -1
  2. {solentware-base-5.2 → solentware_base-5.2.1}/pyproject.toml +1 -1
  3. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_databasedu.py +3 -3
  4. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_db.py +5 -5
  5. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_db_tkinter.py +7 -5
  6. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_dbdu.py +4 -4
  7. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_dbdu_tkinter.py +4 -4
  8. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_dpt.py +20 -12
  9. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_lmdb.py +5 -5
  10. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_lmdbdu.py +4 -4
  11. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_nosql.py +8 -8
  12. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_nosqldu.py +3 -3
  13. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_sqlite.py +28 -15
  14. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/find.py +4 -2
  15. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/record.py +5 -3
  16. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/where.py +97 -80
  17. {solentware-base-5.2 → solentware_base-5.2.1/solentware_base.egg-info}/PKG-INFO +1 -1
  18. {solentware-base-5.2 → solentware_base-5.2.1}/LICENCE +0 -0
  19. {solentware-base-5.2 → solentware_base-5.2.1}/MANIFEST.in +0 -0
  20. {solentware-base-5.2 → solentware_base-5.2.1}/README +0 -0
  21. {solentware-base-5.2 → solentware_base-5.2.1}/setup.cfg +0 -0
  22. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/__init__.py +0 -0
  23. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/apsw_database.py +0 -0
  24. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/apswdu_database.py +0 -0
  25. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/berkeleydb_database.py +0 -0
  26. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/berkeleydbdu_database.py +0 -0
  27. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/bsddb3_database.py +0 -0
  28. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/bsddb3du_database.py +0 -0
  29. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/__init__.py +0 -0
  30. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_bytebit.py +0 -0
  31. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_database.py +0 -0
  32. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/_sqlitedu.py +0 -0
  33. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/bytebit.py +0 -0
  34. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/constants.py +0 -0
  35. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/cursor.py +0 -0
  36. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/filespec.py +0 -0
  37. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/findvalues.py +0 -0
  38. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/merge.py +0 -0
  39. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/recordset.py +0 -0
  40. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/recordsetbasecursor.py +0 -0
  41. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/recordsetcursor.py +0 -0
  42. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/segmentsize.py +0 -0
  43. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/tree.py +0 -0
  44. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/core/wherevalues.py +0 -0
  45. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/db_tcl.py +0 -0
  46. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/db_tkinter_database.py +0 -0
  47. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/db_tkinterdu_database.py +0 -0
  48. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/dpt_database.py +0 -0
  49. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/dptdu_database.py +0 -0
  50. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/dptfastload_database.py +0 -0
  51. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/gnu_database.py +0 -0
  52. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/gnu_module.py +0 -0
  53. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/gnudu_database.py +0 -0
  54. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/lmdb_database.py +0 -0
  55. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/lmdbdu_database.py +0 -0
  56. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/modulequery.py +0 -0
  57. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/ndbm_database.py +0 -0
  58. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/ndbm_module.py +0 -0
  59. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/ndbmdu_database.py +0 -0
  60. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/sqlite3_database.py +0 -0
  61. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/sqlite3du_database.py +0 -0
  62. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/__init__.py +0 -0
  63. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/base_3_to_4.py +0 -0
  64. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/base_3_to_4_db.py +0 -0
  65. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/base_3_to_4_sqlite.py +0 -0
  66. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/berkeleydb_du_splice_fix.py +0 -0
  67. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/compare_dpt_tape_files.py +0 -0
  68. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/create_database.py +0 -0
  69. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/sqlite_table_name_upgrade.py +0 -0
  70. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/traverse_dpt_tape_files.py +0 -0
  71. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/tools/ui_base_3_to_4.py +0 -0
  72. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/unqlite_database.py +0 -0
  73. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/unqlitedu_database.py +0 -0
  74. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/vedis_database.py +0 -0
  75. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base/vedisdu_database.py +0 -0
  76. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base.egg-info/SOURCES.txt +0 -0
  77. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base.egg-info/dependency_links.txt +0 -0
  78. {solentware-base-5.2 → solentware_base-5.2.1}/solentware_base.egg-info/top_level.txt +0 -0
  79. {solentware-base-5.2 → solentware_base-5.2.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: solentware-base
3
- Version: 5.2
3
+ Version: 5.2.1
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
@@ -10,7 +10,7 @@ line-length = 79
10
10
 
11
11
  [project]
12
12
  name = "solentware-base"
13
- version = "5.2"
13
+ version = "5.2.1"
14
14
  authors = [
15
15
  { name="Roger Marsh", email="roger.marsh@solentware.co.uk" },
16
16
  ]
@@ -151,9 +151,9 @@ class Database(_database.Database):
151
151
  if values[-1] != record_number:
152
152
  values.append(record_number)
153
153
  if len(values) > SegmentSize.db_upper_conversion_limit:
154
- vsk = value_segments[
155
- key
156
- ] = SegmentSize.empty_bitarray.copy()
154
+ vsk = value_segments[key] = (
155
+ SegmentSize.empty_bitarray.copy()
156
+ )
157
157
  for j in values:
158
158
  vsk[j] = True
159
159
  vsk[record_number] = True
@@ -1414,7 +1414,7 @@ class Database(_database.Database):
1414
1414
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1415
1415
  if keylike is None:
1416
1416
  return recordlist
1417
- pattern = b".*?" + keylike
1417
+ matcher = re.compile(keylike)
1418
1418
  cursor = self.table[SUBFILE_DELIMITER.join((file, field))].cursor(
1419
1419
  txn=self.dbtxn
1420
1420
  )
@@ -1422,7 +1422,7 @@ class Database(_database.Database):
1422
1422
  record = cursor.first()
1423
1423
  while record:
1424
1424
  key, value = record
1425
- if re.match(pattern, key, flags=re.IGNORECASE | re.DOTALL):
1425
+ if matcher.search(key):
1426
1426
  self.populate_recordset_segment(recordlist, value)
1427
1427
  record = cursor.next()
1428
1428
  finally:
@@ -1478,9 +1478,9 @@ class Database(_database.Database):
1478
1478
 
1479
1479
  Keys are in range set by combinations of ge, gt, le, and lt.
1480
1480
  """
1481
- if ge and gt:
1481
+ if isinstance(ge, bytes) and isinstance(gt, bytes):
1482
1482
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
1483
- if le and lt:
1483
+ if isinstance(le, bytes) and isinstance(lt, bytes):
1484
1484
  raise DatabaseError("Both 'le' and 'lt' given in key range")
1485
1485
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1486
1486
  cursor = self.table[SUBFILE_DELIMITER.join((file, field))].cursor(
@@ -1490,7 +1490,7 @@ class Database(_database.Database):
1490
1490
  if ge is None and gt is None:
1491
1491
  record = cursor.first()
1492
1492
  else:
1493
- record = cursor.set_range(ge or gt)
1493
+ record = cursor.set_range(ge or gt or b"")
1494
1494
  if gt:
1495
1495
  while record:
1496
1496
  if record[0] > gt:
@@ -1793,7 +1793,7 @@ class Database(_database.Database):
1793
1793
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1794
1794
  if keylike is None:
1795
1795
  return recordlist
1796
- pattern = b".*?" + keylike
1796
+ matcher = re.compile(keylike)
1797
1797
  command = [
1798
1798
  self.table[SUBFILE_DELIMITER.join((file, field))],
1799
1799
  "cursor",
@@ -1805,7 +1805,7 @@ class Database(_database.Database):
1805
1805
  record = tcl_tk_call((cursor, "get", "-first"))
1806
1806
  while record:
1807
1807
  key, value = record[0]
1808
- if re.match(pattern, key, flags=re.IGNORECASE | re.DOTALL):
1808
+ if matcher.search(key):
1809
1809
  self.populate_recordset_segment(recordlist, value)
1810
1810
  record = tcl_tk_call((cursor, "get", "-next"))
1811
1811
  finally:
@@ -1872,9 +1872,9 @@ class Database(_database.Database):
1872
1872
 
1873
1873
  Keys are in range set by combinations of ge, gt, le, and lt.
1874
1874
  """
1875
- if ge and gt:
1875
+ if isinstance(ge, str) and isinstance(gt, str):
1876
1876
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
1877
- if le and lt:
1877
+ if isinstance(le, str) and isinstance(lt, str):
1878
1878
  raise DatabaseError("Both 'le' and 'lt' given in key range")
1879
1879
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1880
1880
  command = [
@@ -1888,7 +1888,9 @@ class Database(_database.Database):
1888
1888
  if ge is None and gt is None:
1889
1889
  record = tcl_tk_call((cursor, "get", "-first"))
1890
1890
  else:
1891
- record = tcl_tk_call((cursor, "get", "-set_range", ge or gt))
1891
+ record = tcl_tk_call(
1892
+ (cursor, "get", "-set_range", ge or gt or "")
1893
+ )
1892
1894
  if gt:
1893
1895
  while record:
1894
1896
  record0 = record[0]
@@ -553,10 +553,10 @@ class Database(_databasedu.Database):
553
553
  new_segment = make_segment_from_item(item)
554
554
  new_segment |= existing_segment
555
555
  new_segment.normalize()
556
- item[
557
- -2
558
- ] = self.database.encode_number_for_sequential_file_dump(
559
- new_segment.count_records(), 2
556
+ item[-2] = (
557
+ self.database.encode_number_for_sequential_file_dump(
558
+ new_segment.count_records(), 2
559
+ )
560
560
  )
561
561
  if int.from_bytes(high[-2], byteorder="big") == 1:
562
562
  item[-1] = segment_table.append(
@@ -700,10 +700,10 @@ class Database(_databasedu.Database):
700
700
  new_segment = make_segment_from_item(item)
701
701
  new_segment |= existing_segment
702
702
  new_segment.normalize()
703
- item[
704
- -2
705
- ] = self.database.encode_number_for_sequential_file_dump(
706
- new_segment.count_records(), 2
703
+ item[-2] = (
704
+ self.database.encode_number_for_sequential_file_dump(
705
+ new_segment.count_records(), 2
706
+ )
707
707
  )
708
708
  if int.from_bytes(high[-2], byteorder="big") == 1:
709
709
  item[-1] = tcl_tk_call(
@@ -849,14 +849,14 @@ class Database(_database.Database):
849
849
  dptapi.APIFieldValue(""),
850
850
  )
851
851
  )
852
- matcher = re.compile(".*?" + keylike, flags=re.IGNORECASE | re.DOTALL)
852
+ matcher = re.compile(keylike)
853
853
  dvcursor = dptfile.table_connection.OpenDirectValueCursor(
854
854
  dptapi.APIFindValuesSpecification(dptfile.secondary[field])
855
855
  )
856
856
  dvcursor.GotoFirst()
857
857
  while dvcursor.Accessible():
858
858
  value = dvcursor.GetCurrentValue()
859
- if matcher.match(value.ExtractString()):
859
+ if matcher.search(value.ExtractString()):
860
860
  vfs = _DPTFoundSet(
861
861
  dptfile,
862
862
  dptfile.table_connection.FindRecords(
@@ -961,9 +961,9 @@ class Database(_database.Database):
961
961
 
962
962
  cache_size is not relevant to DPT.
963
963
  """
964
- if ge and gt:
964
+ if isinstance(ge, str) and isinstance(gt, str):
965
965
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
966
- if le and lt:
966
+ if isinstance(le, str) and isinstance(lt, str):
967
967
  raise DatabaseError("Both 'le' and 'lt' given in key range")
968
968
  if ge is None and gt is None and le is None and lt is None:
969
969
  return self.recordlist_all(file, field, cache_size=cache_size)
@@ -975,8 +975,8 @@ class Database(_database.Database):
975
975
  dptfile.table_connection.FindRecords(
976
976
  dptapi.APIFindSpecification(
977
977
  dptfile.secondary[field],
978
- dptapi.FD_GE if ge else dptapi.FD_GT,
979
- dptapi.APIFieldValue(ge or gt),
978
+ dptapi.FD_GE if ge is not None else dptapi.FD_GT,
979
+ dptapi.APIFieldValue(ge or gt or ""),
980
980
  )
981
981
  ),
982
982
  )
@@ -986,24 +986,32 @@ class Database(_database.Database):
986
986
  dptfile.table_connection.FindRecords(
987
987
  dptapi.APIFindSpecification(
988
988
  dptfile.secondary[field],
989
- dptapi.FD_LE if le else dptapi.FD_LT,
990
- dptapi.APIFieldValue(le or lt),
989
+ dptapi.FD_LE if le is not None else dptapi.FD_LT,
990
+ dptapi.APIFieldValue(le or lt or ""),
991
991
  )
992
992
  ),
993
993
  )
994
994
  else:
995
995
  if ge:
996
- range_ = dptapi.FD_RANGE_GE_LE if le else dptapi.FD_RANGE_GE_LT
996
+ range_ = (
997
+ dptapi.FD_RANGE_GE_LE
998
+ if le is not None
999
+ else dptapi.FD_RANGE_GE_LT
1000
+ )
997
1001
  else:
998
- range_ = dptapi.FD_RANGE_GT_LE if le else dptapi.FD_RANGE_GT_LT
1002
+ range_ = (
1003
+ dptapi.FD_RANGE_GT_LE
1004
+ if le is not None
1005
+ else dptapi.FD_RANGE_GT_LT
1006
+ )
999
1007
  foundset = _DPTFoundSet(
1000
1008
  dptfile,
1001
1009
  dptfile.table_connection.FindRecords(
1002
1010
  dptapi.APIFindSpecification(
1003
1011
  dptfile.secondary[field],
1004
1012
  range_,
1005
- dptapi.APIFieldValue(ge or gt),
1006
- dptapi.APIFieldValue(le or lt),
1013
+ dptapi.APIFieldValue(ge or gt or ""),
1014
+ dptapi.APIFieldValue(le or lt or ""),
1007
1015
  )
1008
1016
  ),
1009
1017
  )
@@ -1482,14 +1482,14 @@ class Database(_database.Database):
1482
1482
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1483
1483
  if keylike is None:
1484
1484
  return recordlist
1485
- pattern = b".*?" + keylike
1485
+ matcher = re.compile(keylike)
1486
1486
  with self.dbtxn.transaction.cursor(
1487
1487
  self.table[SUBFILE_DELIMITER.join((file, field))].datastore
1488
1488
  ) as cursor:
1489
1489
  record = cursor.first()
1490
1490
  while record:
1491
1491
  key, value = cursor.item()
1492
- if re.match(pattern, key, flags=re.IGNORECASE | re.DOTALL):
1492
+ if matcher.search(key):
1493
1493
  self.populate_recordset_segment(recordlist, value)
1494
1494
  record = cursor.next()
1495
1495
  return recordlist
@@ -1540,9 +1540,9 @@ class Database(_database.Database):
1540
1540
 
1541
1541
  Keys are in range set by combinations of ge, gt, le, and lt.
1542
1542
  """
1543
- if ge and gt:
1543
+ if isinstance(ge, bytes) and isinstance(gt, bytes):
1544
1544
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
1545
- if le and lt:
1545
+ if isinstance(le, bytes) and isinstance(lt, bytes):
1546
1546
  raise DatabaseError("Both 'le' and 'lt' given in key range")
1547
1547
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1548
1548
  with self.dbtxn.transaction.cursor(
@@ -1551,7 +1551,7 @@ class Database(_database.Database):
1551
1551
  if ge is None and gt is None:
1552
1552
  record = cursor.first()
1553
1553
  else:
1554
- record = cursor.set_range(ge or gt)
1554
+ record = cursor.set_range(ge or gt or b"")
1555
1555
  if gt:
1556
1556
  while record:
1557
1557
  record = cursor.item()
@@ -611,10 +611,10 @@ class Database(_databasedu.Database):
611
611
  new_segment = make_segment_from_item(item)
612
612
  new_segment |= existing_segment
613
613
  new_segment.normalize()
614
- item[
615
- -2
616
- ] = self.database.encode_number_for_sequential_file_dump(
617
- new_segment.count_records(), 2
614
+ item[-2] = (
615
+ self.database.encode_number_for_sequential_file_dump(
616
+ new_segment.count_records(), 2
617
+ )
618
618
  )
619
619
  if int.from_bytes(item[-2], byteorder="big") == 1:
620
620
  item[-1] = write_segment_value(
@@ -108,9 +108,9 @@ class Database(_database.Database):
108
108
  continue
109
109
  for fieldattr in NOSQL_FIELDATTS:
110
110
  if fieldattr not in filedesc[FIELDS][fieldname]:
111
- filedesc[FIELDS][fieldname][
112
- fieldattr
113
- ] = SECONDARY_FIELDATTS[fieldattr]
111
+ filedesc[FIELDS][fieldname][fieldattr] = (
112
+ SECONDARY_FIELDATTS[fieldattr]
113
+ )
114
114
  self.specification = specification
115
115
  self.segment_size_bytes = segment_size_bytes
116
116
  self.dbenv = None
@@ -1229,7 +1229,7 @@ class Database(_database.Database):
1229
1229
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1230
1230
  if keylike is None:
1231
1231
  return recordlist
1232
- matcher = re.compile(".*?" + keylike, flags=re.IGNORECASE | re.DOTALL)
1232
+ matcher = re.compile(keylike)
1233
1233
  db = self.dbenv
1234
1234
  fieldtree = self.trees[SUBFILE_DELIMITER.join((file, field))]
1235
1235
  cursor = tree.Cursor(fieldtree)
@@ -1238,7 +1238,7 @@ class Database(_database.Database):
1238
1238
  k = cursor.next()
1239
1239
  if k is None:
1240
1240
  break
1241
- if not matcher.match(k):
1241
+ if not matcher.search(k):
1242
1242
  continue
1243
1243
  self.populate_recordset(
1244
1244
  recordlist,
@@ -1322,9 +1322,9 @@ class Database(_database.Database):
1322
1322
  ("'", field, "' field in '", file, "' file is not ordered")
1323
1323
  )
1324
1324
  )
1325
- if ge and gt:
1325
+ if isinstance(ge, str) and isinstance(gt, str):
1326
1326
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
1327
- if le and lt:
1327
+ if isinstance(le, str) and isinstance(lt, str):
1328
1328
  raise DatabaseError("Both 'le' and 'lt' given in key range")
1329
1329
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1330
1330
  db = self.dbenv
@@ -1334,7 +1334,7 @@ class Database(_database.Database):
1334
1334
  if ge is None and gt is None:
1335
1335
  k = cursor.first()
1336
1336
  else:
1337
- k = cursor.nearest(ge or gt)
1337
+ k = cursor.nearest(ge or gt or "")
1338
1338
  if gt:
1339
1339
  while k is not None:
1340
1340
  if k > gt:
@@ -121,9 +121,9 @@ class Database(_databasedu.Database):
121
121
  ebmc = self.ebm_control[file]
122
122
  tes = ebmc.table_ebm_segments
123
123
  insertion_point = bisect_right(tes, segment)
124
- self.dbenv[
125
- SUBFILE_DELIMITER.join((ebmc.ebm_table, str(segment)))
126
- ] = repr(self.existence_bit_maps[file][segment].tobytes())
124
+ self.dbenv[SUBFILE_DELIMITER.join((ebmc.ebm_table, str(segment)))] = (
125
+ repr(self.existence_bit_maps[file][segment].tobytes())
126
+ )
127
127
  if not (tes and tes[insertion_point - 1] == segment):
128
128
  tes.insert(insertion_point, segment)
129
129
  self.dbenv[ebmc.ebm_table] = repr(tes)
@@ -991,9 +991,12 @@ class Database(_database.Database):
991
991
  update_count_and_reference,
992
992
  (
993
993
  count,
994
- record_number % (segment * SegmentSize.db_segment_size)
995
- if segment
996
- else record_number,
994
+ (
995
+ record_number
996
+ % (segment * SegmentSize.db_segment_size)
997
+ if segment
998
+ else record_number
999
+ ),
997
1000
  key,
998
1001
  segment,
999
1002
  ),
@@ -1488,12 +1491,12 @@ class Database(_database.Database):
1488
1491
  )
1489
1492
  )
1490
1493
  db_segment_size_bytes = SegmentSize.db_segment_size_bytes
1491
- matcher = re.compile(".*?" + keylike, flags=re.IGNORECASE | re.DOTALL)
1494
+ matcher = re.compile(keylike)
1492
1495
  get_segment_records = self.get_segment_records
1493
1496
  cursor = self.dbenv.cursor()
1494
1497
  try:
1495
1498
  for record in cursor.execute(statement):
1496
- if not matcher.match(record[0]):
1499
+ if not matcher.search(record[0]):
1497
1500
  continue
1498
1501
  if record[2] == 1:
1499
1502
  segment = RecordsetSegmentInt(
@@ -1595,9 +1598,11 @@ class Database(_database.Database):
1595
1598
  values = (
1596
1599
  b"".join(
1597
1600
  (
1598
- keystart.encode()
1599
- if isinstance(keystart, str)
1600
- else keystart,
1601
+ (
1602
+ keystart.encode()
1603
+ if isinstance(keystart, str)
1604
+ else keystart
1605
+ ),
1601
1606
  b"*",
1602
1607
  )
1603
1608
  ),
@@ -1638,14 +1643,22 @@ class Database(_database.Database):
1638
1643
 
1639
1644
  Keys are in range set by combinations of ge, gt, le, and lt.
1640
1645
  """
1641
- if ge and gt:
1646
+ if isinstance(ge, str) and isinstance(gt, str):
1642
1647
  raise DatabaseError("Both 'ge' and 'gt' given in key range")
1643
- if le and lt:
1648
+ if isinstance(le, str) and isinstance(lt, str):
1644
1649
  raise DatabaseError("Both 'le' and 'lt' given in key range")
1645
1650
  if ge is None and gt is None and le is None and lt is None:
1646
1651
  return self.recordlist_all(file, field, cache_size=cache_size)
1647
- highop = "<" if lt else "<=" if le else None
1648
- lowop = ">" if gt else ">=" if ge else None
1652
+ highop = (
1653
+ "<"
1654
+ if isinstance(lt, str)
1655
+ else "<=" if isinstance(le, str) else None
1656
+ )
1657
+ lowop = (
1658
+ ">"
1659
+ if isinstance(gt, str)
1660
+ else ">=" if isinstance(ge, str) else None
1661
+ )
1649
1662
  recordlist = RecordList(dbhome=self, dbset=file, cache_size=cache_size)
1650
1663
  if highop is None:
1651
1664
  statement = " ".join(
@@ -1666,7 +1679,7 @@ class Database(_database.Database):
1666
1679
  "?",
1667
1680
  )
1668
1681
  )
1669
- values = (ge or gt,)
1682
+ values = (ge or gt or "",)
1670
1683
  elif lowop is None:
1671
1684
  statement = " ".join(
1672
1685
  (
@@ -1686,7 +1699,7 @@ class Database(_database.Database):
1686
1699
  "?",
1687
1700
  )
1688
1701
  )
1689
- values = (le or lt,)
1702
+ values = (le or lt or "",)
1690
1703
  else:
1691
1704
  statement = " ".join(
1692
1705
  (
@@ -1709,7 +1722,7 @@ class Database(_database.Database):
1709
1722
  "?",
1710
1723
  )
1711
1724
  )
1712
- values = ge or gt, le or lt
1725
+ values = (ge or gt or "", le or lt or "")
1713
1726
  db_segment_size_bytes = SegmentSize.db_segment_size_bytes
1714
1727
  get_segment_records = self.get_segment_records
1715
1728
  cursor = self.dbenv.cursor()
@@ -152,8 +152,10 @@ class Find:
152
152
  for segment in recordset.rs_segments.values():
153
153
  j = segment.first()
154
154
  while j:
155
- instance.load_record(j)
156
- yield j[0], instance.value
155
+ instance.load_record(
156
+ self._db.get_primary_record(self._dbset, j[1])
157
+ )
158
+ yield j[1], instance.value
157
159
  j = segment.next()
158
160
 
159
161
  def non_index_condition(self, obj, record_number, record):
@@ -547,9 +547,11 @@ class Record:
547
547
  record = database.get_primary_record(
548
548
  dbset,
549
549
  # database.decode_as_primary_key(dbset, newrecord.srkey))
550
- database.decode_record_number(newrecord.srkey)
551
- if isinstance(newrecord.srkey, int)
552
- else newrecord.srkey,
550
+ (
551
+ database.decode_record_number(newrecord.srkey)
552
+ if isinstance(newrecord.srkey, int)
553
+ else newrecord.srkey
554
+ ),
553
555
  )
554
556
  if record is None:
555
557
  database.put_instance(dbset, newrecord)
@@ -36,13 +36,12 @@ particularly the last of these.
36
36
  """
37
37
 
38
38
  import re
39
- import sre_constants
40
39
  from tkinter import simpledialog
41
40
 
42
41
  from .constants import SECONDARY
43
42
 
44
- DOUBLE_QUOTE_STRING = '".*?"'
45
- SINGLE_QUOTE_STRING = "'.*?'"
43
+ DOUBLE_QUOTE_STRING = r'"[^\\"]*(?:\\.[^\\"]*)*"'
44
+ SINGLE_QUOTE_STRING = r"'[^\\']*(?:\\.[^\\']*)*'"
46
45
  LEFT_PARENTHESIS = "("
47
46
  RIGHT_PARENTHESIS = ")"
48
47
  OR = "or"
@@ -67,7 +66,7 @@ BELOW = "below"
67
66
  BEFORE = "before"
68
67
  STARTS = "starts"
69
68
  PRESENT = "present"
70
- STRING = ".+?"
69
+ STRING = r"\w+|\s+|[^\w\s()'\"]+|['\"]"
71
70
 
72
71
  LEADING_SPACE = r"(?<=\s)"
73
72
  TRAILING_SPACE = r"(?=\s)"
@@ -102,9 +101,9 @@ WHERE_RE = re.compile(
102
101
  BEFORE.join((LEADING_SPACE, TRAILING_SPACE)),
103
102
  STARTS.join((LEADING_SPACE, TRAILING_SPACE)),
104
103
  PRESENT.join((LEADING_SPACE, r"(?=\s|\)|\Z)")),
105
- STRING,
104
+ STRING.join("()"),
106
105
  )
107
- ).join(("(", ")")),
106
+ ),
108
107
  flags=re.IGNORECASE | re.DOTALL,
109
108
  )
110
109
 
@@ -175,26 +174,41 @@ class Where:
175
174
  def lex(self):
176
175
  """Split instance's statement into tokens."""
177
176
  tokens = []
177
+ items = []
178
178
  strings = []
179
- for word in WHERE_RE.split(self.statement):
179
+ string_items = []
180
+ for item in WHERE_RE.finditer(self.statement):
181
+ word = item.group()
180
182
  if word.lower() in KEYWORDS:
181
183
  if strings:
182
- tokens.append("".join([_trim(s) for s in strings if s]))
184
+ tokens.append("".join(_trim(strings)))
183
185
  strings.clear()
186
+ items.append(tuple(string_items))
187
+ string_items.clear()
184
188
  tokens.append(word.lower())
185
- elif word.strip():
186
- strings.append(word.strip())
189
+ items.append((item,))
190
+ elif word:
191
+ strings.append(word)
192
+ string_items.append(item)
187
193
  if strings:
188
- tokens.append("".join([_trim(s) for s in strings if s]))
194
+ tokens.append("".join(_trim(strings)))
189
195
  strings.clear()
190
- self.tokens = tokens
196
+ items.append(tuple(string_items))
197
+ string_items.clear()
198
+ self.tokens = [
199
+ (i, t)
200
+ for i, t in zip(items, tokens)
201
+ if t != "" or None in [j.group(1) for j in i]
202
+ ]
191
203
 
192
204
  def parse(self):
193
205
  """Parse instance's tokens to create node structure to do query."""
194
206
  self.node = WhereClause()
195
207
  state = self._set_field_not_leftp_start
196
208
  for i, token in enumerate(self.tokens):
197
- state = state(token)
209
+ # May need to be 'state = state(token)' with the <state>
210
+ # methods influenced by token[0] if appropriate.
211
+ state = state(token[1])
198
212
  if not state:
199
213
  self._error_information.tokens = self.tokens[: i + 1]
200
214
  break
@@ -392,10 +406,10 @@ class Where:
392
406
  if token_lower == LEFT_PARENTHESIS:
393
407
  self._first_token_left_parenthesis(token)
394
408
  return self._set_field_not_leftp
395
- if token_lower not in KEYWORDS:
396
- self._first_token_field(token)
397
- return self._set_not_num_alpha_condition
398
- return self.error(token)
409
+ if token_lower in KEYWORDS:
410
+ self.raise_where_error(token)
411
+ self._first_token_field(token)
412
+ return self._set_not_num_alpha_condition
399
413
 
400
414
  def _set_field_leftp(self, token):
401
415
  """Expect fieldname, or '(', after 'not'."""
@@ -403,11 +417,11 @@ class Where:
403
417
  if token_lower == LEFT_PARENTHESIS:
404
418
  self._boolean_left_parenthesis(token)
405
419
  return self._set_field_not_leftp
406
- if token_lower not in KEYWORDS:
407
- self._deferred_not_phrase()
408
- self.node.field = token
409
- return self._set_not_num_alpha_condition
410
- return self.error(token)
420
+ if token_lower in KEYWORDS:
421
+ self.raise_where_error(token)
422
+ self._deferred_not_phrase()
423
+ self.node.field = token
424
+ return self._set_not_num_alpha_condition
411
425
 
412
426
  def _set_field_not_leftp(self, token):
413
427
  """Expect fieldname, 'not', or '(', after '(' or boolean."""
@@ -418,11 +432,11 @@ class Where:
418
432
  if token_lower == LEFT_PARENTHESIS:
419
433
  self._boolean_left_parenthesis(token)
420
434
  return self._set_field_not_leftp
421
- if token_lower not in KEYWORDS:
422
- self._deferred_not_phrase()
423
- self.node.field = token
424
- return self._set_not_num_alpha_condition
425
- return self.error(token)
435
+ if token_lower in KEYWORDS:
436
+ self.raise_where_error(token)
437
+ self._deferred_not_phrase()
438
+ self.node.field = token
439
+ return self._set_not_num_alpha_condition
426
440
 
427
441
  def _set_not_num_alpha_condition(self, token):
428
442
  """Expect 'not', 'num', 'alpha', or a condition, after fieldname."""
@@ -440,7 +454,7 @@ class Where:
440
454
  # for the token_lower == NOT case because 'is' is never
441
455
  # preceded by 'not'.
442
456
  if self._not:
443
- return self.error(token)
457
+ self.raise_where_error(token)
444
458
  self.node.condition = token_lower
445
459
  return self._set_not_value
446
460
 
@@ -473,7 +487,8 @@ class Where:
473
487
  self._deferred_not_condition()
474
488
  self.node.condition = token_lower
475
489
  return self._set_first_value
476
- return self.error(token)
490
+ self.raise_where_error(token)
491
+ return None
477
492
 
478
493
  def _set_second_condition(self, token):
479
494
  """Expect second condition after first value of double condition."""
@@ -481,7 +496,8 @@ class Where:
481
496
  if token_lower in SECOND_CONDITIONS:
482
497
  self.node.condition = self.node.condition, token_lower
483
498
  return self._set_second_value
484
- return self.error(token)
499
+ self.raise_where_error(token)
500
+ return None
485
501
 
486
502
  def _set_not_value(self, token):
487
503
  """Expect 'not', or a value, after a condition."""
@@ -493,39 +509,37 @@ class Where:
493
509
 
494
510
  def _set_value(self, token):
495
511
  """Expect value after single condition."""
496
- token_lower = token.lower()
497
- if token_lower not in KEYWORDS:
498
- self.node.value = token
499
- return self._set_and_or_nor_rightp__single_condition
500
- return self.error(token)
512
+ self.node.value = token
513
+ return self._set_and_or_nor_rightp__single_condition
501
514
 
502
515
  def _set_value_like(self, token):
503
516
  """Expect value, a regular expression, after like."""
504
- token_lower = token.lower()
505
- if token_lower not in KEYWORDS:
506
- try:
507
- re.compile(token, flags=re.IGNORECASE | re.DOTALL)
508
- except sre_constants.error:
509
- return self.error(token)
510
- self.node.value = token
511
- return self._set_and_or_nor_rightp__single_condition
512
- return self.error(token)
517
+ try:
518
+ re.compile(token)
519
+ except re.error as exc:
520
+ raise WhereError(
521
+ "".join(
522
+ (
523
+ "Problem with 'like' argument\n\n'",
524
+ token,
525
+ "'\n\nThe reported regular expression ",
526
+ "error is\n\n",
527
+ str(exc),
528
+ )
529
+ )
530
+ ) from exc
531
+ self.node.value = token
532
+ return self._set_and_or_nor_rightp__single_condition
513
533
 
514
534
  def _set_first_value(self, token):
515
535
  """Expect first value after first condition in double condition."""
516
- token_lower = token.lower()
517
- if token_lower not in KEYWORDS:
518
- self.node.value = token
519
- return self._set_second_condition
520
- return self.error(token)
536
+ self.node.value = token
537
+ return self._set_second_condition
521
538
 
522
539
  def _set_second_value(self, token):
523
540
  """Expect second value after second condition in double condition."""
524
- token_lower = token.lower()
525
- if token_lower not in KEYWORDS:
526
- self.node.value = self.node.value, token
527
- return self._set_and_or_nor_rightp__single_condition
528
- return self.error(token)
541
+ self.node.value = self.node.value, token
542
+ return self._set_and_or_nor_rightp__single_condition
529
543
 
530
544
  def _set_and_or_nor_rightp__single_condition(self, token):
531
545
  """Expect boolean or rightp after value in single condition phrase.
@@ -589,7 +603,8 @@ class Where:
589
603
  raise WhereError("No unmatched left-parentheses")
590
604
  self.node = self.node.up
591
605
  return self._set_and_or_nor_rightp
592
- return self.error(token)
606
+ self.raise_where_error(token)
607
+ return None
593
608
 
594
609
  def _set_field_leftp_not_condition(self, token):
595
610
  """Expect fieldname, '(', 'not', or condition after rightp."""
@@ -602,11 +617,11 @@ class Where:
602
617
  def _set_field_leftp_condition(self, token):
603
618
  """Expect fieldname, '(', or condition after rightp 'not'."""
604
619
  token_lower = token.lower()
605
- if token_lower not in KEYWORDS:
606
- self._deferred_not_phrase()
607
- self.node.field = token
608
- return self._set_not_num_alpha_condition
609
- return self._set_leftp_condition(token)
620
+ if token_lower in KEYWORDS:
621
+ return self._set_leftp_condition(token)
622
+ self._deferred_not_phrase()
623
+ self.node.field = token
624
+ return self._set_not_num_alpha_condition
610
625
 
611
626
  def _set_field_leftp_not_condition_value(self, token):
612
627
  """Expect fieldname, '(', 'not', condition, or value, after rightp."""
@@ -619,10 +634,10 @@ class Where:
619
634
  def _set_field_leftp_condition_value(self, token):
620
635
  """Expect fieldname, '(', condition, or value, after rightp 'not'."""
621
636
  token_lower = token.lower()
622
- if token_lower not in KEYWORDS:
623
- self._f_or_v = token
624
- return self._set_field_or_value__not
625
- return self._set_leftp_condition(token)
637
+ if token_lower in KEYWORDS:
638
+ return self._set_leftp_condition(token)
639
+ self._f_or_v = token
640
+ return self._set_field_or_value__not
626
641
 
627
642
  def _set_field_or_value__not(self, token):
628
643
  """Expect keyword to interpret previous token as field or value."""
@@ -657,7 +672,7 @@ class Where:
657
672
  # for the token_lower == NOT case because 'is' is never
658
673
  # preceded by 'not'.
659
674
  if self._not:
660
- return self.error(token)
675
+ self.raise_where_error(token)
661
676
  self._field_condition(token_lower)
662
677
  return self._set_not_value
663
678
 
@@ -680,7 +695,8 @@ class Where:
680
695
  self._field_condition(token_lower)
681
696
  self._alphanum_condition(token_lower)
682
697
  return self._set_condition
683
- return self.error(token)
698
+ self.raise_where_error(token)
699
+ return None
684
700
 
685
701
  # Why is this not the same as _set_num_alpha_condition?
686
702
  # Perhaps the question should be the other way round!
@@ -699,7 +715,7 @@ class Where:
699
715
  # for the token_lower == NOT case because 'is' is never
700
716
  # preceded by 'not'.
701
717
  if self._not:
702
- return self.error(token)
718
+ self.raise_where_error(token)
703
719
  self.node.condition = token_lower
704
720
  return self._set_not_value
705
721
 
@@ -727,14 +743,12 @@ class Where:
727
743
  self._copy_pre_alphanumeric()
728
744
  self._alphanum_condition(token_lower)
729
745
  return self._set_condition
730
- return self.error(token)
746
+ self.raise_where_error(token)
747
+ return None
731
748
 
732
- # Returning False always cannot possibly be correct, surely?
733
- def error(self, token):
734
- """Return False."""
735
- if token.lower() in KEYWORDS:
736
- return False
737
- return False
749
+ def raise_where_error(self, token):
750
+ """Raise WhereError exception."""
751
+ raise WhereError(token.join(("Unable to process token '", "'")))
738
752
 
739
753
  def _deferred_not_condition(self):
740
754
  """Nearest 'not' to left inverts a condition such as 'eq'."""
@@ -1112,15 +1126,18 @@ class WhereConstraint:
1112
1126
  self.pending = False
1113
1127
 
1114
1128
 
1115
- def _trim(string):
1116
- """Return string with one leading and trailing ' or " removed.
1129
+ def _trim(strings):
1130
+ """Return trimmed copy of strings.
1117
1131
 
1118
- The two quote characters allow values containing spaces.
1132
+ The first item has leading whitespace removed.
1133
+ The last item has trailing whitespace removed.
1134
+ One leading and one trailing ' or " is removed from all items longer
1135
+ than one character.
1119
1136
 
1120
1137
  """
1121
- if string[0] in "'\"":
1122
- return string[1:-1]
1123
- return string
1138
+ strings[0] = strings[0].lstrip()
1139
+ strings[-1] = strings[-1].rstrip()
1140
+ return [s[1:-1] if len(s) > 1 and s[0] in "'\"" else s for s in strings]
1124
1141
 
1125
1142
 
1126
1143
  class WhereStatementError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: solentware-base
3
- Version: 5.2
3
+ Version: 5.2.1
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
File without changes
File without changes
File without changes
File without changes