247 lines
8.6 KiB
Markdown
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)
|
|
|
|
|
|
|