# Datenbank-Schema [← Zurück: Feature Lifecycle](08-lifecycle.md) | [Weiter: Sicherheitshinweise →](10-security.md) Die Datenbank-Tabellen werden automatisch vom `DatabaseConnector` erstellt, basierend auf den Pydantic-Modellen: ## Chat-Interface Tabellen: - **RealEstateQuery**: Speichert Abfragen - **RealEstateQueryResult**: Speichert Abfrageergebnisse (mit JSONB für `rowData`) - **RealEstateChatSession**: Speichert Chat-Sessions ## Real Estate-Datenmodell Tabellen: Die folgenden Tabellen werden basierend auf den Real Estate-Datenmodell-Entitäten erstellt: - **Projekt**: Bauprojekte (mit `parzellen`, `dokumente`, `kontextInformationen` als JSONB) - **Parzelle**: Grundstücke mit Bauparametern (mit `parzellenNachbarschaft`, `dokumente`, `kontextInformationen` als JSONB) - **Dokument**: Dateien und URLs - **Kontext**: Zusatzinformationen - **GeoPolylinie**: Geometrische Linien/Polygone (mit `punkte` als JSONB) - **GeoPunkt**: 3D-Koordinaten - **Land**: Nationale Ebene (mit `dokumente`, `kontextInformationen` als JSONB) - **Kanton**: Kantonale Ebene (mit `dokumente`, `kontextInformationen` als JSONB) - **Gemeinde**: Gemeinde-Ebene (mit `dokumente`, `kontextInformationen` als JSONB) --- ## Automatische Tabellenerstellung ### Wie funktioniert die automatische Tabellenerstellung? Der `DatabaseConnector` erstellt Tabellen **automatisch beim ersten Zugriff** auf ein Pydantic-Modell. Sie müssen keine SQL-CREATE-TABLE-Statements manuell schreiben. #### 1. Ablauf der Tabellenerstellung: ``` 1. Code ruft z.B. `db.recordCreate(Projekt, projekt_data)` auf ↓ 2. DatabaseConnector ruft `_ensureTableExists(Projekt)` auf ↓ 3. Prüft ob Tabelle "Projekt" existiert (über information_schema) ↓ 4. Wenn NICHT vorhanden: → Ruft `_create_table_from_model()` auf → Extrahiert Felder aus Pydantic-Modell mit `_get_model_fields()` → Mappt Python-Typen zu SQL-Typen → Erstellt CREATE TABLE Statement → Führt SQL aus → Erstellt Indexes für Foreign Keys ``` #### 2. Typ-Mapping (Python → PostgreSQL): Der `DatabaseConnector` mappt automatisch Pydantic-Feldtypen zu PostgreSQL-Datentypen: | Python/Pydantic Typ | PostgreSQL Typ | Beispiel | |---------------------|----------------|----------| | `str` oder `Optional[str]` | `TEXT` | `label: str` → `"label" TEXT` | | `int` | `INTEGER` | `vollgeschossZahl: int` → `"vollgeschossZahl" INTEGER` | | `float` | `DOUBLE PRECISION` | `az: float` → `"az" DOUBLE PRECISION` | | `bool` | `BOOLEAN` | `closed: bool` → `"closed" BOOLEAN` | | `Dict[str, Any]` oder `dict` | `JSONB` | `parameters: Dict[str, Any]` → `"parameters" JSONB` | | `List[...]` oder `list` | `JSONB` | `parzellen: List[Parzelle]` → `"parzellen" JSONB` | | `Optional[Enum]` | `TEXT` | `statusProzess: StatusProzess` → `"statusProzess" TEXT` | **Spezielle Felder:** - Felder mit Namen `*Id` (z.B. `kontextKantonId`) erhalten automatisch einen Index - Systemfelder werden automatisch hinzugefügt: `_createdAt`, `_createdBy`, `_modifiedAt`, `_modifiedBy` #### 3. Beispiel: CREATE TABLE Statement Für das `Projekt`-Modell würde automatisch folgendes SQL erstellt: ```sql CREATE TABLE IF NOT EXISTS "Projekt" ( "id" VARCHAR(255) PRIMARY KEY, "mandateId" TEXT, "label" TEXT, "statusProzess" TEXT, "perimeter" JSONB, "baulinie" JSONB, "parzellen" JSONB, "dokumente" JSONB, "kontextInformationen" JSONB, "_createdAt" DOUBLE PRECISION, "_modifiedAt" DOUBLE PRECISION, "_createdBy" VARCHAR(255), "_modifiedBy" VARCHAR(255) ); -- Automatisch erstellte Indexes für Foreign Keys: CREATE INDEX IF NOT EXISTS "idx_Projekt_mandateId" ON "Projekt" ("mandateId"); ``` #### 4. Automatische Schema-Migrationen **Wichtig:** Der Connector unterstützt **additive Migrationen**: - Wenn eine Tabelle bereits existiert, werden **fehlende Spalten automatisch hinzugefügt** - **Bestehende Spalten werden NICHT gelöscht oder geändert** - Wenn Sie ein neues Feld zum Pydantic-Modell hinzufügen, wird es beim nächsten Zugriff automatisch als Spalte hinzugefügt **Beispiel:** ```python # Ursprüngliches Modell class Projekt(BaseModel): id: str label: str statusProzess: Optional[StatusProzess] # Später: Neues Feld hinzugefügt class Projekt(BaseModel): id: str label: str statusProzess: Optional[StatusProzess] beschreibung: Optional[str] # NEU # Beim nächsten recordCreate() wird automatisch ausgeführt: # ALTER TABLE "Projekt" ADD COLUMN "beschreibung" TEXT ``` #### 5. Wann werden Tabellen erstellt? Tabellen werden erstellt, wenn Sie **zum ersten Mal** eine der folgenden Operationen ausführen: - `db.recordCreate(model_class, data)` - Erstellt Record - `db.recordUpdate(model_class, recordId, data)` - Aktualisiert Record - `db.getRecordset(model_class)` - Lädt Records - `db.getRecord(model_class, recordId)` - Lädt einen Record **Beispiel:** ```python # Beim ersten Aufruf wird die Tabelle "Projekt" automatisch erstellt interface = getInterface(currentUser) projekt = interface.createProjekt(label="Mein Projekt") # → Tabelle "Projekt" wird jetzt in PostgreSQL erstellt ``` #### 6. Manuelle Tabellenerstellung (optional) Falls Sie Tabellen manuell erstellen möchten (z.B. für Initialisierung), können Sie: ```python from modules.connectors.connectorDbPostgre import DatabaseConnector from modules.datamodels.datamodelRealEstate import Projekt, Parzelle # Connector initialisieren db = DatabaseConnector( dbHost="localhost", dbDatabase="poweron_app", dbUser="poweron_dev", dbPassword="...", dbPort=5432 ) # Tabellen explizit erstellen db._ensureTableExists(Projekt) db._ensureTableExists(Parzelle) # ... weitere Modelle ``` #### 7. Wichtige Hinweise: ✅ **Automatisch:** - Tabellenerstellung beim ersten Zugriff - Spalten-Erstellung basierend auf Pydantic-Feldern - Index-Erstellung für Foreign Keys (`*Id` Felder) - Systemfelder (`_createdAt`, etc.) werden automatisch hinzugefügt ❌ **NICHT automatisch:** - Foreign Key Constraints (werden nicht erstellt - Sie müssen sie manuell hinzufügen falls gewünscht) - Unique Constraints (außer PRIMARY KEY auf `id`) - Check Constraints - Trigger oder Stored Procedures ⚠️ **Einschränkungen:** - **Keine Schema-Änderungen**: Wenn Sie einen Feldtyp ändern (z.B. `str` → `int`), wird die Spalte NICHT automatisch geändert - **Keine Spalten-Löschung**: Gelöschte Felder im Modell werden nicht aus der Datenbank entfernt - **Case-Sensitive**: Tabellennamen werden exakt wie der Klassenname verwendet (z.B. `Projekt`, nicht `projekt`) #### 8. Beispiel: Vollständiger Ablauf ```python # 1. Pydantic-Modell definieren class Projekt(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) mandateId: str label: str statusProzess: Optional[StatusProzess] parzellen: List[Parzelle] = Field(default_factory=list) # 2. Interface initialisieren (erstellt noch keine Tabellen) interface = getInterface(currentUser) # 3. Ersten Record erstellen (erstellt jetzt die Tabelle!) projekt = interface.createProjekt( label="Mein erstes Projekt", statusProzess=StatusProzess.PLANUNG ) # → Intern wird ausgeführt: # 1. _ensureTableExists(Projekt) aufgerufen # 2. Tabelle "Projekt" existiert nicht → wird erstellt # 3. CREATE TABLE "Projekt" (...) wird ausgeführt # 4. Record wird eingefügt # 4. Weitere Records können jetzt ohne Tabellenerstellung erstellt werden projekt2 = interface.createProjekt(label="Zweites Projekt") # → Tabelle existiert bereits, nur INSERT wird ausgeführt ``` --- ## Zusammenfassung: - ✅ **Tabellenname** = Klassenname des Pydantic-Modells (z.B. `Projekt`) - ✅ **Spalten** = Alle Felder aus dem Pydantic-Modell - ✅ **Typen** = Automatisch gemappt (str→TEXT, List→JSONB, etc.) - ✅ **Systemfelder** = Automatisch hinzugefügt (`_createdAt`, `_createdBy`, etc.) - ✅ **Indexes** = Automatisch für Felder mit `*Id` Suffix - ✅ **Migrationen** = Additive Migrationen (neue Spalten werden hinzugefügt) - ⚠️ **Keine Constraints** = Foreign Keys, Unique, Check müssen manuell erstellt werden ## Beispiel-Abfragen auf Real Estate-Datenmodell: ```sql -- Alle Parzellen in einer bestimmten Gemeinde SELECT * FROM Parzelle WHERE plz = '8000' ORDER BY label; -- Projekte mit Status "Planung" SELECT * FROM Projekt WHERE "statusProzess" = 'Planung'; -- Parzellen mit bestimmter Bauzone SELECT label, az, bz, gebaeudehoeheMax FROM Parzelle WHERE bauzone = 'W3'; -- Dokumente eines Projekts SELECT * FROM Dokument WHERE id IN ( SELECT unnest(dokumente::jsonb->>'id') FROM Projekt WHERE id = '...' ); ``` --- [← Zurück: Feature Lifecycle](08-lifecycle.md) | [Weiter: Sicherheitshinweise →](10-security.md)