8.6 KiB
Datenbank-Schema
← Zurück: Feature Lifecycle | Weiter: Sicherheitshinweise →
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,kontextInformationenals JSONB) - Parzelle: Grundstücke mit Bauparametern (mit
parzellenNachbarschaft,dokumente,kontextInformationenals JSONB) - Dokument: Dateien und URLs
- Kontext: Zusatzinformationen
- GeoPolylinie: Geometrische Linien/Polygone (mit
punkteals JSONB) - GeoPunkt: 3D-Koordinaten
- Land: Nationale Ebene (mit
dokumente,kontextInformationenals JSONB) - Kanton: Kantonale Ebene (mit
dokumente,kontextInformationenals JSONB) - Gemeinde: Gemeinde-Ebene (mit
dokumente,kontextInformationenals 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:
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:
# 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 Recorddb.recordUpdate(model_class, recordId, data)- Aktualisiert Recorddb.getRecordset(model_class)- Lädt Recordsdb.getRecord(model_class, recordId)- Lädt einen Record
Beispiel:
# 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:
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 (
*IdFelder) - 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, nichtprojekt)
8. Beispiel: Vollständiger Ablauf
# 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
*IdSuffix - ✅ 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:
-- 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 = '...'
);