gateway/docs/real-estate-feature-integration-guide/09-database-schema.md

247 lines
8.6 KiB
Markdown

# 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)