bpmn-lib 4.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bpmn_lib/__init__.py +38 -0
- bpmn_lib/database/__init__.py +25 -0
- bpmn_lib/database/instance/__init__.py +13 -0
- bpmn_lib/database/instance/database_builder.py +151 -0
- bpmn_lib/database/instance/database_bulk_validator.py +350 -0
- bpmn_lib/database/instance/database_index_builder.py +112 -0
- bpmn_lib/database/instance/database_instance.py +413 -0
- bpmn_lib/database/schema/__init__.py +15 -0
- bpmn_lib/database/schema/column_definition.py +223 -0
- bpmn_lib/database/schema/database_schema.py +167 -0
- bpmn_lib/database/schema/database_schema_parser.py +374 -0
- bpmn_lib/database/schema/foreign_key_relationship.py +139 -0
- bpmn_lib/database/schema/table_definition.py +245 -0
- bpmn_lib/navigator/__init__.py +80 -0
- bpmn_lib/navigator/bpmn_constants.py +52 -0
- bpmn_lib/navigator/bpmn_hierarchy_navigator.py +1423 -0
- bpmn_lib/navigator/navigator_factory.py +175 -0
- bpmn_lib/py.typed +1 -0
- bpmn_lib/utils/__init__.py +11 -0
- bpmn_lib/utils/validation_result.py +131 -0
- bpmn_lib/validation/__init__.py +11 -0
- bpmn_lib/validation/exceptions.py +5 -0
- bpmn_lib/validation/expression_ast.py +78 -0
- bpmn_lib/validation/expression_parser.py +232 -0
- bpmn_lib/validation/rule_engine.py +267 -0
- bpmn_lib/validation/rule_store.py +191 -0
- bpmn_lib-4.0.5.dist-info/METADATA +129 -0
- bpmn_lib-4.0.5.dist-info/RECORD +31 -0
- bpmn_lib-4.0.5.dist-info/WHEEL +5 -0
- bpmn_lib-4.0.5.dist-info/licenses/LICENSE +21 -0
- bpmn_lib-4.0.5.dist-info/top_level.txt +1 -0
bpmn_lib/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BPMN Process Navigation Library.
|
|
3
|
+
|
|
4
|
+
This library provides classes for navigating and managing BPMN element hierarchies,
|
|
5
|
+
database schema definitions, and validation utilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from bpmn_lib.navigator.bpmn_hierarchy_navigator import BPMNHierarchyNavigator
|
|
9
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
10
|
+
from bpmn_lib.database.instance.database_builder import DatabaseBuilder
|
|
11
|
+
from bpmn_lib.database.schema.database_schema import DatabaseSchema
|
|
12
|
+
from bpmn_lib.database.schema.database_schema_parser import DatabaseSchemaParser
|
|
13
|
+
from bpmn_lib.database.schema.table_definition import TableDefinition
|
|
14
|
+
from bpmn_lib.database.schema.column_definition import ColumnDefinition
|
|
15
|
+
from bpmn_lib.database.schema.foreign_key_relationship import ForeignKeyRelationship
|
|
16
|
+
from bpmn_lib.utils.validation_result import ValidationResult
|
|
17
|
+
from basic_framework import MarkdownDocument
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Navigator
|
|
21
|
+
"BPMNHierarchyNavigator",
|
|
22
|
+
# Database Instance
|
|
23
|
+
"DatabaseInstance",
|
|
24
|
+
"DatabaseBuilder",
|
|
25
|
+
# Database Schema
|
|
26
|
+
"DatabaseSchema",
|
|
27
|
+
"DatabaseSchemaParser",
|
|
28
|
+
"TableDefinition",
|
|
29
|
+
"ColumnDefinition",
|
|
30
|
+
"ForeignKeyRelationship",
|
|
31
|
+
# Utils
|
|
32
|
+
"ValidationResult",
|
|
33
|
+
"MarkdownDocument",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
from importlib.metadata import version
|
|
37
|
+
|
|
38
|
+
__version__: str = version("bpmn-lib")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Database module for schema and instance management."""
|
|
2
|
+
|
|
3
|
+
from bpmn_lib.database.schema.database_schema import DatabaseSchema
|
|
4
|
+
from bpmn_lib.database.schema.database_schema_parser import DatabaseSchemaParser
|
|
5
|
+
from bpmn_lib.database.schema.table_definition import TableDefinition
|
|
6
|
+
from bpmn_lib.database.schema.column_definition import ColumnDefinition
|
|
7
|
+
from bpmn_lib.database.schema.foreign_key_relationship import ForeignKeyRelationship
|
|
8
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
9
|
+
from bpmn_lib.database.instance.database_builder import DatabaseBuilder
|
|
10
|
+
from bpmn_lib.database.instance.database_bulk_validator import DatabaseBulkValidator
|
|
11
|
+
from bpmn_lib.database.instance.database_index_builder import DatabaseIndexBuilder
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
# Schema
|
|
15
|
+
"DatabaseSchema",
|
|
16
|
+
"DatabaseSchemaParser",
|
|
17
|
+
"TableDefinition",
|
|
18
|
+
"ColumnDefinition",
|
|
19
|
+
"ForeignKeyRelationship",
|
|
20
|
+
# Instance
|
|
21
|
+
"DatabaseInstance",
|
|
22
|
+
"DatabaseBuilder",
|
|
23
|
+
"DatabaseBulkValidator",
|
|
24
|
+
"DatabaseIndexBuilder",
|
|
25
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Database instance module."""
|
|
2
|
+
|
|
3
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
4
|
+
from bpmn_lib.database.instance.database_builder import DatabaseBuilder
|
|
5
|
+
from bpmn_lib.database.instance.database_bulk_validator import DatabaseBulkValidator
|
|
6
|
+
from bpmn_lib.database.instance.database_index_builder import DatabaseIndexBuilder
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DatabaseInstance",
|
|
10
|
+
"DatabaseBuilder",
|
|
11
|
+
"DatabaseBulkValidator",
|
|
12
|
+
"DatabaseIndexBuilder",
|
|
13
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DatabaseBuilder - Koordiniert den Aufbau der Datenbank in Phasen.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
from basic_framework.proc_frame import log_msg, log_and_raise
|
|
7
|
+
from basic_framework.container_utils.container_in_memory import ContainerInMemory
|
|
8
|
+
from basic_framework.container_utils.abstract_container import AbstractContainer
|
|
9
|
+
from bpmn_lib.database.schema.database_schema import DatabaseSchema
|
|
10
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
11
|
+
from bpmn_lib.utils.validation_result import ValidationResult
|
|
12
|
+
from bpmn_lib.database.instance.database_bulk_validator import DatabaseBulkValidator
|
|
13
|
+
from bpmn_lib.database.instance.database_index_builder import DatabaseIndexBuilder
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabaseBuilder:
|
|
17
|
+
"""Koordiniert den Aufbau der Datenbank in Phasen."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, schema: DatabaseSchema, result: ValidationResult) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Initialisiert den DatabaseBuilder.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
schema: DatabaseSchema mit Tabellendefinitionen
|
|
25
|
+
result: ValidationResult für Fehlerberichte
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
TypeError: Wenn schema oder result None ist (Python's Typsystem)
|
|
29
|
+
"""
|
|
30
|
+
self._schema: DatabaseSchema = schema
|
|
31
|
+
|
|
32
|
+
# DatabaseInstance erstellen mit neuem Konstruktor
|
|
33
|
+
self._instance: DatabaseInstance = DatabaseInstance(self._schema)
|
|
34
|
+
|
|
35
|
+
# Validator und IndexBuilder erstellen mit neuen Konstruktoren
|
|
36
|
+
self._bulk_validator: DatabaseBulkValidator = DatabaseBulkValidator(self._instance, result)
|
|
37
|
+
self._index_builder: DatabaseIndexBuilder = DatabaseIndexBuilder(self._instance)
|
|
38
|
+
|
|
39
|
+
log_msg(f"DatabaseBuilder fuer Schema '{self._schema.get_schema_name()}' initialisiert.")
|
|
40
|
+
|
|
41
|
+
def load_all_data(self, o_data_source: Dict[str, ContainerInMemory]) -> None:
|
|
42
|
+
"""Laedt alle Daten ohne Constraint-Pruefung."""
|
|
43
|
+
log_msg("Starte Bulk-Load der Daten...")
|
|
44
|
+
self._load_from_container_dictionary(o_data_source)
|
|
45
|
+
|
|
46
|
+
def _load_from_container_dictionary(self, o_container_dict: Dict[str, ContainerInMemory]) -> None:
|
|
47
|
+
"""Laedt Daten aus einem Dictionary von ContainerInMemory-Objekten."""
|
|
48
|
+
n_total_rows = 0
|
|
49
|
+
|
|
50
|
+
# Fuer jede Tabelle im Dictionary
|
|
51
|
+
for v_table_name in o_container_dict.keys():
|
|
52
|
+
s_table_name = str(v_table_name)
|
|
53
|
+
|
|
54
|
+
# Pruefen ob Tabelle im Schema existiert
|
|
55
|
+
if not self._table_exists_in_schema(s_table_name):
|
|
56
|
+
log_msg(f"WARNUNG: Tabelle '{s_table_name}' existiert nicht im Schema und wird uebersprungen.")
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Source Container holen
|
|
60
|
+
o_source_container = o_container_dict[s_table_name]
|
|
61
|
+
|
|
62
|
+
# Iterator fuer Source erstellen
|
|
63
|
+
o_source_iterator = o_source_container.create_iterator()
|
|
64
|
+
|
|
65
|
+
# Zeilen kopieren
|
|
66
|
+
n_row_count = 0
|
|
67
|
+
|
|
68
|
+
while not o_source_iterator.is_empty():
|
|
69
|
+
# Zeile in Ziel-Tabelle einfuegen
|
|
70
|
+
if self._instance.insert_row_from_iterator(s_table_name, o_source_iterator):
|
|
71
|
+
n_row_count += 1
|
|
72
|
+
|
|
73
|
+
o_source_iterator.pp()
|
|
74
|
+
|
|
75
|
+
# Log-Nachricht alle 1000 Zeilen
|
|
76
|
+
if n_row_count % 1000 == 0:
|
|
77
|
+
pass # log_msg(f"Tabelle '{s_table_name}': {n_row_count} Zeilen geladen")
|
|
78
|
+
|
|
79
|
+
# log_msg(f"Tabelle '{s_table_name}': {n_row_count} Zeilen geladen.")
|
|
80
|
+
n_total_rows += n_row_count
|
|
81
|
+
|
|
82
|
+
# log_msg(f"Bulk-Load abgeschlossen. Insgesamt {n_total_rows} Zeilen geladen.")
|
|
83
|
+
|
|
84
|
+
def load_table_data(self, s_table_name: str, o_source_container: AbstractContainer) -> None:
|
|
85
|
+
"""Laedt Daten fuer eine einzelne Tabelle."""
|
|
86
|
+
# Pruefen ob Tabelle im Schema existiert
|
|
87
|
+
if not self._table_exists_in_schema(s_table_name):
|
|
88
|
+
log_and_raise(f"Tabelle '{s_table_name}' existiert nicht im Schema.")
|
|
89
|
+
|
|
90
|
+
# log_msg(f"Lade Daten fuer Tabelle '{s_table_name}'...")
|
|
91
|
+
|
|
92
|
+
# Iterator fuer Source erstellen
|
|
93
|
+
o_source_iterator = o_source_container.create_iterator()
|
|
94
|
+
|
|
95
|
+
# Zeilen kopieren
|
|
96
|
+
n_row_count = 0
|
|
97
|
+
|
|
98
|
+
while not o_source_iterator.is_empty():
|
|
99
|
+
# Zeile in Ziel-Tabelle einfuegen
|
|
100
|
+
if self._instance.insert_row_from_iterator(s_table_name, o_source_iterator):
|
|
101
|
+
n_row_count += 1
|
|
102
|
+
|
|
103
|
+
o_source_iterator.pp()
|
|
104
|
+
|
|
105
|
+
# Log-Nachricht alle 1000 Zeilen
|
|
106
|
+
if n_row_count % 1000 == 0:
|
|
107
|
+
pass # log_msg(f"Tabelle '{s_table_name}': {n_row_count} Zeilen geladen")
|
|
108
|
+
|
|
109
|
+
# log_msg(f"Tabelle '{s_table_name}': {n_row_count} Zeilen geladen.")
|
|
110
|
+
|
|
111
|
+
def validate_all_constraints(self) -> None:
|
|
112
|
+
"""Prueft nachtraeglich alle Constraints."""
|
|
113
|
+
log_msg("Starte Validierung aller Constraints...")
|
|
114
|
+
|
|
115
|
+
# BulkValidator ausfuehren
|
|
116
|
+
self._bulk_validator.validate_all()
|
|
117
|
+
|
|
118
|
+
def build_indexes_if_valid(self) -> None:
|
|
119
|
+
"""Erstellt Indizes nur bei erfolgreicher Validierung."""
|
|
120
|
+
# log_msg("Erstelle Indizes...")
|
|
121
|
+
|
|
122
|
+
# IndexBuilder ausfuehren
|
|
123
|
+
self._index_builder.build_all_indexes()
|
|
124
|
+
|
|
125
|
+
# log_msg("Indizes erfolgreich erstellt.")
|
|
126
|
+
|
|
127
|
+
def create_read_only_database(self) -> DatabaseInstance:
|
|
128
|
+
"""Gibt fertige DatabaseInstance zurueck."""
|
|
129
|
+
# Pruefen ob alle Schritte durchgefuehrt wurden
|
|
130
|
+
if not self._instance.is_finalized():
|
|
131
|
+
log_and_raise("create_read_only_database: Indizes wurden nicht erstellt.")
|
|
132
|
+
|
|
133
|
+
# Instanz auf Read-Only setzen
|
|
134
|
+
self._instance.set_read_only()
|
|
135
|
+
|
|
136
|
+
log_msg("Read-Only Datenbank erfolgreich erstellt.")
|
|
137
|
+
return self._instance
|
|
138
|
+
|
|
139
|
+
def _table_exists_in_schema(self, s_table_name: str) -> bool:
|
|
140
|
+
"""Hilfsmethode - Prueft ob Tabelle im Schema existiert."""
|
|
141
|
+
o_table_names = self._schema.get_table_names()
|
|
142
|
+
|
|
143
|
+
for v_name in o_table_names:
|
|
144
|
+
if str(v_name) == s_table_name:
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
def get_instance(self) -> DatabaseInstance:
|
|
150
|
+
"""Gibt die DatabaseInstance zurueck (auch vor Finalisierung)."""
|
|
151
|
+
return self._instance
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DatabaseBulkValidator - Nachtraegliche Validierung aller Daten.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from basic_framework.proc_frame import log_msg, log_and_raise
|
|
7
|
+
from basic_framework import is_effectively_null
|
|
8
|
+
from basic_framework.container_utils.abstract_iterator import AbstractIterator
|
|
9
|
+
from bpmn_lib.database.schema.database_schema import DatabaseSchema
|
|
10
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
11
|
+
from bpmn_lib.utils.validation_result import ValidationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DatabaseBulkValidator:
|
|
15
|
+
"""Nachtraegliche Validierung aller Daten."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, instance: DatabaseInstance, result: ValidationResult) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Initialisiert den BulkValidator.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
instance: DatabaseInstance mit den zu validierenden Daten
|
|
23
|
+
result: ValidationResult für Fehlerberichte
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
TypeError: Wenn instance oder result None ist (Python's Typsystem)
|
|
27
|
+
"""
|
|
28
|
+
self._schema: DatabaseSchema = instance.get_schema()
|
|
29
|
+
self._instance: DatabaseInstance = instance
|
|
30
|
+
self._result: ValidationResult = result
|
|
31
|
+
|
|
32
|
+
def validate_all(self) -> None:
|
|
33
|
+
"""Fuehrt alle Validierungen durch."""
|
|
34
|
+
log_msg("Starte Bulk-Validierung der Datenbank...")
|
|
35
|
+
|
|
36
|
+
# 1. Primary Key Validierung
|
|
37
|
+
log_msg("1. Validiere Primary Keys...")
|
|
38
|
+
self.validate_primary_keys()
|
|
39
|
+
|
|
40
|
+
# 2. Foreign Key Validierung
|
|
41
|
+
# log_msg("2. Validiere Foreign Keys...")
|
|
42
|
+
self.validate_foreign_keys()
|
|
43
|
+
|
|
44
|
+
# 3. Not Null Validierung
|
|
45
|
+
# log_msg("3. Validiere NOT NULL Constraints...")
|
|
46
|
+
self.validate_not_null_constraints()
|
|
47
|
+
|
|
48
|
+
# 4. Unique Constraints Validierung
|
|
49
|
+
# log_msg("4. Validiere UNIQUE Constraints...")
|
|
50
|
+
self.validate_unique_constraints()
|
|
51
|
+
|
|
52
|
+
# 5. Value Domain Validierung
|
|
53
|
+
# log_msg("5. Validiere Value Domains...")
|
|
54
|
+
self.validate_value_domains()
|
|
55
|
+
|
|
56
|
+
# 6. Datentyp Validierung
|
|
57
|
+
# log_msg("6. Validiere Datentypen...")
|
|
58
|
+
self.validate_data_types()
|
|
59
|
+
|
|
60
|
+
def validate_primary_keys(self) -> bool:
|
|
61
|
+
"""Validiert Primary Keys (Eindeutigkeit)."""
|
|
62
|
+
# log_msg("Validiere Primary Keys...")
|
|
63
|
+
b_success = True
|
|
64
|
+
|
|
65
|
+
# Fuer jede Tabelle
|
|
66
|
+
o_table_names = self._schema.get_table_names()
|
|
67
|
+
|
|
68
|
+
for v_table_name in o_table_names:
|
|
69
|
+
s_table_name = str(v_table_name)
|
|
70
|
+
|
|
71
|
+
o_table_def = self._schema.get_table_definition(s_table_name)
|
|
72
|
+
o_pk_columns = o_table_def.get_primary_key_columns()
|
|
73
|
+
|
|
74
|
+
if len(o_pk_columns) > 0:
|
|
75
|
+
# Container holen
|
|
76
|
+
o_container = self._instance.get_table(s_table_name)
|
|
77
|
+
|
|
78
|
+
# Temporaerer Index fuer PK-Pruefung
|
|
79
|
+
o_pk_values = {}
|
|
80
|
+
|
|
81
|
+
# Durch alle Zeilen iterieren
|
|
82
|
+
o_iterator = o_container.create_iterator()
|
|
83
|
+
|
|
84
|
+
while not o_iterator.is_empty():
|
|
85
|
+
# PK-Wert(e) zusammenbauen
|
|
86
|
+
s_pk_value = self._build_key_string(o_iterator, o_pk_columns)
|
|
87
|
+
|
|
88
|
+
# Pruefen ob PK bereits existiert
|
|
89
|
+
if s_pk_value in o_pk_values:
|
|
90
|
+
self._add_validation_error(f"Primary Key Verletzung in Tabelle '{s_table_name}': "
|
|
91
|
+
f"Doppelter Wert '{s_pk_value}' in Zeile {o_iterator.position()}")
|
|
92
|
+
b_success = False
|
|
93
|
+
else:
|
|
94
|
+
o_pk_values[s_pk_value] = o_iterator.position()
|
|
95
|
+
|
|
96
|
+
o_iterator.pp()
|
|
97
|
+
|
|
98
|
+
return b_success
|
|
99
|
+
|
|
100
|
+
def validate_not_null_constraints(self) -> bool:
|
|
101
|
+
"""Validiert NOT NULL Constraints."""
|
|
102
|
+
# log_msg("Validiere NOT NULL Constraints...")
|
|
103
|
+
b_success = True
|
|
104
|
+
|
|
105
|
+
# Fuer jede Tabelle
|
|
106
|
+
o_table_names = self._schema.get_table_names()
|
|
107
|
+
|
|
108
|
+
for v_table_name in o_table_names:
|
|
109
|
+
s_table_name = str(v_table_name)
|
|
110
|
+
|
|
111
|
+
o_table_def = self._schema.get_table_definition(s_table_name)
|
|
112
|
+
|
|
113
|
+
# NOT NULL Spalten sammeln
|
|
114
|
+
o_not_null_columns: List[str] = []
|
|
115
|
+
|
|
116
|
+
for v_col_name in o_table_def.get_column_names():
|
|
117
|
+
o_col_def = o_table_def.get_column(str(v_col_name))
|
|
118
|
+
|
|
119
|
+
if o_col_def is None:
|
|
120
|
+
log_and_raise(ValueError(f"Column '{v_col_name}' not found in table '{s_table_name}'"))
|
|
121
|
+
|
|
122
|
+
if not o_col_def.is_nullable():
|
|
123
|
+
o_not_null_columns.append(str(v_col_name))
|
|
124
|
+
|
|
125
|
+
if len(o_not_null_columns) > 0:
|
|
126
|
+
# Container holen
|
|
127
|
+
o_container = self._instance.get_table(s_table_name)
|
|
128
|
+
|
|
129
|
+
# Durch alle Zeilen iterieren
|
|
130
|
+
o_iterator = o_container.create_iterator()
|
|
131
|
+
|
|
132
|
+
n_violations = 0
|
|
133
|
+
|
|
134
|
+
while not o_iterator.is_empty():
|
|
135
|
+
# NOT NULL Spalten pruefen
|
|
136
|
+
for v_col in o_not_null_columns:
|
|
137
|
+
v_value = o_iterator.value(str(v_col))
|
|
138
|
+
|
|
139
|
+
if v_value is None or str(v_value) == "":
|
|
140
|
+
self._add_validation_error(f"NOT NULL Verletzung in Tabelle '{s_table_name}', "
|
|
141
|
+
f"Spalte '{v_col}', Zeile {o_iterator.position()}")
|
|
142
|
+
b_success = False
|
|
143
|
+
n_violations += 1
|
|
144
|
+
|
|
145
|
+
o_iterator.pp()
|
|
146
|
+
|
|
147
|
+
if n_violations == 0:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
return b_success
|
|
151
|
+
|
|
152
|
+
def validate_unique_constraints(self) -> bool:
|
|
153
|
+
"""Validiert Unique Constraints."""
|
|
154
|
+
# log_msg("Validiere Unique Constraints...")
|
|
155
|
+
b_success = True
|
|
156
|
+
|
|
157
|
+
# Fuer jede Tabelle
|
|
158
|
+
o_table_names = self._schema.get_table_names()
|
|
159
|
+
|
|
160
|
+
for v_table_name in o_table_names:
|
|
161
|
+
s_table_name = str(v_table_name)
|
|
162
|
+
|
|
163
|
+
o_table_def = self._schema.get_table_definition(s_table_name)
|
|
164
|
+
o_unique_constraints = o_table_def.get_unique_constraints()
|
|
165
|
+
|
|
166
|
+
if len(o_unique_constraints) > 0:
|
|
167
|
+
# Container holen
|
|
168
|
+
o_container = self._instance.get_table(s_table_name)
|
|
169
|
+
|
|
170
|
+
# Fuer jeden Unique Constraint
|
|
171
|
+
n_constraint_index = 1
|
|
172
|
+
|
|
173
|
+
for o_constraint in o_unique_constraints:
|
|
174
|
+
o_unique_columns = o_constraint
|
|
175
|
+
|
|
176
|
+
# Temporaerer Index fuer Unique-Pruefung
|
|
177
|
+
o_unique_values = {}
|
|
178
|
+
|
|
179
|
+
# Durch alle Zeilen iterieren
|
|
180
|
+
o_iterator = o_container.create_iterator()
|
|
181
|
+
|
|
182
|
+
while not o_iterator.is_empty():
|
|
183
|
+
# Unique-Wert(e) zusammenbauen
|
|
184
|
+
s_unique_value = self._build_key_string(o_iterator, o_unique_columns)
|
|
185
|
+
|
|
186
|
+
# Nur pruefen wenn nicht alle Werte NULL sind
|
|
187
|
+
if s_unique_value != "":
|
|
188
|
+
# Pruefen ob Wert bereits existiert
|
|
189
|
+
if s_unique_value in o_unique_values:
|
|
190
|
+
self._add_validation_error(f"Unique Constraint #{n_constraint_index} verletzt in Tabelle "
|
|
191
|
+
f"'{s_table_name}': Doppelter Wert '{s_unique_value}' "
|
|
192
|
+
f"in Zeile {o_iterator.position()}")
|
|
193
|
+
b_success = False
|
|
194
|
+
else:
|
|
195
|
+
o_unique_values[s_unique_value] = o_iterator.position()
|
|
196
|
+
|
|
197
|
+
o_iterator.pp()
|
|
198
|
+
|
|
199
|
+
n_constraint_index += 1
|
|
200
|
+
|
|
201
|
+
return b_success
|
|
202
|
+
|
|
203
|
+
def validate_foreign_keys(self) -> bool:
|
|
204
|
+
"""Validiert Foreign Key Constraints."""
|
|
205
|
+
# log_msg("Validiere Foreign Keys...")
|
|
206
|
+
b_success = True
|
|
207
|
+
|
|
208
|
+
# Alle FK-Beziehungen durchgehen
|
|
209
|
+
o_relationships = self._schema.get_relationships()
|
|
210
|
+
|
|
211
|
+
for o_rel in o_relationships:
|
|
212
|
+
o_fk_rel = o_rel
|
|
213
|
+
|
|
214
|
+
# Container fuer Source und Target holen
|
|
215
|
+
o_source_container = self._instance.get_table(o_fk_rel.get_source_table())
|
|
216
|
+
o_target_container = self._instance.get_table(o_fk_rel.get_target_table())
|
|
217
|
+
|
|
218
|
+
# Alle Werte aus Target-Tabelle sammeln (fuer Performance)
|
|
219
|
+
o_target_values = {}
|
|
220
|
+
|
|
221
|
+
o_target_iterator = o_target_container.create_iterator()
|
|
222
|
+
|
|
223
|
+
while not o_target_iterator.is_empty():
|
|
224
|
+
s_target_value = o_target_iterator.value(o_fk_rel.get_target_column())
|
|
225
|
+
|
|
226
|
+
if not is_effectively_null(s_target_value):
|
|
227
|
+
s_target_key = str(s_target_value)
|
|
228
|
+
if s_target_key not in o_target_values:
|
|
229
|
+
o_target_values[s_target_key] = True
|
|
230
|
+
else:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
o_target_iterator.pp()
|
|
234
|
+
|
|
235
|
+
# Durch Source-Tabelle iterieren und FK pruefen
|
|
236
|
+
o_source_iterator = o_source_container.create_iterator()
|
|
237
|
+
|
|
238
|
+
n_violations = 0
|
|
239
|
+
|
|
240
|
+
while not o_source_iterator.is_empty():
|
|
241
|
+
v_source_value = o_source_iterator.value(o_fk_rel.get_source_column())
|
|
242
|
+
|
|
243
|
+
# NULL-Werte sind erlaubt fuer FK
|
|
244
|
+
if not is_effectively_null(v_source_value):
|
|
245
|
+
s_source_key = str(v_source_value)
|
|
246
|
+
|
|
247
|
+
# Pruefen ob Wert in Target existiert
|
|
248
|
+
if s_source_key not in o_target_values:
|
|
249
|
+
self._add_validation_error(f"Foreign Key Verletzung: '{o_fk_rel.get_description()}' - "
|
|
250
|
+
f"Wert '{s_source_key}' existiert nicht in Zieltabelle, "
|
|
251
|
+
f"Zeile {o_source_iterator.position()} in '{o_fk_rel.get_source_table()}'")
|
|
252
|
+
b_success = False
|
|
253
|
+
n_violations += 1
|
|
254
|
+
|
|
255
|
+
o_source_iterator.pp()
|
|
256
|
+
|
|
257
|
+
if n_violations > 0:
|
|
258
|
+
log_msg(f"FK '{o_fk_rel.get_description()}': Ungültige Referenzen gefunden.")
|
|
259
|
+
|
|
260
|
+
return b_success
|
|
261
|
+
|
|
262
|
+
def validate_value_domains(self) -> bool:
|
|
263
|
+
"""Validiert Value Domains."""
|
|
264
|
+
# log_msg("Validiere Value Domains...")
|
|
265
|
+
b_success = True
|
|
266
|
+
|
|
267
|
+
# Fuer jede Tabelle
|
|
268
|
+
o_table_names = self._schema.get_table_names()
|
|
269
|
+
|
|
270
|
+
for v_table_name in o_table_names:
|
|
271
|
+
s_table_name = str(v_table_name)
|
|
272
|
+
|
|
273
|
+
o_table_def = self._schema.get_table_definition(s_table_name)
|
|
274
|
+
|
|
275
|
+
# Spalten mit Value Domain sammeln
|
|
276
|
+
o_value_domain_columns: List[str] = []
|
|
277
|
+
|
|
278
|
+
for v_col_name in o_table_def.get_column_names():
|
|
279
|
+
if o_table_def.has_value_domain(str(v_col_name)):
|
|
280
|
+
o_value_domain_columns.append(str(v_col_name))
|
|
281
|
+
|
|
282
|
+
if len(o_value_domain_columns) > 0:
|
|
283
|
+
# Container holen
|
|
284
|
+
o_container = self._instance.get_table(s_table_name)
|
|
285
|
+
|
|
286
|
+
# Durch alle Zeilen iterieren
|
|
287
|
+
o_iterator = o_container.create_iterator()
|
|
288
|
+
|
|
289
|
+
while not o_iterator.is_empty():
|
|
290
|
+
# Value Domain Spalten pruefen
|
|
291
|
+
for v_col in o_value_domain_columns:
|
|
292
|
+
v_value = o_iterator.value(str(v_col))
|
|
293
|
+
|
|
294
|
+
# NULL ist erlaubt
|
|
295
|
+
if v_value is not None and str(v_value) != "":
|
|
296
|
+
# Erlaubte Werte holen
|
|
297
|
+
o_allowed_values = o_table_def.get_value_domain(str(v_col))
|
|
298
|
+
|
|
299
|
+
if o_allowed_values is None:
|
|
300
|
+
log_and_raise(ValueError(f"Value domain for column '{v_col}' in table '{s_table_name}' not found"))
|
|
301
|
+
|
|
302
|
+
# Pruefen ob Wert erlaubt ist
|
|
303
|
+
if not self._is_value_in_collection(str(v_value), o_allowed_values):
|
|
304
|
+
self._add_validation_error(f"Value Domain Verletzung in Tabelle '{s_table_name}', "
|
|
305
|
+
f"Spalte '{v_col}': Wert '{v_value}' nicht erlaubt, "
|
|
306
|
+
f"Zeile {o_iterator.position()}")
|
|
307
|
+
b_success = False
|
|
308
|
+
|
|
309
|
+
o_iterator.pp()
|
|
310
|
+
|
|
311
|
+
return b_success
|
|
312
|
+
|
|
313
|
+
def validate_data_types(self) -> bool:
|
|
314
|
+
"""Validiert Datentypen (Stichproben)."""
|
|
315
|
+
# log_msg("Validiere Datentypen (Stichproben)...")
|
|
316
|
+
# Hier koennten Stichproben-Validierungen durchgefuehrt werden
|
|
317
|
+
# Fuer diese Version gehen wir davon aus, dass beim Import bereits geprueft wurde
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
def _build_key_string(self, o_iterator: AbstractIterator, o_columns: List[str]) -> str:
|
|
321
|
+
"""Hilfsfunktion zum Erstellen von Schluesseln."""
|
|
322
|
+
s_key = ""
|
|
323
|
+
|
|
324
|
+
for v_col in o_columns:
|
|
325
|
+
v_value = o_iterator.value(str(v_col))
|
|
326
|
+
|
|
327
|
+
if s_key != "":
|
|
328
|
+
s_key += "|"
|
|
329
|
+
|
|
330
|
+
if v_value is None:
|
|
331
|
+
s_key += "[NULL]"
|
|
332
|
+
else:
|
|
333
|
+
s_key += str(v_value)
|
|
334
|
+
|
|
335
|
+
return s_key
|
|
336
|
+
|
|
337
|
+
def _is_value_in_collection(self, s_value: str, o_collection: List[str]) -> bool:
|
|
338
|
+
"""Hilfsfunktion zum Pruefen ob Wert in Collection enthalten ist."""
|
|
339
|
+
for v_item in o_collection:
|
|
340
|
+
if str(v_item) == s_value:
|
|
341
|
+
return True
|
|
342
|
+
return False
|
|
343
|
+
|
|
344
|
+
def _add_validation_error(self, s_message: str) -> None:
|
|
345
|
+
"""Fuegt einen Validierungsfehler hinzu."""
|
|
346
|
+
self._result.add_error(s_message)
|
|
347
|
+
|
|
348
|
+
def _add_validation_warning(self, s_message: str) -> None:
|
|
349
|
+
"""Fuegt eine Validierungswarnung hinzu."""
|
|
350
|
+
self._result.add_warning(s_message)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DatabaseIndexBuilder - Erstellt Indizes nach erfolgreicher Validierung.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from basic_framework.proc_frame import log_msg
|
|
7
|
+
from bpmn_lib.database.schema.database_schema import DatabaseSchema
|
|
8
|
+
from bpmn_lib.database.instance.database_instance import DatabaseInstance
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseIndexBuilder:
|
|
12
|
+
"""Erstellt Indizes nach erfolgreicher Validierung."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, instance: DatabaseInstance) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialisiert den IndexBuilder.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
instance: DatabaseInstance mit den zu indizierenden Daten
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
TypeError: Wenn instance None ist (Python's Typsystem)
|
|
23
|
+
"""
|
|
24
|
+
self._schema: DatabaseSchema = instance.get_schema()
|
|
25
|
+
self._instance: DatabaseInstance = instance
|
|
26
|
+
self._index_count: int = 0
|
|
27
|
+
|
|
28
|
+
def build_all_indexes(self) -> None:
|
|
29
|
+
"""Erstellt alle Indizes."""
|
|
30
|
+
# log_msg("IndexBuilder: Starte Index-Erstellung...")
|
|
31
|
+
|
|
32
|
+
self._index_count = 0
|
|
33
|
+
|
|
34
|
+
# Delegiere an DatabaseInstance
|
|
35
|
+
self._instance.create_indexes()
|
|
36
|
+
|
|
37
|
+
# log_msg("IndexBuilder: Index-Erstellung abgeschlossen.")
|
|
38
|
+
|
|
39
|
+
def verify_index_integrity(self) -> bool:
|
|
40
|
+
"""Verifiziert die Integritaet der erstellten Indizes."""
|
|
41
|
+
log_msg("Verifiziere Index-Integritaet...")
|
|
42
|
+
|
|
43
|
+
b_success = True
|
|
44
|
+
|
|
45
|
+
# Pruefungen wuerden hier implementiert
|
|
46
|
+
# Fuer diese Version gehen wir davon aus, dass die Indizes korrekt sind
|
|
47
|
+
|
|
48
|
+
log_msg("Index-Integritaet verifiziert.")
|
|
49
|
+
return b_success
|
|
50
|
+
|
|
51
|
+
def get_index_statistics(self) -> str:
|
|
52
|
+
"""Gibt Statistiken ueber die erstellten Indizes zurueck."""
|
|
53
|
+
s_stats = "INDEX-STATISTIKEN\n"
|
|
54
|
+
s_stats += "=================\n"
|
|
55
|
+
|
|
56
|
+
# Detaillierte Statistiken koennten hier gesammelt werden
|
|
57
|
+
s_stats += "Indizes wurden von DatabaseInstance erstellt.\n"
|
|
58
|
+
|
|
59
|
+
return s_stats
|
|
60
|
+
|
|
61
|
+
def generate_index_report(self) -> str:
|
|
62
|
+
"""Erstellt einen Report ueber alle Indizes."""
|
|
63
|
+
s_report = "INDEX-REPORT\n"
|
|
64
|
+
s_report += "============\n\n"
|
|
65
|
+
|
|
66
|
+
# Fuer jede Tabelle Index-Informationen sammeln
|
|
67
|
+
o_table_names = self._schema.get_table_names()
|
|
68
|
+
|
|
69
|
+
for v_table_name in o_table_names:
|
|
70
|
+
s_report += f"Tabelle: {v_table_name}\n"
|
|
71
|
+
|
|
72
|
+
o_table_def = self._schema.get_table_definition(str(v_table_name))
|
|
73
|
+
|
|
74
|
+
# Primary Key
|
|
75
|
+
o_pk_columns = o_table_def.get_primary_key_columns()
|
|
76
|
+
if len(o_pk_columns) > 0:
|
|
77
|
+
s_report += f" - Primary Key Index: {self._collection_to_string(o_pk_columns)}\n"
|
|
78
|
+
|
|
79
|
+
# Foreign Keys
|
|
80
|
+
o_fks = o_table_def.get_foreign_keys()
|
|
81
|
+
if len(o_fks) > 0:
|
|
82
|
+
for o_fk in o_fks:
|
|
83
|
+
o_fk_rel = o_fk
|
|
84
|
+
s_report += f" - Foreign Key Index: {o_fk_rel.get_source_column()}\n"
|
|
85
|
+
|
|
86
|
+
# Unique Constraints
|
|
87
|
+
o_uniques = o_table_def.get_unique_constraints()
|
|
88
|
+
if len(o_uniques) > 0:
|
|
89
|
+
n_index = 1
|
|
90
|
+
for o_unique in o_uniques:
|
|
91
|
+
o_col = o_unique
|
|
92
|
+
s_report += f" - Unique Index #{n_index}: {self._collection_to_string(o_col)}\n"
|
|
93
|
+
n_index += 1
|
|
94
|
+
|
|
95
|
+
s_report += "\n"
|
|
96
|
+
|
|
97
|
+
return s_report
|
|
98
|
+
|
|
99
|
+
def _collection_to_string(self, o_col: List[str]) -> str:
|
|
100
|
+
"""Hilfsfunktion: Collection zu String."""
|
|
101
|
+
s_result = "("
|
|
102
|
+
|
|
103
|
+
b_first = True
|
|
104
|
+
|
|
105
|
+
for v_item in o_col:
|
|
106
|
+
if not b_first:
|
|
107
|
+
s_result += ", "
|
|
108
|
+
s_result += str(v_item)
|
|
109
|
+
b_first = False
|
|
110
|
+
|
|
111
|
+
s_result += ")"
|
|
112
|
+
return s_result
|